//! Manage certificates in a database.

use std::path::PathBuf;

use clap::Parser;
use log::info;

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

/// List all certificate names, one per line.
#[derive(Debug, Parser)]
pub struct ListCerts {}

impl LeafCommand for ListCerts {
    type Error = CertError;

    fn run(&self, config: &Config) -> Result<(), CertError> {
        let db = EncryptedDatabase::new(config.sop(), config.database()).read_database()?;
        info!("loaded database OK");
        for name in db.cert_names() {
            println!("{name}");
        }
        Ok(())
    }
}

/// Add a certificate to the database.
#[derive(Debug, Parser)]
pub struct AddCert {
    /// Name of certificate.
    #[clap(long)]
    name: String,

    /// File where certificate is databased.
    #[clap(long)]
    cert: PathBuf,
}

impl LeafCommand for AddCert {
    type Error = CertError;

    fn run(&self, config: &Config) -> Result<(), CertError> {
        let encrypted = EncryptedDatabase::new(config.sop(), config.database());
        let mut db = encrypted.read_database()?;
        let cert =
            std::fs::read(&self.cert).map_err(|err| CertError::ReadCert(self.cert.clone(), err))?;
        let cert = Certificate::new(cert);
        db.push_cert(&self.name, &cert);
        encrypted.write_database(&db)?;

        Ok(())
    }
}

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

impl LeafCommand for ShowCert {
    type Error = CertError;

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

        Ok(())
    }
}

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

impl LeafCommand for RemoveCert {
    type Error = CertError;

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

        Ok(())
    }
}

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

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

impl LeafCommand for RenameCert {
    type Error = CertError;

    fn run(&self, config: &Config) -> Result<(), CertError> {
        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 for managing certificates in a database.
#[derive(Debug, thiserror::Error)]
pub enum CertError {
    /// Can't read certificate.
    #[error("failed to read certificate from file {0}")]
    ReadCert(PathBuf, #[source] std::io::Error),

    /// No such certificate.
    #[error("there is not certificate named {0}")]
    NoSuchCert(String),

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

    /// Error handling cleartext database.
    #[error(transparent)]
    Store(#[from] DatabaseError),
}
