//! The `init` sub-command.

use std::{fs::create_dir_all, path::PathBuf};

use clap::Parser;
use log::{debug, info};

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

/// Create and initialize a new password database. Use an existing key
/// for this device and extract the corresponding certificate and
/// store it in the new password database.
#[derive(Debug, Parser)]
pub struct InitCommand {
    /// Name of certificate extracted from the specified key.
    #[clap(long)]
    name: String,

    /// File containing the key to use on this device. This implies
    /// using a SOP implementation that supports software keys only.
    #[clap(long)]
    key: Option<PathBuf>,

    /// File containing the certificate corresponding to the key to
    /// use on this device. This implies using a SOP implementation
    /// that supports OpenPGP cards for decryption.
    #[clap(long)]
    cert: Option<PathBuf>,
}

impl LeafCommand for InitCommand {
    type Error = InitError;

    fn run(&self, config: &Config) -> Result<(), InitError> {
        info!("init password database");
        debug!("{config:#?}");
        let db_dir = config.database();
        if !db_dir.exists() {
            println!("creating {}", db_dir.display());
            create_dir_all(db_dir).map_err(|err| InitError::CreateDir(db_dir.into(), err))?;
        }

        let mut db = Database::default();
        let encrypted = EncryptedDatabase::new(config.sop(), config.database());

        if let Some(key) = &self.key {
            encrypted.install_key(key)?;
            let cert = encrypted.extract_cert()?;
            db.push_cert(&self.name, &cert);
        } else if let Some(cert) = &self.cert {
            encrypted.install_cert(cert)?;
            let cert = Certificate::load(cert)?;
            db.push_cert(&self.name, &cert);
        } else {
            return Err(InitError::NoKey);
        }

        encrypted.write_database(&db)?;
        Ok(())
    }
}

/// Possible errors from the `init` sub-command.
#[derive(Debug, thiserror::Error)]
pub enum InitError {
    /// Can't create database directory.
    #[error("failed to create database directory {0}")]
    CreateDir(PathBuf, #[source] std::io::Error),

    /// Either --key or --cert must be used.
    #[error("neither --key or --cert has been specified")]
    NoKey,

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

    /// Error from dealing with the the `sop` module.
    #[error(transparent)]
    Sop(#[from] SopError),
}
