//! Multi-signature wallet support for Synor.
//!
//! Implements N-of-M threshold signatures where:
//! - M = total number of signers
//! - N = required signatures to execute
//!
//! # Example: 2-of-3 Multisig
//!
//! ```text
//! ┌─────────────────────────────────────────────────────────────┐
//! │                    2-of-3 MULTISIG WALLET                    │
//! ├─────────────────────────────────────────────────────────────┤
//! │                                                              │
//! │  Signers:  [Founder]  [Advisor A]  [Advisor B]              │
//! │                                                              │
//! │  ┌──────────────┐                                           │
//! │  │ Transaction  │  "Send 100K SYNOR to Developer"           │
//! │  │   Pending    │                                           │
//! │  └──────┬───────┘                                           │
//! │         │                                                    │
//! │         ▼                                                    │
//! │  Founder signs    ✓                                         │
//! │  Advisor A signs  ✓                                         │
//! │  Advisor B        (not needed - 2 signatures reached)       │
//! │         │                                                    │
//! │         ▼                                                    │
//! │  ┌──────────────┐                                           │
//! │  │  EXECUTED    │  Transaction sent!                        │
//! │  └──────────────┘                                           │
//! │                                                              │
//! └─────────────────────────────────────────────────────────────┘
//! ```

use borsh::{BorshDeserialize, BorshSerialize};
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};
use synor_types::{Address, Hash256};
use thiserror::Error;

/// Unique identifier for a multisig transaction.
pub type MultisigTxId = Hash256;

/// Multisig configuration.
#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, Serialize, Deserialize)]
pub struct MultisigConfig {
    /// Required number of signatures.
    pub threshold: u32,
    /// List of authorized signers.
    pub signers: Vec<Address>,
    /// Transaction expiry time in seconds (0 = no expiry).
    pub tx_expiry_seconds: u64,
    /// Whether the config can be changed.
    pub mutable: bool,
}

impl MultisigConfig {
    /// Creates a new multisig configuration.
    pub fn new(threshold: u32, signers: Vec<Address>) -> Result<Self, MultisigError> {
        if threshold == 0 {
            return Err(MultisigError::InvalidThreshold);
        }
        if signers.is_empty() {
            return Err(MultisigError::NoSigners);
        }
        if threshold as usize > signers.len() {
            return Err(MultisigError::ThresholdTooHigh);
        }

        // Check for duplicate signers
        let unique: HashSet<_> = signers.iter().collect();
        if unique.len() != signers.len() {
            return Err(MultisigError::DuplicateSigner);
        }

        Ok(MultisigConfig {
            threshold,
            signers,
            tx_expiry_seconds: 7 * 24 * 60 * 60, // 1 week default
            mutable: true,
        })
    }

    /// Creates a 2-of-3 multisig.
    pub fn two_of_three(
        signer1: Address,
        signer2: Address,
        signer3: Address,
    ) -> Result<Self, MultisigError> {
        Self::new(2, vec![signer1, signer2, signer3])
    }

    /// Creates a 3-of-5 multisig.
    pub fn three_of_five(signers: [Address; 5]) -> Result<Self, MultisigError> {
        Self::new(3, signers.to_vec())
    }

    /// Creates an N-of-N multisig (all must sign).
    pub fn unanimous(signers: Vec<Address>) -> Result<Self, MultisigError> {
        let n = signers.len() as u32;
        Self::new(n, signers)
    }

    /// Checks if an address is a signer.
    pub fn is_signer(&self, address: &Address) -> bool {
        self.signers.contains(address)
    }

    /// Returns the number of signers.
    pub fn signer_count(&self) -> usize {
        self.signers.len()
    }

    /// Returns threshold description (e.g., "2-of-3").
    pub fn description(&self) -> String {
        format!("{}-of-{}", self.threshold, self.signers.len())
    }
}

