#[macro_use]
extern crate rocket;
extern crate tokio;

use clap::Parser;
use serde_json::{from_str, to_string_pretty, to_value};

mod cli;
use cli::{
    Cli, Commands, invite::InviteCommands, member::MemberCommands, ring::RingCommands,
    site::SiteCommands,
};
pub mod consts;
use consts::{DATABASE_URL, MANIFEST_VERSION, LISTEN_ADDRESS, LISTEN_PORT};
pub mod db;
pub mod error;
pub mod utils;
use error::RingspaceError;
use utils::{
    Keypair, generate_encryption_keypair, generate_signing_keypair, sign_value, verify_value,
};
use utils::{get_input, get_vote};
pub mod invite;
use invite::{Invite, InviteResponse};
pub mod joined_ring;
use joined_ring::JoinedRing;
pub mod manifest;
use manifest::{
    MemberManifest, MemberManifestResponse, RingManifest, RingManifestResponse,
};
pub mod member;
use member::{
    Member, RotationRequest, RotationRequestPayload, UnenrollRequestPayload, UnenrollRequest
};
pub mod membership;
use membership::Membership;
pub mod server;
pub mod site;
use site::Site;
pub mod standing;
use standing::{Standing, StandingResult, VoteSplit};
pub mod managed_ring;
use managed_ring::ManagedRing;
pub mod member_vote;
use member_vote::MemberVote;
pub mod site_vote;
use site_vote::SiteVote;
pub mod jwks;

