// Copyright (c) Subzero Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

//! Mnemonic-related functionality for wallet generation and key derivation.
//!
//! This module provides tools for generating BIP39 mnemonic phrases, deriving
//! Ed25519 keypairs from mnemonics using BIP32/BIP44 derivation paths, and
//! creating child keypairs for hierarchical deterministic wallets.

use bip32::{ChildNumber, DerivationPath};
use bip39::{Language, Mnemonic, MnemonicType, Seed};
use ed25519_dalek::SigningKey as Keypair;
use slip10_ed25519::derive_ed25519_private_key;

use crate::{
    constants::{BASE_DERIVATION_PATH, DERIVATION_PATH_COIN_TYPE, DERIVATION_PATH_PURPOSE_ED25519},
    error::{Result, RialoError},
};

/// Generates a new 12-word BIP39 mnemonic phrase.
///
/// Creates a cryptographically secure mnemonic phrase that can be used
/// as the foundation for a hierarchical deterministic wallet.
///
/// # Returns
///
/// Returns a `Result` containing the 12-word mnemonic phrase as a `String`
/// on success.
///
/// # Examples
///
/// ```
/// use rialo_cdk::wallet::mnemonic;
///
/// let mnemonic_phrase = mnemonic::generate_mnemonic().unwrap();
/// println!("Generated mnemonic: {}", mnemonic_phrase);
/// ```
#[cfg(feature = "mnemonic")]
pub fn generate_mnemonic() -> Result<String> {
    let mnemonic = Mnemonic::new(MnemonicType::Words12, Language::English);
    Ok(mnemonic.phrase().to_string())
}

/// Validates a BIP39 mnemonic phrase.
///
/// Returns `true` if the phrase is a valid BIP39 mnemonic (correct word count,
/// valid words, correct checksum), `false` otherwise.
#[cfg(feature = "mnemonic")]
pub fn validate_mnemonic(phrase: &str) -> bool {
    Mnemonic::from_phrase(phrase, Language::English).is_ok()
}

/// Validates derivation path for Ed25519 (SLIP-0010)
fn validate_path(path: Option<DerivationPath>) -> Result<DerivationPath> {
    match path {
        Some(p) => {
            // Expect: m/{purpose}'/{coin_type}'/{account}'/0'
            if let &[purpose, coin_type, account, change] = p.as_ref() {
                if Some(purpose) == ChildNumber::new(DERIVATION_PATH_PURPOSE_ED25519, true).ok()
                    && Some(coin_type) == ChildNumber::new(DERIVATION_PATH_COIN_TYPE, true).ok()
                    && account.is_hardened()
                    && change.is_hardened()
                {
                    Ok(p)
                } else {
                    Err(RialoError::Keyring(format!(
                        "Invalid path: must be m/{}'/{}'/<account>'/0' with all hardened",
                        DERIVATION_PATH_PURPOSE_ED25519, DERIVATION_PATH_COIN_TYPE
                    )))
                }
            } else {
                Err(RialoError::Keyring(
                    "Invalid path: expected 4 levels".to_string(),
                ))
            }
        }
        None => {
            // Default: m/{purpose}'/{coin_type}'/0'/0'
            format!("m/{DERIVATION_PATH_PURPOSE_ED25519}'/{DERIVATION_PATH_COIN_TYPE}'/0'/0'")
                .parse()
                .map_err(|_| RialoError::Keyring("Cannot parse default path".to_string()))
        }
    }
}