/// State of a multisig transaction.
#[derive(
    Clone, Copy, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, Serialize, Deserialize,
)]
pub enum MultisigTxState {
    /// Transaction is pending signatures.
    Pending,
    /// Transaction has enough signatures and can be executed.
    Ready,
    /// Transaction has been executed.
    Executed,
    /// Transaction was cancelled.
    Cancelled,
    /// Transaction expired.
    Expired,
}

/// Type of multisig transaction.
#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, Serialize, Deserialize)]
pub enum MultisigTxType {
    /// Transfer tokens to an address.
    Transfer { to: Address, amount: u64 },
    /// Add a new signer.
    AddSigner { new_signer: Address },
    /// Remove a signer.
    RemoveSigner { signer: Address },
    /// Change the threshold.
    ChangeThreshold { new_threshold: u32 },
    /// Execute a raw transaction.
    RawTransaction { tx_data: Vec<u8> },
    /// Create a vesting contract.
    CreateVesting {
        beneficiary: Address,
        amount: u64,
        cliff_months: u32,
        vesting_months: u32,
        description: String,
    },
    /// Approve a DAO proposal.
    ApproveProposal { proposal_id: Hash256 },
    /// Custom action with arbitrary data.
    Custom { action_type: String, data: Vec<u8> },
}

/// A pending multisig transaction.
#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, Serialize, Deserialize)]
pub struct MultisigTransaction {
    /// Unique transaction ID.
    pub id: MultisigTxId,
    /// Wallet this transaction belongs to.
    pub wallet_id: Hash256,
    /// Transaction type and parameters.
    pub tx_type: MultisigTxType,
    /// Who proposed this transaction.
    pub proposer: Address,
    /// When the transaction was proposed (Unix timestamp).
    pub proposed_at: u64,
    /// When the transaction expires (Unix timestamp, 0 = never).
    pub expires_at: u64,
    /// Current state.
    pub state: MultisigTxState,
    /// Addresses that have signed.
    pub signatures: Vec<Address>,
    /// Description/reason for this transaction.
    pub description: String,
    /// When executed (if applicable).
    pub executed_at: Option<u64>,
    /// Who executed (if applicable).
    pub executed_by: Option<Address>,
}

impl MultisigTransaction {
    /// Creates a new pending transaction.
    pub fn new(
        wallet_id: Hash256,
        tx_type: MultisigTxType,
        proposer: Address,
        current_time: u64,
        expiry_seconds: u64,
        description: String,
    ) -> Self {
        // Generate ID from content
        let mut id_data = Vec::new();
        id_data.extend_from_slice(wallet_id.as_bytes());
        id_data.extend_from_slice(&current_time.to_le_bytes());
        id_data.extend_from_slice(proposer.payload());
        if let Ok(tx_bytes) = borsh::to_vec(&tx_type) {
            id_data.extend_from_slice(&tx_bytes);
        }
        let id = Hash256::blake3(&id_data);

        let expires_at = if expiry_seconds > 0 {
            current_time + expiry_seconds
        } else {
            0
        };

        MultisigTransaction {
            id,
            wallet_id,
            tx_type,
            proposer: proposer.clone(),
            proposed_at: current_time,
            expires_at,
            state: MultisigTxState::Pending,
            signatures: vec![proposer], // Proposer auto-signs
            description,
            executed_at: None,
            executed_by: None,
        }
    }

    /// Checks if the transaction has expired.
    pub fn is_expired(&self, current_time: u64) -> bool {
        self.expires_at > 0 && current_time > self.expires_at
    }

    /// Returns the number of signatures.
    pub fn signature_count(&self) -> usize {
        self.signatures.len()
    }

    /// Checks if an address has signed.
    pub fn has_signed(&self, address: &Address) -> bool {
        self.signatures.contains(address)
    }

    /// Checks if the transaction is ready to execute.
    pub fn is_ready(&self, threshold: u32) -> bool {
        self.signatures.len() >= threshold as usize
    }
}

/// Multisig errors.
#[derive(Debug, Error)]
pub enum MultisigError {
    #[error("Wallet not found")]
    WalletNotFound,

    #[error("Transaction not found")]
    TransactionNotFound,

