use anyhow::Result;
use clap::{Parser, Subcommand};
mod auth;
use keynest::{KdfParams, Keynest, Storage, default_storage};
use serde::Serialize;
use std::fmt::Display;
use std::path::PathBuf;

fn print_json<T: Serialize>(value: &T) {
    println!("{}", serde_json::to_string_pretty(value).unwrap());
}

fn print_plain<T: Display>(value: &T) {
    println!("{value}");
}

#[derive(Debug, clap::Args)]
struct Argon2Args {
    /// Argon2 memory cost in KiB (default: 65536)
    #[arg(long = "argon-mem")]
    mem_cost_kib: Option<u32>,

    /// Argon2 time cost / iterations (default: 3)
    #[arg(long = "argon-time")]
    time_cost: Option<u32>,

    /// Argon2 parallelism (default: 1)
    #[arg(long = "argon-parallelism")]
    parallelism: Option<u32>,
}

impl Argon2Args {
    fn to_kdf_params(&self) -> anyhow::Result<KdfParams> {
        let default = KdfParams::default();

        KdfParams::new(
            self.mem_cost_kib.unwrap_or(default.mem_cost_kib()),
            self.time_cost.unwrap_or(default.time_cost()),
            self.parallelism.unwrap_or(default.parallelism()),
        )
    }
}

fn resolve_storage(path: Option<PathBuf>) -> Result<Storage> {
    match path {
        Some(p) => Ok(Storage::new(p)),
        None => default_storage(),
    }
}

#[derive(Debug, Parser)]
#[command(name = "keynest")]
#[command(
    version,
    about = "Simple, offline, cross-platform secrets manager written in Rust."
)]
struct Cli {
    /// Path to the keynest storage file
    #[arg(long, global = true, value_name = "PATH", env = "KEYNEST_PATH")]
    store: Option<PathBuf>,

    #[command(subcommand)]
    command: Commands,
}

#[derive(Debug, Subcommand)]
enum Commands {
    /// Initializes the secret storage
    #[command(after_help = "\
Examples:
  keynest init                                      Initialize a new keystore with default settings
  keynest init --argon-mem 131072                 Initialize with higher memory cost (128 MiB)
  keynest init --argon-time 5 --argon-mem 65536   Initialize with custom Argon2 parameters")]
    Init {
        #[command(flatten)]
        argon2: Argon2Args,
    },

    /// Change password and/or argon2 parameters and re-encrypt keystore
    #[command(after_help = "\
Examples:
  keynest rekey                                  Change the keystore password
  keynest rekey --argon-mem 131072              Change password and upgrade memory cost")]
    Rekey {
        #[command(flatten)]
        argon2: Argon2Args,
    },

    /// Stores a secret by name
    #[command(
        arg_required_else_help = false,
        after_help = "\
Examples:
  keynest set api_key \"secret123\"              Store a secret from command line argument
  keynest set api_key --file secret.txt         Store a secret from a file
  keynest set api_key --prompt                   Store a secret from interactive prompt"
    )]
    Set {
        key: String,
        value: Option<String>,
        /// Prompt for secret value
        #[arg(long = "prompt")]
        prompt: bool,
        /// Read secret from file
        #[arg(long = "file")]
        file: Option<PathBuf>,
    },

    /// Retrieves secret by name
    #[command(
        arg_required_else_help = true,
        after_help = "\
Examples:
  keynest get api_key                              Display the secret value on stdout
  keynest get api_key --clip                       Copy the secret to clipboard (auto-clears after 15 seconds)
  keynest get api_key -c --timeout 30              Copy the secret to clipboard with custom timeout
  keynest get api_key --json                       Output the secret as JSON (includes key and value)"
    )]
    Get {
        key: String,
        /// Copy secret to clipboard
        #[arg(long, short = 'c')]
        clip: bool,
        /// Seconds before clipboard is cleared (default: 15, min: 1)
        #[arg(long = "timeout", default_value_t = 15)]
        timeout: u64,
        /// Output as JSON
        #[arg(long, short = 'j')]
        json: bool,
    },

    /// Updates existing secret value by name
    #[command(
        arg_required_else_help = true,
        after_help = "\
Examples:
  keynest update api_key \"new_secret\"          Update an existing secret value"
    )]
    Update { key: String, new_value: String },

    /// Lists all stored secrets
    #[command(
        arg_required_else_help = false,
        after_help = "\
Examples:
  keynest list                                   List all secret keys
  keynest list --all                            List all secrets with timestamps
  keynest list --json                           List all keys as JSON array
  keynest list --all --json                    List all secrets with timestamps as JSON"
    )]
    List {
        #[arg(required = false, short, long, default_value_t = false)]
        /// Print names and secrets
        all: bool,
        /// Output as JSON
        #[arg(long, short = 'j')]
        json: bool,
    },

    /// Removes secrets by name
    #[command(
        arg_required_else_help = true,
        after_help = "\
Examples:
  keynest remove api_key                         Remove a secret from the keystore"
    )]
    Remove { key: String },

    /// Shows information about the store
    #[command(after_help = "\
Examples:
  keynest info                                   Show keystore information (version, algorithm, KDF parameters)
  keynest info --json                            Output information as JSON")]
    Info {
        /// Output as JSON
        #[arg(long, short = 'j')]
        json: bool,
    },
}

