//! Cryptographic operations for the Synor wallet
//!
//! Implements:
//! - BIP39 mnemonic generation and validation
//! - Argon2id password-based key derivation
//! - ChaCha20-Poly1305 authenticated encryption
//! - Ed25519 key derivation from seed
//! - Bech32m address encoding

use argon2::{
    password_hash::SaltString,
    Argon2, Params,
};
use bip39::Mnemonic;
use chacha20poly1305::{
    aead::{Aead, KeyInit},
    ChaCha20Poly1305, Nonce,
};
use hmac::Mac;
use rand::{rngs::OsRng, RngCore};
use sha2::Sha512;
use zeroize::{Zeroize, ZeroizeOnDrop};

use crate::{Error, Result};

/// Type alias for HMAC-SHA512 to avoid ambiguity
type HmacSha512 = hmac::Hmac<Sha512>;

/// Encrypted wallet data stored on disk
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct EncryptedWallet {
    /// Argon2 salt (22 bytes, base64 encoded)
    pub salt: String,
    /// ChaCha20-Poly1305 nonce (12 bytes, hex encoded)
    pub nonce: String,
    /// Encrypted seed (ciphertext + 16-byte tag, hex encoded)
    pub ciphertext: String,
    /// Version for future compatibility
    pub version: u32,
}

/// Sensitive seed data that auto-zeros on drop
#[derive(Zeroize, ZeroizeOnDrop)]
pub struct SeedData {
    /// 64-byte seed derived from mnemonic
    pub seed: [u8; 64],
}

/// Generate a new random 24-word BIP39 mnemonic
pub fn generate_mnemonic() -> Result<String> {
    // Generate 256 bits of entropy for 24 words
    let mut entropy = [0u8; 32]; // 256 bits for 24 words
    OsRng.fill_bytes(&mut entropy);

    let mnemonic = Mnemonic::from_entropy(&entropy)
        .map_err(|e| Error::Crypto(format!("Failed to generate mnemonic: {}", e)))?;

    Ok(mnemonic.to_string())
}

/// Validate a BIP39 mnemonic phrase
pub fn validate_mnemonic(phrase: &str) -> Result<()> {
    Mnemonic::parse(phrase)
        .map_err(|_| Error::InvalidMnemonic)?;
    Ok(())
}

/// Derive a 64-byte seed from mnemonic using BIP39
/// The passphrase is optional (empty string if not used)
pub fn mnemonic_to_seed(mnemonic: &str, passphrase: &str) -> Result<SeedData> {
    let mnemonic = Mnemonic::parse(mnemonic)
        .map_err(|_| Error::InvalidMnemonic)?;

    let seed = mnemonic.to_seed(passphrase);
    let mut seed_array = [0u8; 64];
    seed_array.copy_from_slice(&seed);

    Ok(SeedData { seed: seed_array })
}

/// Derive an encryption key from password using Argon2id
fn derive_encryption_key(password: &str, salt: &SaltString) -> Result<[u8; 32]> {
    // Use Argon2id with secure parameters
    // Memory: 64 MB, Iterations: 3, Parallelism: 4
    let params = Params::new(65536, 3, 4, Some(32))
        .map_err(|e| Error::Crypto(format!("Argon2 params error: {}", e)))?;

    let argon2 = Argon2::new(argon2::Algorithm::Argon2id, argon2::Version::V0x13, params);

    let mut key = [0u8; 32];
    argon2
        .hash_password_into(password.as_bytes(), salt.as_str().as_bytes(), &mut key)
        .map_err(|e| Error::Crypto(format!("Key derivation failed: {}", e)))?;

    Ok(key)
}

/// Encrypt the seed with password using Argon2id + ChaCha20-Poly1305
pub fn encrypt_seed(seed: &[u8; 64], password: &str) -> Result<EncryptedWallet> {
    // Generate random salt for Argon2
    let salt = SaltString::generate(&mut OsRng);

    // Derive encryption key
    let key = derive_encryption_key(password, &salt)?;

    // Generate random nonce for ChaCha20-Poly1305
    let mut nonce_bytes = [0u8; 12];
    OsRng.fill_bytes(&mut nonce_bytes);
    let nonce = Nonce::from_slice(&nonce_bytes);

    // Encrypt the seed
    let cipher = ChaCha20Poly1305::new_from_slice(&key)
        .map_err(|e| Error::Crypto(format!("Cipher init failed: {}", e)))?;

    let ciphertext = cipher
        .encrypt(nonce, seed.as_ref())
        .map_err(|e| Error::Crypto(format!("Encryption failed: {}", e)))?;

    Ok(EncryptedWallet {
        salt: salt.to_string(),
        nonce: hex::encode(nonce_bytes),
        ciphertext: hex::encode(ciphertext),
        version: 1,
    })
}