    #[error("Not authorized (not a signer)")]
    NotAuthorized,

    #[error("Already signed this transaction")]
    AlreadySigned,

    #[error("Transaction already executed")]
    AlreadyExecuted,

    #[error("Transaction cancelled")]
    Cancelled,

    #[error("Transaction expired")]
    Expired,

    #[error("Not enough signatures")]
    NotEnoughSignatures,

    #[error("Invalid threshold (must be > 0)")]
    InvalidThreshold,

    #[error("Threshold higher than signer count")]
    ThresholdTooHigh,

    #[error("No signers provided")]
    NoSigners,

    #[error("Duplicate signer in list")]
    DuplicateSigner,

    #[error("Cannot remove signer: would break threshold")]
    CannotRemoveSigner,

    #[error("Wallet configuration is immutable")]
    Immutable,

    #[error("Insufficient balance")]
    InsufficientBalance,
}

/// A multisig wallet.
#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, Serialize, Deserialize)]
pub struct MultisigWallet {
    /// Unique wallet ID.
    pub id: Hash256,
    /// Wallet name/label.
    pub name: String,
    /// Configuration.
    pub config: MultisigConfig,
    /// Pending transactions.
    pub pending_transactions: HashMap<MultisigTxId, MultisigTransaction>,
    /// Executed transaction history (limited).
    pub executed_transactions: Vec<MultisigTxId>,
    /// Token balance (tracked separately from UTXO for simplicity).
    pub balance: u64,
    /// Creation timestamp.
    pub created_at: u64,
    /// Nonce for transaction ordering.
    pub nonce: u64,
}

impl MultisigWallet {
    /// Creates a new multisig wallet.
    pub fn new(name: String, config: MultisigConfig, created_at: u64) -> Self {
        // Generate wallet ID
        let mut id_data = Vec::new();
        id_data.extend_from_slice(name.as_bytes());
        id_data.extend_from_slice(&created_at.to_le_bytes());
        for signer in &config.signers {
            id_data.extend_from_slice(signer.payload());
        }
        let id = Hash256::blake3(&id_data);

        MultisigWallet {
            id,
            name,
            config,
            pending_transactions: HashMap::new(),
            executed_transactions: Vec::new(),
            balance: 0,
            created_at,
            nonce: 0,
        }
    }

    /// Proposes a new transaction.
    pub fn propose(
        &mut self,
        tx_type: MultisigTxType,
        proposer: &Address,
        current_time: u64,
        description: String,
    ) -> Result<MultisigTxId, MultisigError> {
        // Check proposer is a signer
        if !self.config.is_signer(proposer) {
            return Err(MultisigError::NotAuthorized);
        }

        let tx = MultisigTransaction::new(
            self.id,
            tx_type,
            proposer.clone(),
            current_time,
            self.config.tx_expiry_seconds,
            description,
        );

        let tx_id = tx.id;
        self.pending_transactions.insert(tx_id, tx);
        self.nonce += 1;

        Ok(tx_id)
    }

    /// Signs a pending transaction.
    pub fn sign(
        &mut self,
        tx_id: &MultisigTxId,
        signer: &Address,
        current_time: u64,
    ) -> Result<MultisigTxState, MultisigError> {
        // Check signer is authorized
        if !self.config.is_signer(signer) {
            return Err(MultisigError::NotAuthorized);
        }

        let tx = self
            .pending_transactions
            .get_mut(tx_id)
            .ok_or(MultisigError::TransactionNotFound)?;

        // Check transaction state
        if tx.state == MultisigTxState::Executed {
            return Err(MultisigError::AlreadyExecuted);
        }
        if tx.state == MultisigTxState::Cancelled {
            return Err(MultisigError::Cancelled);
        }
        if tx.is_expired(current_time) {
            tx.state = MultisigTxState::Expired;
            return Err(MultisigError::Expired);
        }

        // Check if already signed
        if tx.has_signed(signer) {
            return Err(MultisigError::AlreadySigned);
        }

        // Add signature
        tx.signatures.push(signer.clone());

        // Update state if threshold reached
        if tx.is_ready(self.config.threshold) {
            tx.state = MultisigTxState::Ready;
        }

        Ok(tx.state)
    }

