//! The `value` sub-commands.

use std::{io::Read, num::NonZeroUsize, path::PathBuf};

use clap::Parser;
use log::info;
use passwords::PasswordGenerator;

use crate::{
    db::{DatabaseError, EncryptedDatabase, EncryptedDatabaseError},
    Config, LeafCommand,
};

/// List names of values, one per line.
#[derive(Debug, Parser)]
pub struct ListValues {}

impl LeafCommand for ListValues {
    type Error = ValueError;

    fn run(&self, config: &Config) -> Result<(), ValueError> {
        let db = EncryptedDatabase::new(config.sop(), config.database()).read_database()?;
        info!("loaded database OK");
        let mut names: Vec<&str> = db.iter().map(|(k, _)| k).collect();
        names.sort();
        for name in names {
            println!("{name}");
        }
        Ok(())
    }
}

/// Add a new value.
///
/// The value can be given as a command line argument, read from the
/// standard input, or read from a named file.
#[derive(Debug, Parser)]
pub struct AddValue {
    /// Name of value.
    name: String,

    /// Read value from this file.
    #[clap(long)]
    file: Option<PathBuf>,

    /// Read value from the standard input.
    #[clap(long, default_value = "false", action = clap::ArgAction::SetTrue)]
    stdin: Option<bool>,
}

impl AddValue {
    fn value(&self) -> Result<Vec<u8>, ValueError> {
        match (&&self.file, self.stdin) {
            // Check that exactly one source of value is specified.
            (Some(_), Some(true)) => Err(ValueError::ThereCanBeOnlyOne),
            (None, None) => Err(ValueError::ThereMustBeOne),
            (None, Some(false)) => Err(ValueError::ThereMustBeOne),

            // File.
            (Some(filename), _) => {
                let value = std::fs::read(filename)
                    .map_err(|err| ValueError::ReadFile(filename.into(), err))?;
                Ok(value)
            }

            // Stdin.
            (_, Some(true)) => {
                let mut stdin = std::io::stdin();
                let mut value = vec![];
                stdin
                    .read_to_end(&mut value)
                    .map_err(ValueError::ReadStdin)?;
                Ok(value)
            }
        }
    }
}

impl LeafCommand for AddValue {
    type Error = ValueError;

    fn run(&self, config: &Config) -> Result<(), ValueError> {
        info!("AddValue self={self:#?}");
        let encrypted = EncryptedDatabase::new(config.sop(), config.database());
        let mut db = encrypted.read_database()?;
        db.insert(&self.name, &self.value()?);
        encrypted.write_database(&db)?;

        Ok(())
    }
}

/// Generate a new random value.
#[derive(Debug, Parser)]
pub struct GenerateValue {
    /// Name of value. This is optional if `--stdout` is not used.
    #[clap(required_unless_present = "stdout")]
    name: Option<String>,

    /// Output generated value to stdout rather then storing it.
    #[clap(long)]
    stdout: bool,

    /// Generate a secret of this many characters.
    #[clap(long, short = 'n', default_value = "8")]
    length: NonZeroUsize,
}

impl LeafCommand for GenerateValue {
    type Error = ValueError;

    fn run(&self, config: &Config) -> Result<(), ValueError> {
        info!("GenerateValue self={self:#?}");
        let gen = PasswordGenerator::new()
            .length(self.length.into())
            .strict(false)
            .exclude_similar_characters(true)
            .numbers(true)
            .lowercase_letters(true)
            .uppercase_letters(true)
            .spaces(false)
            .symbols(true);
        let value = gen.generate_one().map_err(ValueError::Generate)?;
        if let Some(name) = &self.name {
            let encrypted = EncryptedDatabase::new(config.sop(), config.database());
            let mut db = encrypted.read_database()?;
            db.insert(name, value.as_bytes());
            encrypted.write_database(&db)?;
        } else {
            println!("{value}");
        }

        Ok(())
    }
}

/// Show a value.
#[derive(Debug, Parser)]
pub struct ShowValue {
    /// Name of value.
    name: String,
}

impl LeafCommand for ShowValue {
    type Error = ValueError;

    fn run(&self, config: &Config) -> Result<(), ValueError> {
        let encrypted = EncryptedDatabase::new(config.sop(), config.database());
        let db = encrypted.read_database()?;
        info!("get value for {}", self.name);
        if let Some(value) = db.get(&self.name) {
            println!("{}", String::from_utf8_lossy(value));
        } else {
            return Err(ValueError::NoSuchValue(self.name.clone()));
        }

        Ok(())
    }
}

/// Remove a value.
#[derive(Debug, Parser)]
pub struct RemoveValue {
    /// Name of value.
    name: String,
}

impl LeafCommand for RemoveValue {
    type Error = ValueError;

    fn run(&self, config: &Config) -> Result<(), ValueError> {
        info!("remove value for {}", self.name);
        let encrypted = EncryptedDatabase::new(config.sop(), config.database());
        let mut db = encrypted.read_database()?;
        db.remove(&self.name);
        encrypted.write_database(&db)?;

        Ok(())
    }
}

/// Rename a value.
#[derive(Debug, Parser)]
pub struct RenameValue {
    /// Old name of value.
    old: String,

    /// New name of value.
    new: String,
}

impl LeafCommand for RenameValue {
    type Error = ValueError;

    fn run(&self, config: &Config) -> Result<(), ValueError> {
        info!("rename {} to {}", self.old, self.new);
        let encrypted = EncryptedDatabase::new(config.sop(), config.database());
        let mut db = encrypted.read_database()?;
        info!("rename in memory");
        db.rename(&self.old, &self.new)?;
        info!("rename in memory OK");
        encrypted.write_database(&db)?;

        Ok(())
    }
}

/// Possible errors from `value` sub-commands.
#[derive(Debug, thiserror::Error)]
pub enum ValueError {
    /// Value does not exist.
    #[error("there is not value named {0}")]
    NoSuchValue(String),

    /// Error from handling encrypted database.
    #[error(transparent)]
    Encrypted(#[from] EncryptedDatabaseError),

    /// Error from using cleartext database.
    #[error(transparent)]
    Database(#[from] DatabaseError),

    /// Confused about adding value.
    #[error("conflict: more than one way used to specify value to be added")]
    ThereCanBeOnlyOne,

    /// Confused about not adding value.
    #[error("no value to add specified")]
    ThereMustBeOne,

    /// Can't read value from file.
    #[error("failed to read value from file {0}")]
    ReadFile(PathBuf, #[source] std::io::Error),

    /// Can't read value from stdin.
    #[error("failed to read value from stdin")]
    ReadStdin(#[source] std::io::Error),

    /// Can't generate a random value.
    #[error("failed to generate a random value: {0}")]
    Generate(&'static str),
}