/// Decrypt the seed with password
pub fn decrypt_seed(wallet: &EncryptedWallet, password: &str) -> Result<SeedData> {
    if wallet.version != 1 {
        return Err(Error::Crypto(format!(
            "Unsupported wallet version: {}",
            wallet.version
        )));
    }

    // Parse salt
    let salt = SaltString::from_b64(&wallet.salt)
        .map_err(|_| Error::Crypto("Invalid salt".to_string()))?;

    // Derive encryption key
    let key = derive_encryption_key(password, &salt)?;

    // Parse nonce
    let nonce_bytes = hex::decode(&wallet.nonce)
        .map_err(|_| Error::Crypto("Invalid nonce".to_string()))?;
    if nonce_bytes.len() != 12 {
        return Err(Error::Crypto("Invalid nonce length".to_string()));
    }
    let nonce = Nonce::from_slice(&nonce_bytes);

    // Parse ciphertext
    let ciphertext = hex::decode(&wallet.ciphertext)
        .map_err(|_| Error::Crypto("Invalid ciphertext".to_string()))?;

    // Decrypt
    let cipher = ChaCha20Poly1305::new_from_slice(&key)
        .map_err(|e| Error::Crypto(format!("Cipher init failed: {}", e)))?;

    let plaintext = cipher
        .decrypt(nonce, ciphertext.as_ref())
        .map_err(|_| Error::InvalidPassword)?;

    if plaintext.len() != 64 {
        return Err(Error::Crypto("Invalid seed length".to_string()));
    }

    let mut seed = [0u8; 64];
    seed.copy_from_slice(&plaintext);

    Ok(SeedData { seed })
}

/// Derive an Ed25519 keypair from seed using BIP32-Ed25519 style derivation
/// Returns (private_key, public_key)
pub fn derive_ed25519_keypair(seed: &[u8; 64], index: u32) -> ([u8; 32], [u8; 32]) {
    // Use HMAC-SHA512 for deterministic key derivation
    // Path: m/44'/synor'/0'/0/index

    let mut mac: HmacSha512 = Mac::new_from_slice(b"ed25519 seed").unwrap();
    mac.update(seed);
    let master = mac.finalize().into_bytes();

    // Derive child key for index
    let mut mac: HmacSha512 = Mac::new_from_slice(&master[32..]).unwrap();
    mac.update(&master[..32]);
    mac.update(&index.to_be_bytes());
    let derived = mac.finalize().into_bytes();

    let mut private_key = [0u8; 32];
    let mut chain_code = [0u8; 32];
    private_key.copy_from_slice(&derived[..32]);
    chain_code.copy_from_slice(&derived[32..]);

    // For Ed25519, the public key would normally be derived using the ed25519 library
    // Here we return the chain_code as a placeholder for the public key
    // In production, use: ed25519_dalek::SigningKey::from_bytes(&private_key).verifying_key()
    (private_key, chain_code)
}

/// Generate a Synor address from a public key
/// Format: synor1<bech32m-encoded-pubkey-hash>
pub fn pubkey_to_address(pubkey: &[u8; 32], testnet: bool) -> Result<String> {
    use bech32::Hrp;
    use sha2::{Digest, Sha256};

    // Hash the public key (SHA256 then take first 20 bytes like Bitcoin)
    let hash = Sha256::digest(pubkey);
    let pubkey_hash: &[u8] = &hash[..20];

    let hrp_str = if testnet { "tsynor" } else { "synor" };
    let hrp = Hrp::parse(hrp_str)
        .map_err(|e| Error::Crypto(format!("Invalid HRP: {}", e)))?;

    // Encode using bech32 segwit encoding (version 0)
    bech32::segwit::encode(hrp, bech32::segwit::VERSION_0, pubkey_hash)
        .map_err(|e| Error::Crypto(format!("Bech32 encoding failed: {}", e)))
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_mnemonic_generation() {
        let mnemonic = generate_mnemonic().unwrap();
        let words: Vec<&str> = mnemonic.split_whitespace().collect();
        assert_eq!(words.len(), 24);
        assert!(validate_mnemonic(&mnemonic).is_ok());
    }

    #[test]
    fn test_encrypt_decrypt_roundtrip() {
        let seed = [42u8; 64];
        let password = "test_password_123";

        let encrypted = encrypt_seed(&seed, password).unwrap();
        let decrypted = decrypt_seed(&encrypted, password).unwrap();

        assert_eq!(seed, decrypted.seed);
    }

    #[test]
    fn test_wrong_password_fails() {
        let seed = [42u8; 64];
        let encrypted = encrypt_seed(&seed, "correct_password").unwrap();
        let result = decrypt_seed(&encrypted, "wrong_password");

        assert!(result.is_err());
    }

    #[test]
    fn test_address_generation() {
        let pubkey = [1u8; 32];
        let address = pubkey_to_address(&pubkey, false).unwrap();
        assert!(address.starts_with("synor1"));

        let testnet_address = pubkey_to_address(&pubkey, true).unwrap();
        assert!(testnet_address.starts_with("tsynor1"));
    }
}