    /// Executes a ready transaction.
    pub fn execute(
        &mut self,
        tx_id: &MultisigTxId,
        executor: &Address,
        current_time: u64,
    ) -> Result<&MultisigTransaction, MultisigError> {
        // Check executor is a signer
        if !self.config.is_signer(executor) {
            return Err(MultisigError::NotAuthorized);
        }

        let tx = self
            .pending_transactions
            .get_mut(tx_id)
            .ok_or(MultisigError::TransactionNotFound)?;

        // Check transaction state
        if tx.state == MultisigTxState::Executed {
            return Err(MultisigError::AlreadyExecuted);
        }
        if tx.is_expired(current_time) {
            tx.state = MultisigTxState::Expired;
            return Err(MultisigError::Expired);
        }
        if !tx.is_ready(self.config.threshold) {
            return Err(MultisigError::NotEnoughSignatures);
        }

        // Validate transaction-specific requirements
        match &tx.tx_type {
            MultisigTxType::Transfer { amount, .. } => {
                if *amount > self.balance {
                    return Err(MultisigError::InsufficientBalance);
                }
                self.balance -= amount;
            }
            MultisigTxType::ChangeThreshold { new_threshold } => {
                if *new_threshold as usize > self.config.signers.len() {
                    return Err(MultisigError::ThresholdTooHigh);
                }
                if !self.config.mutable {
                    return Err(MultisigError::Immutable);
                }
                self.config.threshold = *new_threshold;
            }
            MultisigTxType::AddSigner { new_signer } => {
                if !self.config.mutable {
                    return Err(MultisigError::Immutable);
                }
                if !self.config.signers.contains(new_signer) {
                    self.config.signers.push(new_signer.clone());
                }
            }
            MultisigTxType::RemoveSigner { signer } => {
                if !self.config.mutable {
                    return Err(MultisigError::Immutable);
                }
                // Check this won't break threshold
                if self.config.signers.len() - 1 < self.config.threshold as usize {
                    return Err(MultisigError::CannotRemoveSigner);
                }
                self.config.signers.retain(|s| s != signer);
            }
            _ => {
                // Other transaction types handled externally
            }
        }

        // Mark as executed
        tx.state = MultisigTxState::Executed;
        tx.executed_at = Some(current_time);
        tx.executed_by = Some(executor.clone());

        // Move to executed history
        self.executed_transactions.push(*tx_id);

        // Limit history size
        while self.executed_transactions.len() > 100 {
            self.executed_transactions.remove(0);
        }

        Ok(self.pending_transactions.get(tx_id).unwrap())
    }

    /// Cancels a pending transaction.
    pub fn cancel(
        &mut self,
        tx_id: &MultisigTxId,
        canceller: &Address,
    ) -> Result<(), MultisigError> {
        // Only proposer can cancel
        let tx = self
            .pending_transactions
            .get_mut(tx_id)
            .ok_or(MultisigError::TransactionNotFound)?;

        if &tx.proposer != canceller {
            return Err(MultisigError::NotAuthorized);
        }

        if tx.state == MultisigTxState::Executed {
            return Err(MultisigError::AlreadyExecuted);
        }

        tx.state = MultisigTxState::Cancelled;
        Ok(())
    }

    /// Deposits tokens to the wallet.
    pub fn deposit(&mut self, amount: u64) {
        self.balance += amount;
    }

    /// Gets a pending transaction.
    pub fn get_transaction(&self, tx_id: &MultisigTxId) -> Option<&MultisigTransaction> {
        self.pending_transactions.get(tx_id)
    }

    /// Gets all pending transactions.
    pub fn pending(&self) -> Vec<&MultisigTransaction> {
        self.pending_transactions
            .values()
            .filter(|tx| tx.state == MultisigTxState::Pending || tx.state == MultisigTxState::Ready)
            .collect()
    }

