//! BIP-39 mnemonic support for Synor wallet key generation.
//!
//! Provides secure mnemonic phrase generation and seed derivation.

// The `inner` field warning is a false positive from the ZeroizeOnDrop derive
#![allow(unused_assignments)]

use bip39::{Language, Mnemonic as Bip39Mnemonic, MnemonicType, Seed};
use thiserror::Error;
use zeroize::ZeroizeOnDrop;

/// A BIP-39 mnemonic phrase for key derivation.
#[derive(Clone, ZeroizeOnDrop)]
pub struct Mnemonic {
    #[zeroize(skip)]
    inner: Bip39Mnemonic,
}

impl Mnemonic {
    /// Generates a new random mnemonic with the specified word count.
    ///
    /// Supported word counts: 12, 15, 18, 21, 24
    pub fn generate(word_count: usize) -> Result<Self, MnemonicError> {
        let mnemonic_type = match word_count {
            12 => MnemonicType::Words12,
            15 => MnemonicType::Words15,
            18 => MnemonicType::Words18,
            21 => MnemonicType::Words21,
            24 => MnemonicType::Words24,
            _ => return Err(MnemonicError::InvalidWordCount(word_count)),
        };
        let inner = Bip39Mnemonic::new(mnemonic_type, Language::English);
        Ok(Mnemonic { inner })
    }

    /// Creates a mnemonic from a phrase string.
    pub fn from_phrase(phrase: &str) -> Result<Self, MnemonicError> {
        let inner = Bip39Mnemonic::from_phrase(phrase, Language::English)
            .map_err(|e| MnemonicError::InvalidPhrase(format!("{}", e)))?;
        Ok(Mnemonic { inner })
    }

    /// Returns the mnemonic phrase as a string.
    pub fn phrase(&self) -> &str {
        self.inner.phrase()
    }

    /// Returns the mnemonic words as a vector.
    pub fn words(&self) -> Vec<&str> {
        self.inner.phrase().split_whitespace().collect()
    }

    /// Derives a 64-byte seed from the mnemonic with an optional passphrase.
    pub fn to_seed(&self, passphrase: &str) -> [u8; 64] {
        let seed = Seed::new(&self.inner, passphrase);
        let mut result = [0u8; 64];
        result.copy_from_slice(seed.as_bytes());
        result
    }

    /// Returns the entropy bytes.
    pub fn entropy(&self) -> &[u8] {
        self.inner.entropy()
    }

    /// Creates a mnemonic from entropy bytes.
    pub fn from_entropy(entropy: &[u8]) -> Result<Self, MnemonicError> {
        let inner = Bip39Mnemonic::from_entropy(entropy, Language::English)
            .map_err(|e| MnemonicError::InvalidEntropy(format!("{}", e)))?;
        Ok(Mnemonic { inner })
    }

    /// Validates that a phrase is a valid BIP-39 mnemonic.
    pub fn validate(phrase: &str) -> bool {
        Bip39Mnemonic::validate(phrase, Language::English).is_ok()
    }

    /// Returns the word count.
    pub fn word_count(&self) -> usize {
        self.inner.phrase().split_whitespace().count()
    }
}

impl std::fmt::Debug for Mnemonic {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("Mnemonic")
            .field("word_count", &self.word_count())
            .finish_non_exhaustive()
    }
}

impl std::fmt::Display for Mnemonic {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        // Only show first and last word for security
        let words: Vec<&str> = self.words();
        if words.len() >= 2 {
            write!(
                f,
                "{} ... {} ({} words)",
                words[0],
                words[words.len() - 1],
                words.len()
            )
        } else {
            write!(f, "[invalid mnemonic]")
        }
    }
}

/// Errors related to mnemonics.
#[derive(Debug, Error)]
pub enum MnemonicError {
    #[error("Invalid word count: {0}. Must be 12, 15, 18, 21, or 24")]
    InvalidWordCount(usize),

    #[error("Invalid mnemonic phrase: {0}")]
    InvalidPhrase(String),

    #[error("Invalid entropy: {0}")]
    InvalidEntropy(String),

    #[error("Checksum validation failed")]
    ChecksumFailed,
}

/// Suggests completions for a partial word from the BIP-39 wordlist.
pub fn suggest_word(partial: &str) -> Vec<&'static str> {
    let wl = Language::English.wordlist();
    wl.get_words_by_prefix(partial)
        .iter()
        .take(10)
        .copied()
        .collect()
}

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

    #[test]
    fn test_generate_mnemonic() {
        let mnemonic = Mnemonic::generate(12).unwrap();
        assert_eq!(mnemonic.word_count(), 12);

        let mnemonic = Mnemonic::generate(24).unwrap();
        assert_eq!(mnemonic.word_count(), 24);
    }

    #[test]
    fn test_invalid_word_count() {
        assert!(Mnemonic::generate(11).is_err());
        assert!(Mnemonic::generate(25).is_err());
    }

    #[test]
    fn test_from_phrase() {
        let phrase = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
        let mnemonic = Mnemonic::from_phrase(phrase).unwrap();
        assert_eq!(mnemonic.phrase(), phrase);
    }

    #[test]
    fn test_seed_derivation() {
        let phrase = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
        let mnemonic = Mnemonic::from_phrase(phrase).unwrap();

        let seed1 = mnemonic.to_seed("");
        let seed2 = mnemonic.to_seed("password");

        // Same mnemonic with different passphrases should produce different seeds
        assert_ne!(seed1, seed2);

        // Same mnemonic and passphrase should produce same seed
        let seed3 = mnemonic.to_seed("");
        assert_eq!(seed1, seed3);
    }

    #[test]
    fn test_validate_phrase() {
        assert!(Mnemonic::validate(
            "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
        ));
        assert!(!Mnemonic::validate("invalid phrase here"));
    }

    #[test]
    fn test_suggest_word() {
        let suggestions = suggest_word("aban");
        assert!(suggestions.contains(&"abandon"));
    }

    #[test]
    fn test_entropy_roundtrip() {
        let mnemonic = Mnemonic::generate(12).unwrap();
        let entropy = mnemonic.entropy().to_vec();

        let recovered = Mnemonic::from_entropy(&entropy).unwrap();
        assert_eq!(mnemonic.phrase(), recovered.phrase());
    }
}