/// Derives an Ed25519 keypair from a BIP39 mnemonic phrase using SLIP-0010.
///
/// This function implements hierarchical deterministic (HD) wallet derivation
/// following BIP32/BIP44 standards adapted for Ed25519 via SLIP-0010. It derives
/// a keypair at the path: `m/44'/501'/{account_index}'/0'`
///
/// # Arguments
///
/// * `mnemonic` - A valid BIP39 mnemonic phrase (12, 15, 18, 21, or 24 words)
/// * `passphrase` - Optional passphrase for additional security (BIP39 extension)
/// * `account_index` - The account index in the derivation path (hardened)
///
/// # Derivation Path
///
/// The function uses the Rialo derivation path:
/// - `m/44'/756'/{account_index}'/0'`
///   - `44'` - BIP44 purpose (hardened)
///   - `756'` - Rialo coin type (hardened)
///   - `{account_index}'` - Account number (hardened)
///   - `0'` - Change address (hardened)
///
/// All path components are hardened (marked with `'`) as required by SLIP-0010
/// for Ed25519 derivation.
///
/// # Returns
///
/// Returns a `Result` containing the derived Ed25519 `Keypair` on success.
///
/// # Errors
///
/// Returns `RialoError::Keyring` if:
/// - The mnemonic phrase is invalid or malformed
/// - The derivation path cannot be constructed
/// - Key derivation fails
///
/// # Examples
///
/// ```
/// use rialo_cdk::wallet::mnemonic;
///
/// // Derive the first account (index 0)
/// let mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
/// let keypair = mnemonic::derive_keypair(mnemonic, None, 0).unwrap();
///
/// // Derive with passphrase for additional security
/// let keypair_protected = mnemonic::derive_keypair(
///     mnemonic,
///     Some("my secure passphrase"),
///     0
/// ).unwrap();
///
/// // Derive multiple accounts from same mnemonic
/// let account_0 = mnemonic::derive_keypair(mnemonic, None, 0).unwrap();
/// let account_1 = mnemonic::derive_keypair(mnemonic, None, 1).unwrap();
/// let account_2 = mnemonic::derive_keypair(mnemonic, None, 2).unwrap();
/// ```
///
/// # Security Considerations
///
/// - **Never share or expose your mnemonic phrase** - it provides complete access to all derived keys
/// - Store mnemonics securely offline (paper wallet, hardware wallet, secure vault)
/// - Consider using a passphrase for additional security (BIP39 extension)
/// - The same mnemonic + passphrase + account_index always produces the same keypair
/// - Different account indices produce cryptographically independent keypairs
///
/// # Compatibility
///
/// This implementation is compatible with:
/// - Solana CLI wallet derivation
/// - Phantom, Solflare, and other Solana wallets
/// - Any wallet following BIP44 with Solana coin type 501
#[cfg(all(feature = "mnemonic", feature = "hd-wallet"))]
pub fn derive_keypair(
    mnemonic: &str,
    passphrase: Option<&str>,
    account_index: u32,
) -> Result<Keypair> {
    // Parse mnemonic
    let mnemonic = Mnemonic::from_phrase(mnemonic, Language::English)
        .map_err(|e| RialoError::Keyring(format!("Invalid mnemonic: {e}")))?;

    // Generate seed
    let passphrase = passphrase.unwrap_or("");
    let seed = Seed::new(&mnemonic, passphrase);

    // Build path: m/{purpose}'/{coin_type}'/{account}'/0'
    let path_str = format!("{}{}'/0'", BASE_DERIVATION_PATH, account_index);
    let path: DerivationPath = path_str
        .parse()
        .map_err(|_| RialoError::Keyring("Cannot parse path".to_string()))?;

    let validated_path = validate_path(Some(path))?;

    // Convert path to indexes
    // For SLIP-0010, hardened indexes need 0x80000000 bit set
    let indexes: Vec<u32> = validated_path
        .into_iter()
        .map(|child| {
            let index: u32 = child.into();
            if child.is_hardened() {
                index | 0x80000000
            } else {
                index
            }
        })
        .collect();

    let derived = derive_ed25519_private_key(seed.as_bytes(), &indexes);

    let keypair = Keypair::from_bytes(&derived);
    Ok(keypair)
}