pub async fn run_cli() -> Result<(), RingspaceError> {
    // Check for a .env file. This won't be true in Docker since
    // we load the variables from the runtime.
    let args = Cli::parse();
    match args.command {
        Commands::Ring(ring_args) => {
            let mut conn = db::get_db_connection().await?;
            let ring_cmd = ring_args.command.unwrap();

            match ring_cmd {
                RingCommands::Create {
                    name,
                    url,
                    description,
                    default_ring,
                } => {
                    let name = name.unwrap_or_else(|| get_input("Ring Name:"));
                    let url = url.unwrap_or_else(|| get_input("Ring URL:"));
                    let description = description.unwrap_or_else(|| get_input("Ring description:"));
                    let ring = ManagedRing::new(&name, &url, &description, default_ring);
                    ring.save(&mut conn).await?;
                    println!("{}", to_string_pretty(&ring)?);
                }
                RingCommands::List => {
                    let rings = managed_ring::list(&mut conn).await?;
                    let rings_json = to_string_pretty(&rings).unwrap();
                    println!("{rings_json}");
                }
                RingCommands::Get { ring_url } => {
                    let ring = match ring_url {
                        Some(u) => managed_ring::get_by_url(&mut conn, &u).await?,
                        None => managed_ring::get_default(&mut conn).await?,
                    };
                    println!("{}", to_string_pretty(&ring).unwrap());
                }

                RingCommands::Delete { ring_url } => {
                    let confirm = get_input(
                        "This will delete the ring and all associated members and votes. Are you sure? (y/N)",
                    );
                    if confirm == "y" || confirm == "Y" {
                        let ring = managed_ring::get_by_url(&mut conn, &ring_url).await?;
                        match ring.delete(&mut conn).await {
                            Ok(_) => {
                                println!("Ring {ring_url} deleted.");
                            }
                            Err(e) => {
                                println!("{e}");
                            }
                        }
                    }
                }
                RingCommands::SetDefault { ring_url } => {
                    let mut ring = managed_ring::get_by_url(&mut conn, &ring_url).await?;
                    ring.default_ring = true;
                    match ring.save(&mut conn).await {
                        Ok(_) => {
                            println!("Default ring set to {}", ring_url);
                        }
                        Err(e) => {
                            println!("{e}");
                        }
                    }
                }
                RingCommands::DecryptVote {
                    ring_url,
                    encrypted_vote,
                } => {
                    let ring = match ring_url {
                        Some(u) => managed_ring::get_by_url(&mut conn, &u).await?,
                        None => managed_ring::get_default(&mut conn).await?,
                    };

                    match ring.decrypt_vote(&encrypted_vote).await {
                        Ok(v) => println!(
                            "{}",
                            to_string_pretty(&from_str::<serde_json::Value>(&v).unwrap()).unwrap()
                        ),
                        Err(e) => println!("{e:?}"),
                    };
                }
                RingCommands::Manifest { ring_url } => {
                    let ring = match ring_url {
                        Some(u) => managed_ring::get_by_url(&mut conn, &u).await?,
                        None => managed_ring::get_default(&mut conn).await?,
                    };
                    let manifest: RingManifestResponse = ring.manifest(&mut conn).await?;
                    println!("{}", to_string_pretty(&manifest).unwrap())
                }
                RingCommands::FetchVotes { ring_url } => {
                    let ring = match ring_url {
                        Some(u) => managed_ring::get_by_url(&mut conn, &u).await?,
                        None => managed_ring::get_default(&mut conn).await?,
                    };
                    let members = ring.clone().members(&mut conn).await?;
                    for m in members {
                        println!("Fetching manifest for {}", m.url);
                        let manifest_response = match manifest::fetch_member(&m.url).await {
                            Ok(m) => m,
                            Err(e) => {
                                println!("Could not fetch manifest for {}", &m.url);
                                println!("{e}");
                                continue;
                            }
                        };
                        let Ok(member) =
                            member::get_by_url(&mut conn, &manifest_response.manifest.url, ring.id)
                                .await
                        else {
                            println!("Could not find Member data for {}", &m.url);
                            continue;
                        };

                        let manifest_value = to_value(&manifest_response.manifest)?;

                        // If the manifest doesn't verify, note and move on
                        match verify_value(
                            &manifest_value,
                            &manifest_response.signature,
                            &member.public_signing_key,
                        ) {
                            Ok(verification_result) => {
                                if verification_result {
                                    println!("Manifest verified for {}", &m.url);
                                } else {
                                    println!("Manifest verification failed for {}", &m.url);
                                    continue;
                                }
                            }
                            Err(e) => {
                                println!("Manifest verification error for {}", &m.url);
                                println!("{e}");
                                continue;
                            }
                        };

                        // Verified manifest in hand, we can extract votes for our ring,
                        // if they exist, send 'em to update_member_votes for processing
                        let Some(ring_data) =
                            manifest_response.manifest.rings.get(&ring.clone().url)
                        else {
                            println!("No ring data for {}", ring.clone().url);
                            continue;
                        };
                        // Extract votes from ring data
                        let Some(votes) = ring_data.get("votes") else {
                            println!("Could not extract votes for ring {}", ring.clone().url);
                            continue;
                        };
                        println!("Votes extracted");

                        // Convert votes Value to object
                        let Some(votes_obj) = votes.as_object() else {
                            println!(
                                "Votes field is not an object for ring {:?}",
                                ring.clone().url
                            );
                            continue;
                        };

                        // Loop through values
                        for v in votes_obj.values() {
                            let Some(vote_str) = v.as_str() else {
                                println!("Vote value is not a string");
                                continue;
                            };
                            // Decrypt vote
                            let Ok(decrypted_vote) = ring.decrypt_vote(vote_str).await else {
                                println!("Could not decrypt vote.");
                                continue;
                            };
                            // This is a little confusing. We are pulling a [SiteVote]
                            // from the Members' manifests. At this point, it's a
                            // [SiteVote] because it is not yet part of the Ring's database.
                            //
                            // Our job here is to convert it into a [MemberVote]
                            let Ok(site_vote) = from_str::<SiteVote>(&decrypted_vote) else {
                                println!("Could not parse vote JSON");
                                continue;
                            };

                            let member_vote = MemberVote::new(
                                ring.id,
                                member.id,
                                site_vote.vote_subject_url,
                                site_vote.standing,
                                site_vote.description,
                            );

                            // Check if vote is valid
                            match member_vote.clone().validate(&mut conn).await {
                                Ok(validation_result) => {
                                    if validation_result {
                                        println!("Valid vote");
                                    } else {
                                        println!(
                                            "Invalid vote for {}",
                                            &member_vote.vote_subject_url
                                        );
                                        continue;
                                    }
                                }
                                Err(e) => {
                                    println!("{e}");
                                    continue;
                                }
                            }
                            println!("New vote for {}", member_vote.vote_subject_url);

                            // Attempt to add vote now that we've sorted all the rest out
                            if let Err(e) = member_vote.clone().save(&mut conn).await {
                                println!("Failed to add vote: {e}");
                                continue;
                            }
                            println!("{}", to_string_pretty(&member_vote).unwrap());
                        }
                    }
                }
            }
        }

        Commands::Site(site_cmd) => {
            
            let mut conn = db::get_db_connection().await?;
            match site_cmd.command.unwrap() {
                SiteCommands::List => match site::list(&mut conn).await {
                    Ok(sites) => {
                        let sites_json = to_string_pretty(&sites).unwrap();
                        println!("{sites_json}");
                    }
                    Err(e) => {
                        println!("{e}");
                    }
                },
                SiteCommands::Create {
                    name,
                    url,
                    description,
                    default_site,
                } => {
                    let name = name.unwrap_or_else(|| get_input("Site Name:"));
                    let url = url.unwrap_or_else(|| get_input("Site URL:"));
                    let description = description.unwrap_or_else(|| get_input("Site description:"));
                    let new_site = Site::new(name, url, description, default_site);
                    match new_site.save(&mut conn).await {
                        Ok(_) => {
                            println!("{}", to_string_pretty(&new_site)?);
                        }
                        Err(e) => {
                            println!("{e:#?}");
                        }
                    }
                }
                SiteCommands::Get { site_url } => {
                    let site = match site_url {
                        Some(u) => site::get_by_url(&mut conn, &u).await?,
                        None => site::get_default(&mut conn).await?,
                    };
                    println!("{}", to_string_pretty(&site).unwrap());
                }
                SiteCommands::Delete { site_url } => {
                    let confirm = get_input(
                        "This will delete the ring and all associated members and votes. Are you sure? (y/N)",
                    );
                    if confirm == "y" || confirm == "Y" {
                        let site = site::get_by_url(&mut conn, &site_url).await?;
                        site.delete(&mut conn).await?;
                        println!("Site deleted");
                    }
                }
                SiteCommands::JoinRing {
                    site_url,
                    ring_url,
                    invite_code,
                    default_ring,
                } => {
                    let site = match site_url {
                        Some(u) => site::get_by_url(&mut conn, &u).await?,
                        None => site::get_default(&mut conn).await?,
                    };

                    match site.join_ring(&ring_url, &invite_code).await {
                        Ok(ring_data) => {
                             // safety catch for keys
                             let ring = match JoinedRing::new(
                                &ring_data.name,
                                &ring_data.url,
                                &ring_data.description,
                                ring_data.manifest_version,
                                &ring_data.keyset,
                                default_ring,
                            ) {
                                Ok(r) => r,
                                Err(e) => {
                                    println!("{e}");
                                    return Err(e) ;
                                }
                            };

                            ring.save(&mut conn).await?;
                            // Get newly-created ring
                            let ring = joined_ring::get_by_url(&mut conn, &ring_data.url).await?;
                            // Use ring to create membership record
                            let new_membership = Membership::new(ring.id, site.id);
                            new_membership.save(&mut conn).await?;
                            // Print ring details
                            println!("{}", to_string_pretty(&ring).unwrap());
                        }
                        Err(e) => {
                            println!("{e}");
                        }
                    }
                }
                SiteCommands::LeaveRing { site_url, ring_url } => {
                    let site = match site_url {
                        Some(u) => site::get_by_url(&mut conn, &u).await?,
                        None => site::get_default(&mut conn).await?,
                    };
                    let confirm =
                        get_input("This will unenroll you from the ring. Are you sure? (y/N)");
                    if confirm == "y" || confirm == "Y" {
                        site.leave_ring(&mut conn, &ring_url).await?;
                        println!("{} unenrolled from {}", site.url, ring_url);
                    }
                }
                SiteCommands::Manifest { site_url } => {
                    let site = match site_url {
                        Some(u) => site::get_by_url(&mut conn, &u).await?,
                        None => site::get_default(&mut conn).await?,
                    };
                    let manifest = site.manifest(&mut conn).await?;
                    println!("{}", to_string_pretty(&manifest).unwrap());
                }
                SiteCommands::Rotate { site_url } => {
                    let confirm =
                        get_input("This will rotate signing keys for the site. Are you sure? (y/N)");
                    if confirm == "y" || confirm == "Y" {
                        let mut site = match site_url {
                            Some(u) => site::get_by_url(&mut conn, &u).await?,
                            None => site::get_default(&mut conn).await?,
                        };
                        match site.request_rotation(&mut conn).await {
                            Ok(keypair) => {
                                site.private_signing_key = keypair.private_key;
                                site.public_signing_key = keypair.public_key;
                                site.save(&mut conn).await?;
                            }
                            Err(e) => {
                                println!("{e}");
                            }
                        }
                    }
                }
                SiteCommands::SetDefault { site_url } => {
                    let mut site = site::get_by_url(&mut conn, &site_url).await?;
                    site.default_site = true;
                    site.save(&mut conn).await?;
                    println!("Set {} to default site", { site.url });
                }

                SiteCommands::Vote {
                    url,
                    ring_url,
                    vote_subject,
                    standing,
                    description,
                } => {
                    let site = match url {
                        Some(u) => site::get_by_url(&mut conn, &u).await?,
                        None => site::get_default(&mut conn).await?,
                    };

                    let ring = match ring_url {
                        Some(u) => joined_ring::get_by_url(&mut conn, &u).await?,
                        None => joined_ring::get_default(&mut conn).await?,
                    };

                    let standing = Standing::try_from(standing)?;

                    let new_vote = SiteVote::new(
                        ring.id,
                        site.id,
                        vote_subject,
                        standing.to_string(),
                        description,
                    );
                    new_vote.save(&mut conn).await?;
                }

                SiteCommands::VoteWizard { site_url } => {
                    let site = match site_url {
                        Some(u) => site::get_by_url(&mut conn, &u).await?,
                        None => site::get_default(&mut conn).await?,
                    };
                    let memberships = site.memberships(&mut conn).await?;
                    for m in memberships {
                        let ring = joined_ring::get_by_id(&mut conn, m.ring_id).await?;
                        let manifest_res: RingManifestResponse =
                            manifest::fetch_ring(&ring.url).await?;

                        let manifest_value = to_value(&manifest_res.manifest)?;
                        // Verify signature
                        match verify_value(
                            &manifest_value,
                            &manifest_res.signature,
                            &ring.public_signing_key,
                        ) {
                            Ok(verification_result) => {
                                if !verification_result {
                                    println!("Manifest verification failed for {}", ring.url);
                                    continue;
                                }

                                let ring_manifest: RingManifest = manifest_res.manifest;
                                // Iterate through member URLs (except for our own)
                                for url in ring_manifest.members.keys().filter(|&u| u != &site.url) {
                                    let standing = get_vote(url)?;
                                    let description = get_input("Reason for vote: ");
                                    let site_vote = SiteVote::new(
                                        ring.id,
                                        site.id,
                                        url.to_string(),
                                        standing.to_string(),
                                        description,
                                    );
                                    site_vote.save(&mut conn).await?;
                                }
                            }
                            Err(e) => {
                                println!("{e}");
                                continue;
                            }
                        }
                    }
                }
            }
        }

        Commands::Invite(invite_args) => {
            let mut conn = db::get_db_connection().await?;
            let invite_cmd = invite_args.command.unwrap();
            match invite_cmd {
                InviteCommands::Create {
                    url,
                    expiration_hours,
                    ring_url,
                } => {
                    let ring = match ring_url {
                        Some(u) => managed_ring::get_by_url(&mut conn, &u).await?,
                        None => managed_ring::get_default(&mut conn).await?,
                    };

                    let new_invite = Invite::new(url.clone(), ring.id, expiration_hours);
                    match new_invite.save(&mut conn).await {
                        Ok(_) => {
                            println!("Created invite for {} on ring {}", url, ring.url);
                            println!("Invite Code:: {}", new_invite.invite_code);
                        }
                        Err(e) => {
                            println!("{e}");
                        }
                    }
                }
                InviteCommands::List => match invite::list(&mut conn).await {
                    Ok(invites) => {
                        let invites_json = to_string_pretty(&invites).unwrap();
                        println!("{invites_json}");
                    }
                    Err(e) => {
                        println!("{e}");
                    }
                },
                InviteCommands::Revoke { invite_id, confirm } => {
                    if let Ok(invite) = invite::get_by_id(&mut conn, invite_id).await {
                        let ring = managed_ring::get_by_id(&mut conn, invite.ring_id).await?;
                        let confirm_res = confirm.unwrap_or_else(|| {
                            
                            let confirm_input = get_input(format!(
                                "This will DELETE the invite to ring {} for {}. Are you sure? (y/N) ", ring.url, invite.url).as_str(),
                            );
                            if confirm_input == "y" || confirm_input == "Y" {
                                return true;
                            }
                            false
                        });
                        if confirm_res {
                            match invite.delete(&mut conn).await {
                                Ok(_) => {
                                    println!("Deleted Invite {invite_id}");
                                }
                                Err(e) => {
                                    println!("{e}");
                                }
                            }
                        }
                    } else {
                        println!("No such invite ID: {invite_id}");
                    }
                }
            }
        }

        Commands::Member(member_args) => {
            let mut conn = db::get_db_connection().await?;
            let member_cmd = member_args.command.unwrap();

            match member_cmd {
                MemberCommands::Enroll {
                    ring_url,
                    name,
                    description,
                    url,
                    public_signing_key,
                } => {
                    let ring = match ring_url {
                        Some(u) => managed_ring::get_by_url(&mut conn, &u).await?,
                        None => managed_ring::get_default(&mut conn).await?,
                    };
                    let member_name = name.unwrap_or_else(|| get_input("Member Name:"));
                    let member_url = url.unwrap_or_else(|| get_input("Member URL:"));
                    let member_description =
                        description.unwrap_or_else(|| get_input("Member description:"));
                    let member_public_signing_key = public_signing_key
                        .unwrap_or_else(|| get_input("Member Public Signing Key:"));
                    let new_member = member::Member::new(
                        member_name,
                        member_url,
                        member_description,
                        MANIFEST_VERSION,
                        member_public_signing_key,
                        ring.id,
                    );
                    match new_member.save(&mut conn).await {
                        Ok(_) => {
                            println!("Enrolled {}", new_member.name);
                        }
                        Err(e) => {
                            panic!("{e}");
                        }
                    }
                }
                MemberCommands::List { ring_url } => {
                    let ring = match ring_url {
                        Some(u) => managed_ring::get_by_url(&mut conn, &u).await?,
                        None => managed_ring::get_default(&mut conn).await?,
                    };
                    let members: Vec<Member> = ring.members(&mut conn).await?;
                    let members_json = to_string_pretty(&members).unwrap();
                    println!("{members_json}");
                }
                MemberCommands::Unenroll { url, ring_url } => {
                    let ring = match ring_url {
                        Some(u) => managed_ring::get_by_url(&mut conn, &u).await?,
                        None => managed_ring::get_default(&mut conn).await?,
                    };
                    let mut member = member::get_by_url(&mut conn, &url, ring.id).await?;
                    member.enrolled = false;
                    match member.save(&mut conn).await {
                        Ok(_) => {
                            println!("Unenrolled {} from {}", url, ring.name);
                        }
                        Err(e) => {
                            println!("{e}");
                        }
                    }
                }
                MemberCommands::Rotate {
                    url,
                    ring_url,
                    new_key,
                } => {
                    let ring = match ring_url {
                        Some(u) => managed_ring::get_by_url(&mut conn, &u).await?,
                        None => managed_ring::get_default(&mut conn).await?,
                    };
                    let mut member = member::get_by_url(&mut conn, &url, ring.id).await?;
                    member.public_signing_key = new_key.clone();
                    match member.save(&mut conn).await {
                        Ok(_) => {
                            println!("Member {} of ring {} set to {}", url, ring.url, new_key);
                        }
                        Err(e) => {
                            println!("{e}");
                        }
                    }
                }
                MemberCommands::Standing {
                    ring_url,
                    member_url,
                } => {
                    let ring = match ring_url {
                        Some(u) => managed_ring::get_by_url(&mut conn, &u).await?,
                        None => managed_ring::get_default(&mut conn).await?,
                    };
                    let member = member::get_by_url(&mut conn, &member_url, ring.id).await?;
                    let standing = member.standing(&mut conn).await?;
                    println!("{}", to_string_pretty(&standing).unwrap());
                }
                MemberCommands::VerifyManifest {
                    ring_url,
                    member_url,
                } => {
                    let ring = match ring_url {
                        Some(u) => managed_ring::get_by_url(&mut conn, &u).await?,
                        None => managed_ring::get_default(&mut conn).await?,
                    };

                    let member = member::get_by_url(&mut conn, &member_url, ring.id).await?;

                    match manifest::fetch_member(&member_url).await {
                        Ok(manifest_response) => {
                            let manifest = &manifest_response.manifest;

                            // Extract signing key
                            let signing_key = match manifest.keyset.get_signing_key() {
                                Some(k) => k,
                                None => {
                                    return Err(RingspaceError::from("Missing signing key"));
                                }
                            };

                            // Signing key match?
                            println!("Local public key: {}", member.public_signing_key);
                            println!("Remote public key: {}", signing_key.x);
                            if signing_key.x != member.public_signing_key {
                                return Err(RingspaceError::from("key mismatch"));
                            }

                            let manifest_value = to_value(&manifest_response.manifest)?;

                            match verify_value(
                                &manifest_value,
                                &manifest_response.signature,
                                &member.public_signing_key,
                            ) {
                                Ok(verification_result) => {
                                    if verification_result {
                                        println!("Manifest verified")
                                    } else {
                                        println!("Manifest verification failed");
                                    }
                                }
                                Err(e) => {
                                    println!("{e}");
                                }
                            }
                        }
                        Err(e) => {
                            println!("{e}")
                        }
                    }
                }
            }
        }
        Commands::Server { port, ring_url, address } => {
            let mut conn = db::get_db_connection().await?;
            let ring = match ring_url {
                Some(u) => managed_ring::get_by_url(&mut conn, &u).await?,
                None => managed_ring::get_default(&mut conn).await?,
            };
            // Launch
            // We send the options over to Rocket to sort out precedence
            // of address/port setting sources.
            let _ = server::rocket(port, address, ring).await;
        }
        Commands::Enckey => {
            println!(
                "{}",
                to_string_pretty(&generate_encryption_keypair()).unwrap()
            );
        }
        Commands::Signkey => {
            println!("{}", to_string_pretty(&generate_signing_keypair()).unwrap());
        }

        Commands::Migrate => {
            match db::migrate().await {
                Ok(_) => { println!("Database migrated successfully!"); }
                Err(e) => { println!("{e}"); }
            }
        }
    }
    Ok(())
}