    /// Gets transactions awaiting a specific signer.
    pub fn awaiting_signature(&self, signer: &Address) -> Vec<&MultisigTransaction> {
        self.pending_transactions
            .values()
            .filter(|tx| {
                (tx.state == MultisigTxState::Pending || tx.state == MultisigTxState::Ready)
                    && !tx.has_signed(signer)
            })
            .collect()
    }

    /// Cleans up expired transactions.
    pub fn cleanup_expired(&mut self, current_time: u64) -> usize {
        let expired: Vec<_> = self
            .pending_transactions
            .iter()
            .filter(|(_, tx)| tx.is_expired(current_time) && tx.state == MultisigTxState::Pending)
            .map(|(id, _)| *id)
            .collect();

        for id in &expired {
            if let Some(tx) = self.pending_transactions.get_mut(id) {
                tx.state = MultisigTxState::Expired;
            }
        }

        expired.len()
    }

    /// Returns wallet summary.
    pub fn summary(&self) -> MultisigWalletSummary {
        let pending: Vec<_> = self.pending().into_iter().collect();

        MultisigWalletSummary {
            id: self.id,
            name: self.name.clone(),
            config_description: self.config.description(),
            signer_count: self.config.signers.len(),
            threshold: self.config.threshold,
            balance: self.balance,
            pending_count: pending.len(),
            ready_count: pending
                .iter()
                .filter(|tx| tx.state == MultisigTxState::Ready)
                .count(),
            total_executed: self.executed_transactions.len(),
        }
    }
}

/// Summary of a multisig wallet.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct MultisigWalletSummary {
    pub id: Hash256,
    pub name: String,
    pub config_description: String,
    pub signer_count: usize,
    pub threshold: u32,
    pub balance: u64,
    pub pending_count: usize,
    pub ready_count: usize,
    pub total_executed: usize,
}

/// Manages multiple multisig wallets.
pub struct MultisigManager {
    wallets: HashMap<Hash256, MultisigWallet>,
    by_signer: HashMap<Address, Vec<Hash256>>,
}

impl MultisigManager {
    /// Creates a new multisig manager.
    pub fn new() -> Self {
        MultisigManager {
            wallets: HashMap::new(),
            by_signer: HashMap::new(),
        }
    }

    /// Creates a new multisig wallet.
    pub fn create_wallet(
        &mut self,
        name: String,
        config: MultisigConfig,
        created_at: u64,
    ) -> Hash256 {
        let wallet = MultisigWallet::new(name, config.clone(), created_at);
        let id = wallet.id;

        // Index by signers
        for signer in &config.signers {
            self.by_signer.entry(signer.clone()).or_default().push(id);
        }

        self.wallets.insert(id, wallet);
        id
    }

    /// Gets a wallet by ID.
    pub fn get(&self, id: &Hash256) -> Option<&MultisigWallet> {
        self.wallets.get(id)
    }

    /// Gets mutable wallet reference.
    pub fn get_mut(&mut self, id: &Hash256) -> Option<&mut MultisigWallet> {
        self.wallets.get_mut(id)
    }

    /// Gets all wallets for a signer.
    pub fn wallets_for_signer(&self, signer: &Address) -> Vec<&MultisigWallet> {
        self.by_signer
            .get(signer)
            .map(|ids| ids.iter().filter_map(|id| self.wallets.get(id)).collect())
            .unwrap_or_default()
    }

    /// Gets all transactions awaiting a signer across all wallets.
    pub fn awaiting_signature(
        &self,
        signer: &Address,
    ) -> Vec<(&MultisigWallet, &MultisigTransaction)> {
        self.wallets_for_signer(signer)
            .into_iter()
            .flat_map(|wallet| {
                wallet
                    .awaiting_signature(signer)
                    .into_iter()
                    .map(move |tx| (wallet, tx))
            })
            .collect()
    }

    /// Returns all wallets.
    pub fn all_wallets(&self) -> Vec<&MultisigWallet> {
        self.wallets.values().collect()
    }
}

