use std::{collections::HashSet, fs, path::PathBuf};

use chrono::Duration;
use ed25519_dalek::SigningKey;
use rand::RngExt;

use crate::{
    agent::{age_crypto, backend::AgentBackend, meta::MetadataStore},
    audit::AuditLog,
    error::{GlovesError, Result},
    fs_secure::{create_private_file_if_missing, ensure_private_dir, write_private_file_atomic},
    human::{backend::HumanBackend, pending::PendingRequestStore},
    manager::{SecretsManager, SetSecretOptions},
    paths::SecretsPaths,
    types::{AgentId, Owner, SecretId, SecretValue},
};

pub(crate) fn init_layout(paths: &SecretsPaths) -> Result<()> {
    ensure_private_dir(paths.root())?;
    ensure_private_dir(&paths.store_dir())?;
    ensure_private_dir(&paths.metadata_dir())?;
    ensure_private_dir(&paths.vaults_dir())?;
    ensure_private_dir(&paths.gpg_homes_dir())?;
    ensure_private_dir(&paths.encrypted_dir())?;
    ensure_private_dir(&paths.mounts_dir())?;
    create_private_file_if_missing(&paths.pending_file(), b"[]")?;
    create_private_file_if_missing(&paths.audit_file(), b"")?;
    create_private_file_if_missing(&paths.vault_sessions_file(), b"[]")?;
    Ok(())
}

pub(crate) fn manager_for_paths(paths: &SecretsPaths) -> Result<SecretsManager> {
    init_layout(paths)?;
    let agent_backend = AgentBackend::new(paths.store_dir())?;
    let human_backend = HumanBackend::new();
    let metadata_store = MetadataStore::new(paths.metadata_dir())?;
    let pending_store = PendingRequestStore::new(paths.pending_file())?;
    let audit_log = AuditLog::new(paths.audit_file())?;
    Ok(SecretsManager::new(
        agent_backend,
        human_backend,
        metadata_store,
        pending_store,
        audit_log,
    ))
}

pub(crate) fn load_or_create_default_identity(paths: &SecretsPaths) -> Result<PathBuf> {
    let path = paths.default_identity_file();
    if path.exists() {
        age_crypto::validate_identity_file(&path)?;
        return Ok(path);
    }

    age_crypto::generate_identity_file(&path)?;
    Ok(path)
}

pub(crate) fn recipient_from_identity_file(identity_file: &std::path::Path) -> Result<String> {
    age_crypto::recipient_from_identity_file(identity_file)
}

pub(crate) fn load_or_create_default_recipient(paths: &SecretsPaths) -> Result<String> {
    let identity_file = load_or_create_default_identity(paths)?;
    recipient_from_identity_file(&identity_file)
}

pub(crate) fn load_or_create_default_signing_key(paths: &SecretsPaths) -> Result<SigningKey> {
    let path = paths.default_signing_key_file();
    if path.exists() {
        let bytes = fs::read(&path)?;
        let key_bytes: [u8; 32] = bytes
            .as_slice()
            .try_into()
            .map_err(|_| GlovesError::InvalidInput("invalid signing key".to_owned()))?;
        return Ok(SigningKey::from_bytes(&key_bytes));
    }

    let mut key_bytes = [0_u8; 32];
    rand::rng().fill(&mut key_bytes);
    let key = SigningKey::from_bytes(&key_bytes);
    write_private_file_atomic(&path, &key.to_bytes())?;
    Ok(key)
}

pub(crate) fn validate_ttl_days(ttl_days: i64, field_name: &str) -> Result<i64> {
    if ttl_days <= 0 {
        return Err(GlovesError::InvalidInput(format!(
            "{field_name} must be greater than zero"
        )));
    }
    Ok(ttl_days)
}

pub(crate) fn parse_request_uuid(request_id: &str) -> Result<uuid::Uuid> {
    request_id
        .parse::<uuid::Uuid>()
        .map_err(|error| GlovesError::InvalidInput(error.to_string()))
}

pub(crate) fn ensure_agent_vault_secret(
    paths: &SecretsPaths,
    secret_name: &str,
    creator: &AgentId,
    ttl_days: i64,
    length_bytes: usize,
) -> Result<()> {
    let manager = manager_for_paths(paths)?;
    let secret_id = SecretId::new(secret_name)?;
    if manager.metadata_store.path_for(&secret_id).exists() {
        return Ok(());
    }

    let recipient = load_or_create_default_recipient(paths)?;
    let mut recipients = HashSet::new();
    recipients.insert(creator.clone());

    let mut secret_bytes = vec![0_u8; length_bytes];
    rand::rng().fill(secret_bytes.as_mut_slice());
    match manager.set(
        secret_id,
        SecretValue::new(secret_bytes),
        SetSecretOptions {
            owner: Owner::Agent,
            ttl: Duration::days(ttl_days),
            created_by: creator.clone(),
            recipients,
            recipient_keys: vec![recipient],
        },
    ) {
        Ok(_) | Err(GlovesError::AlreadyExists) => Ok(()),
        Err(error) => Err(error),
    }
}