/// Derives an Ed25519 keypair from a mnemonic using a custom derivation path.
///
/// This function provides fine-grained control over the derivation path, allowing
/// you to specify a custom BIP32 path string. The path must follow SLIP-0010
/// requirements for Ed25519: all components must be hardened.
///
/// # Arguments
///
/// * `mnemonic` - A valid BIP39 mnemonic phrase
/// * `passphrase` - Optional passphrase for additional security
/// * `path` - Custom derivation path string (e.g., `"m/44'/501'/0'/0'"`)
///
/// # Path Requirements
///
/// The derivation path must:
/// - Start with `m/` (master key)
/// - Have exactly 4 levels: `m/{purpose}'/{coin_type}'/{account}'/0'`
/// - Use `44'` for purpose (BIP44 standard)
/// - Use `501'` for coin type (Solana)
/// - Have all components hardened (marked with `'`)
///
/// # Returns
///
/// Returns a `Result` containing the derived Ed25519 `Keypair` on success.
///
/// # Errors
///
/// Returns `RialoError::Keyring` if:
/// - The mnemonic phrase is invalid
/// - The path string cannot be parsed
/// - The path doesn't meet SLIP-0010 requirements (wrong length, non-hardened components)
/// - The purpose is not 44' or coin type is not 756'
///
/// # Examples
///
/// ```
/// use rialo_cdk::wallet::mnemonic;
///
/// let mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
///
/// // Derive with standard path
/// let keypair = mnemonic::derive_keypair_from_path(
///     mnemonic,
///     None,
///     "m/44'/756'/0'/0'"
/// ).unwrap();
///
/// // Derive account 5
/// let account_5 = mnemonic::derive_keypair_from_path(
///     mnemonic,
///     None,
///     "m/44'/756'/5'/0'"
/// ).unwrap();
///
/// // With passphrase
/// let protected = mnemonic::derive_keypair_from_path(
///     mnemonic,
///     Some("my passphrase"),
///     "m/44'/756'/0'/0'"
/// ).unwrap();
/// ```
///
/// # Invalid Paths
///
/// The following paths will fail validation:
/// - `"m/44/756/0/0"` - Components not hardened
/// - `"m/44'/756'/0'"` - Wrong number of levels (only 3)
/// - `"m/49'/756'/0'/0'"` - Wrong purpose (should be 44')
/// - `"m/44'/0'/0'/0'"` - Wrong coin type (should be 756')
///
/// # When to Use
///
/// Use this function when you need:
/// - Custom derivation paths for testing
/// - Compatibility with non-standard wallet implementations
/// - Explicit control over the derivation path
///
/// For standard use cases, prefer [`derive_keypair`] which handles path
/// construction automatically.
#[cfg(all(feature = "mnemonic", feature = "hd-wallet"))]
pub fn derive_keypair_from_path(
    mnemonic: &str,
    passphrase: Option<&str>,
    path: &str,
) -> Result<Keypair> {
    let mnemonic = Mnemonic::from_phrase(mnemonic, Language::English)
        .map_err(|e| RialoError::Keyring(format!("Invalid mnemonic: {e}")))?;

    let passphrase = passphrase.unwrap_or("");
    let seed = Seed::new(&mnemonic, passphrase);

    let path: DerivationPath = path
        .parse()
        .map_err(|_| RialoError::Keyring(format!("Cannot parse path: {}", path)))?;
    let validated_path = validate_path(Some(path))?;

    // Convert path to indexes with hardened bit
    let indexes: Vec<u32> = validated_path
        .into_iter()
        .map(|child| {
            let index: u32 = child.into();
            if child.is_hardened() {
                index | 0x80000000
            } else {
                index
            }
        })
        .collect();
    let derived = derive_ed25519_private_key(seed.as_bytes(), &indexes);

    let keypair = Keypair::from_bytes(&derived);
    Ok(keypair)
}

