use std::path::Path;

use super::{Args, MainError};

use obnam::{cipher::Key, config::Config, repository::BackupRepository};

pub mod chunk;
pub mod client;
pub mod credential;
pub mod remote;
pub mod repo;
pub mod sop;

/// A trait for implementing subcommands that have no sub-subcommands.
///
/// This is meant to make all the leaf subcommands have a consistent
/// API, which in turn makes it easier to write the code that calls
/// them.
pub trait Leaf {
    /// The error type for this leaf command.
    type Error;

    /// Execute the leaf command.
    fn run(&self, args: &Args, config: &Config) -> Result<(), Self::Error>;

    /// We need to have been given a repository.
    fn need_repository(config: &Config) -> Result<&Path, MainError> {
        config.repository().ok_or(MainError::NoRepository)
    }

    /// Open given repository.
    fn open_repository(config: &Config) -> Result<BackupRepository, MainError> {
        let dir = Self::need_repository(config)?;
        BackupRepository::open(dir).map_err(MainError::OpenRepository)
    }

    /// Get client key without accessing the backup repository.
    fn client_key(args: &Args) -> Result<Key, MainError> {
        if let Some(client_key) = &args.client_key {
            // We were given the client key to use.
            Ok(Key::from(client_key))
        } else {
            Err(MainError::NeedClientKey)
        }
    }

    /// Get client key from backup repository, if necessary.
    fn client_key_from_repo(
        args: &Args,
        config: &Config,
        repo: &BackupRepository,
    ) -> Result<Key, MainError> {
        match Self::client_key(args) {
            Ok(key) => Ok(key),
            Err(err) => {
                let methods = config.credential_methods();
                let credentials = repo
                    .find_credential_chunks()
                    .map_err(MainError::FindCredential)?;
                for meta in credentials.iter() {
                    let credential = repo
                        .get_credential_chunk(meta.id())
                        .map_err(|err| MainError::GetCredential(meta.id().clone(), err))?;
                    for method in methods.iter() {
                        if let Ok(client_key) = credential.decrypt(method) {
                            return Ok(client_key);
                        }
                    }
                }

                Err(err)
            }
        }
    }
}