impl Default for MultisigManager {
    fn default() -> Self {
        Self::new()
    }
}

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

    fn test_address(n: u8) -> Address {
        let mut bytes = [0u8; 32];
        bytes[0] = n;
        Address::from_parts(synor_types::Network::Devnet, AddressType::P2PKH, bytes)
    }

    #[test]
    fn test_multisig_config() {
        let config =
            MultisigConfig::two_of_three(test_address(1), test_address(2), test_address(3))
                .unwrap();

        assert_eq!(config.threshold, 2);
        assert_eq!(config.signer_count(), 3);
        assert_eq!(config.description(), "2-of-3");
        assert!(config.is_signer(&test_address(1)));
        assert!(!config.is_signer(&test_address(4)));
    }

    #[test]
    fn test_invalid_config() {
        // Threshold > signers
        let result = MultisigConfig::new(3, vec![test_address(1), test_address(2)]);
        assert!(matches!(result, Err(MultisigError::ThresholdTooHigh)));

        // Zero threshold
        let result = MultisigConfig::new(0, vec![test_address(1)]);
        assert!(matches!(result, Err(MultisigError::InvalidThreshold)));

        // Duplicate signer
        let result = MultisigConfig::new(1, vec![test_address(1), test_address(1)]);
        assert!(matches!(result, Err(MultisigError::DuplicateSigner)));
    }

    #[test]
    fn test_multisig_flow() {
        let signer1 = test_address(1);
        let signer2 = test_address(2);
        let signer3 = test_address(3);
        let recipient = test_address(10);

        let config =
            MultisigConfig::two_of_three(signer1.clone(), signer2.clone(), signer3.clone())
                .unwrap();

        let mut wallet = MultisigWallet::new("Test Wallet".to_string(), config, 0);
        wallet.deposit(1_000_000);

        // Propose a transfer
        let tx_id = wallet
            .propose(
                MultisigTxType::Transfer {
                    to: recipient,
                    amount: 100_000,
                },
                &signer1,
                100,
                "Pay developer".to_string(),
            )
            .unwrap();

        // Check proposer auto-signed
        let tx = wallet.get_transaction(&tx_id).unwrap();
        assert_eq!(tx.signature_count(), 1);
        assert_eq!(tx.state, MultisigTxState::Pending);

        // Second signature
        let state = wallet.sign(&tx_id, &signer2, 101).unwrap();
        assert_eq!(state, MultisigTxState::Ready);

        // Execute
        let executed_tx = wallet.execute(&tx_id, &signer2, 102).unwrap();
        assert_eq!(executed_tx.state, MultisigTxState::Executed);
        assert_eq!(wallet.balance, 900_000);
    }

    #[test]
    fn test_not_enough_signatures() {
        let signer1 = test_address(1);
        let signer2 = test_address(2);
        let signer3 = test_address(3);

        let config =
            MultisigConfig::two_of_three(signer1.clone(), signer2.clone(), signer3.clone())
                .unwrap();

        let mut wallet = MultisigWallet::new("Test".to_string(), config, 0);
        wallet.deposit(1_000_000);

        let tx_id = wallet
            .propose(
                MultisigTxType::Transfer {
                    to: test_address(10),
                    amount: 100,
                },
                &signer1,
                100,
                "Test".to_string(),
            )
            .unwrap();

        // Try to execute with only 1 signature
        let result = wallet.execute(&tx_id, &signer1, 101);
        assert!(matches!(result, Err(MultisigError::NotEnoughSignatures)));
    }

    #[test]
    fn test_multisig_manager() {
        let mut manager = MultisigManager::new();
        let signer = test_address(1);

        let config = MultisigConfig::new(1, vec![signer.clone()]).unwrap();
        let wallet_id = manager.create_wallet("Test".to_string(), config, 0);

        assert!(manager.get(&wallet_id).is_some());
        assert_eq!(manager.wallets_for_signer(&signer).len(), 1);
    }
}