/// Convenience function to derive the default keypair (account 0) from a mnemonic.
///
/// This is a convenience wrapper around [`derive_keypair`] that always uses account
/// index 0, which is the standard first account for most wallet implementations.
/// Use this when you need a simple, default keypair from a mnemonic phrase.
///
/// # Arguments
///
/// * `mnemonic` - A valid BIP39 mnemonic phrase (12, 15, 18, 21, or 24 words)
/// * `passphrase` - Optional passphrase for additional security (BIP39 extension)
///
/// # Derivation Path
///
/// Always derives at: `m/44'/756'/0'/0'` (Rialo's first account)
///
/// # Returns
///
/// Returns a `Result` containing the derived Ed25519 `Keypair` for account 0.
///
/// # Errors
///
/// Returns `RialoError::Keyring` if:
/// - The mnemonic phrase is invalid or malformed
/// - Key derivation fails
///
/// # Examples
///
/// ```
/// use rialo_cdk::wallet::mnemonic;
///
/// // Derive default account without passphrase
/// let mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
/// let keypair = mnemonic::keypair_from_mnemonic(mnemonic, None).unwrap();
///
/// // Derive default account with passphrase protection
/// let protected = mnemonic::keypair_from_mnemonic(
///     mnemonic,
///     Some("my secure passphrase")
/// ).unwrap();
/// ```
///
/// # When to Use
///
/// - **Use this** for simple wallet setups with a single account
/// - **Use this** when you don't need multiple accounts from the same mnemonic
/// - **Use [`derive_keypair`]** when you need specific account indices
/// - **Use [`derive_child_keypair`]** when you need multiple accounts without passphrases
///
/// # Security Considerations
///
/// - This always derives account 0 - the most commonly used account
/// - Multiple calls with the same mnemonic + passphrase produce the same keypair
/// - Store mnemonics securely offline (paper wallet, hardware wallet, secure vault)
#[cfg(all(feature = "mnemonic", feature = "hd-wallet"))]
pub fn keypair_from_mnemonic(mnemonic: &str, passphrase: Option<&str>) -> Result<Keypair> {
    derive_keypair(mnemonic, passphrase, 0)
}

/// Convenience function to derive child keypairs without a passphrase.
///
/// This is a convenience wrapper around [`derive_keypair`] that omits the passphrase
/// parameter, making it simpler to derive multiple accounts from the same mnemonic.
/// Use this when you need multiple accounts but don't require passphrase protection.
///
/// # Arguments
///
/// * `mnemonic` - A valid BIP39 mnemonic phrase (12, 15, 18, 21, or 24 words)
/// * `account_index` - The account index in the derivation path (hardened)
///
/// # Derivation Path
///
/// Derives at: `m/44'/756'/{account_index}'/0'`
/// - `44'` - BIP44 purpose (hardened)
/// - `756'` - Rialo coin type (hardened)
/// - `{account_index}'` - Your specified account number (hardened)
/// - `0'` - Change address (hardened)
///
/// # Returns
///
/// Returns a `Result` containing the derived Ed25519 `Keypair` for the specified account.
///
/// # Errors
///
/// Returns `RialoError::Keyring` if:
/// - The mnemonic phrase is invalid or malformed
/// - The derivation path cannot be constructed
/// - Key derivation fails
///
/// # Examples
///
/// ```
/// use rialo_cdk::wallet::mnemonic;
///
/// let mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
///
/// // Derive multiple accounts from the same mnemonic
/// let account_0 = mnemonic::derive_child_keypair(mnemonic, 0).unwrap();
/// let account_1 = mnemonic::derive_child_keypair(mnemonic, 1).unwrap();
/// let account_2 = mnemonic::derive_child_keypair(mnemonic, 2).unwrap();
///
/// // Each account has a different keypair
/// assert_ne!(
///     account_0.verifying_key().as_bytes(),
///     account_1.verifying_key().as_bytes()
/// );
/// ```
///
/// # When to Use
///
/// - **Use this** when deriving multiple accounts without passphrase protection
/// - **Use this** for simpler API when you don't need passphrases
/// - **Use [`derive_keypair`]** when you need passphrase protection
/// - **Use [`keypair_from_mnemonic`]** when you only need the default account 0
///
/// # Security Considerations
///
/// - This function does not use a passphrase - consider [`derive_keypair`] for additional security
/// - Each account index produces a cryptographically independent keypair
/// - The same mnemonic + account_index always produces the same keypair
/// - All accounts share the same mnemonic - if the mnemonic is compromised, all accounts are compromised
///
/// # Compatibility
///
/// This implementation is compatible with:
/// - Any wallet following BIP44 with Rialo coin type 756
/// - Other Rialo wallet implementations using the standard derivation path
#[cfg(all(feature = "mnemonic", feature = "hd-wallet"))]
pub fn derive_child_keypair(mnemonic: &str, account_index: u32) -> Result<Keypair> {
    derive_keypair(mnemonic, None, account_index)
}