fn copy_to_clipboard(secret: &str, timeout: u64) -> anyhow::Result<()> {
    use arboard::Clipboard;
    use std::{thread::sleep, time::Duration};

    let mut cb = Clipboard::new()?;
    let old = cb.get_text().ok();

    cb.set_text(secret.to_string())?;

    eprintln!("Secret copied to clipboard for {timeout}s");
    eprintln!("Press Ctrl+C to clear early");

    let old_clip = old.clone();
    ctrlc::set_handler(move || {
        if let Ok(mut cb) = Clipboard::new() {
            let _ = cb.set_text(old_clip.clone().unwrap_or_default());
        }
        sleep(Duration::from_millis(100));
        std::process::exit(0);
    })?;

    let had_old = old.is_some();
    sleep(Duration::from_secs(timeout));

    let mut cb = Clipboard::new()?;
    cb.set_text(old.unwrap_or_default())?;

    eprintln!("Clipboard {}", if had_old { "restored" } else { "cleared" });

    Ok(())
}

fn main() -> anyhow::Result<()> {
    let args = Cli::parse();
    let password = auth::read_password()?;
    match args.command {
        Commands::Init { argon2 } => {
            let kdf = argon2.to_kdf_params()?;
            let storage = resolve_storage(args.store.clone())?;
            Keynest::init_with_storage_and_kdf(password, storage, kdf)?;
            println!("keystore initialized");
        }
        Commands::Set {
            key,
            value,
            prompt,
            file,
        } => {
            let secret = if prompt {
                rpassword::prompt_password("Secret: ")?
            } else if let Some(path) = file {
                std::fs::read_to_string(&path)?
            } else {
                value.ok_or_else(|| {
                    anyhow::anyhow!(
                        "secret value required: provide as argument, --prompt, or --file"
                    )
                })?
            };

            let storage = resolve_storage(args.store.clone())?;
            let mut kn = Keynest::open_with_storage(password, storage)?;
            kn.set(&key, &secret)?;
            kn.save()?;
            println!("stored secret '{key}'");
        }
        Commands::Update { key, new_value } => {
            let storage = resolve_storage(args.store.clone())?;
            let mut kn = Keynest::open_with_storage(password, storage)?;
            kn.update(&key, &new_value)?;
            kn.save()?;
            println!("secret '{key}' updated.");
        }
        Commands::Get {
            key,
            clip,
            timeout,
            json,
        } => {
            if timeout == 0 {
                anyhow::bail!("timeout must be greater than 0");
            }

            let storage = resolve_storage(args.store.clone())?;
            let kn = Keynest::open_with_storage(password, storage)?;
            match kn.get(&key) {
                Some(secret) => {
                    if clip {
                        copy_to_clipboard(secret, timeout)?;
                    } else if json {
                        print_json(&serde_json::json!({"key": key, "value": secret}));
                    } else {
                        print_plain(&secret);
                    }
                }
                None => {
                    eprintln!("key not found: {key}");
                    std::process::exit(1);
                }
            }
        }
        Commands::List { all, json } => {
            let storage = resolve_storage(args.store.clone())?;
            let kn = Keynest::open_with_storage(password, storage)?;

            if json {
                if all {
                    let entries: Vec<_> = kn
                        .list_all()
                        .iter()
                        .map(|e| {
                            serde_json::json!({
                                "key": e.key(),
                                "updated": e.updated()
                            })
                        })
                        .collect();
                    print_json(&entries);
                } else {
                    let keys: Vec<&str> = kn.list().iter().map(|s| s.as_str()).collect();
                    print_json(&keys);
                }
            } else if all {
                let entries = kn.list_all();

                if entries.is_empty() {
                    println!("No secrets stored.");
                    return Ok(());
                }

                let key_width = entries
                    .iter()
                    .map(|e| e.key().len())
                    .chain(std::iter::once("Key".len()))
                    .max()
                    .unwrap();

                let updated_width = entries
                    .iter()
                    .map(|e| e.updated().len())
                    .chain(std::iter::once("Updated".len()))
                    .max()
                    .unwrap();

                println!("{:<key_width$}  {:<updated_width$}", "Key", "Updated");
                println!("{:-<key_width$}  {:-<updated_width$}", "", "");

                for e in entries {
                    println!("{:<key_width$}  {:<updated_width$}", e.key(), e.updated());
                }
            } else {
                for secret_key in kn.list() {
                    println!("{secret_key}");
                }
            }
        }
        Commands::Remove { key } => {
            let storage = resolve_storage(args.store.clone())?;
            let mut kn = Keynest::open_with_storage(password, storage)?;
            kn.remove(&key)?;
            kn.save()?;
            println!("Removed '{key}'");
        }
        Commands::Info { json } => {
            let storage = resolve_storage(args.store.clone())?;
            let kn = Keynest::open_with_storage(password, storage.clone())?;
            let info = kn.info()?;

            if json {
                print_json(&info);
            } else {
                println!("{info}");
            }
        }
        Commands::Rekey { argon2 } => {
            let storage = resolve_storage(args.store.clone())?;
            let mut kn = Keynest::open_with_storage(password, storage)?;

            let new_password = auth::read_new_password_with_confirmation()?;

            let kdf = argon2.to_kdf_params()?;
            kn.rekey(new_password, kdf)?;

            println!("store successfully rekeyed");
        }
    }

    Ok(())
}
