use crate::config::*;
use crate::repository::Repository;
use anyhow::bail;
use clap::{CommandFactory, Parser};
use log::debug;

use std::io::Write;
use std::path::Path;

#[derive(Parser, Debug)]
pub struct Key {
    #[clap(subcommand)]
    subcmd: Option<SubCommand>,
}

#[derive(Parser, Debug)]
pub enum SubCommand {
    /// Generate a new key. The name used for a key is not required to
    /// match a user's remote or SSH credentials. By default, new keys
    /// are stored in your global configuration directory.
    Generate {
        #[clap(long = "email")]
        email: Option<String>,
        login: String,
    },
    /// Associate a generated key with a remote identity. Patches authored
    /// by unproven keys will only display the key as the author. Example
    /// of proving a key (after generating one): `pijul key prove <nestlogin>@ssh.pijul.com`
    Prove {
        #[clap(short = 'k')]
        no_cert_check: bool,
        remote: String,
    },
}

impl Key {
    pub async fn run(self) -> Result<(), anyhow::Error> {
        match self.subcmd {
            Some(SubCommand::Generate { email, login }) => {
                if let Some(mut dir) = global_config_dir() {
                    std::fs::create_dir_all(&dir)?;
                    dir.push("secretkey.json");
                    if std::fs::metadata(&dir).is_ok() {
                        bail!("Cannot overwrite key file {:?}", dir)
                    }
                    debug!("creating file {:?}", dir);
                    let mut f = open_secret_file(&dir)?;
                    let pass = rpassword::read_password_from_tty(Some(
                        "Password for the new key (press enter to leave it unencrypted): ",
                    ))?;
                    let pass = if pass.is_empty() {
                        None
                    } else {
                        Some(pass.as_ref())
                    };

                    let k = libpijul::key::SKey::generate(None);
                    serde_json::to_writer_pretty(&mut f, &k.save(pass))?;
                    f.write_all(b"\n")?;
                    let mut stderr = std::io::stderr();
                    writeln!(stderr, "Wrote secret key in {:?}", dir)?;
                    dir.pop();

                    dir.push("publickey.json");
                    debug!("creating file {:?}", dir);
                    let mut f = std::fs::File::create(&dir)?;
                    let pk = k.public_key();
                    serde_json::to_writer_pretty(&mut f, &pk)?;
                    f.write_all(b"\n")?;

                    dir.pop();
                    dir.push("identities");
                    std::fs::create_dir_all(&dir)?;
                    dir.push(&pk.key);
                    debug!("creating file {:?}", dir);
                    let mut f = std::fs::File::create(&dir)?;
                    serde_json::to_writer_pretty(
                        &mut f,
                        &super::Identity {
                            public_key: pk,
                            origin: String::new(),
                            login,
                            email,
                            name: None,
                            last_modified: std::time::SystemTime::now()
                                .duration_since(std::time::SystemTime::UNIX_EPOCH)
                                .unwrap()
                                .as_secs(),
                        },
                    )?;
                    f.write_all(b"\n")?;
                }
            }
            Some(SubCommand::Prove {
                remote,
                no_cert_check,
            }) => {
                let mut remote = if let Ok(repo) = Repository::find_root(None) {
                    use crate::remote::*;
                    if let RemoteRepo::Ssh(ssh) = repo
                        .remote(
                            None,
                            &remote,
                            crate::DEFAULT_CHANNEL,
                            Direction::Pull,
                            no_cert_check,
                            false,
                        )
                        .await?
                    {
                        ssh
                    } else {
                        bail!("No such remote: {}", remote)
                    }
                } else if let Some(mut ssh) = crate::remote::ssh::ssh_remote(&remote, false) {
                    if let Some(c) = ssh.connect(&remote, crate::DEFAULT_CHANNEL).await? {
                        c
                    } else {
                        bail!("No such remote: {}", remote)
                    }
                } else {
                    bail!("No such remote: {}", remote)
                };
                let (_, key) = super::load_key()?;
                remote.prove(key).await?;
            }

            None => {
                Self::command().write_long_help(&mut std::io::stdout())?;
            }
        }
        Ok(())
    }
}

#[cfg(unix)]
fn open_secret_file(path: &Path) -> Result<std::fs::File, std::io::Error> {
    use std::fs::OpenOptions;
    use std::os::unix::fs::OpenOptionsExt;
    OpenOptions::new()
        .write(true)
        .create(true)
        .mode(0o600)
        .open(path)
}

#[cfg(not(unix))]
fn open_secret_file(path: &Path) -> Result<std::fs::File, std::io::Error> {
    std::fs::File::create(path)
}