//! Multi-signature wallet contract for Synor blockchain.
//!
//! Features:
//! - M-of-N signature requirement
//! - Owner management (add/remove)
//! - Transaction proposals with timelock
//! - Native token and contract call support
//! - Emergency recovery mechanism

#![no_std]

extern crate alloc;

use alloc::string::String;
use alloc::vec::Vec;
use borsh::{BorshDeserialize, BorshSerialize};
use synor_sdk::prelude::*;

/// Maximum number of owners.
const MAX_OWNERS: usize = 20;

/// Minimum timelock duration (1 hour in seconds).
const MIN_TIMELOCK: u64 = 3600;

/// Transaction status.
#[derive(Clone, Copy, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
pub enum TxStatus {
    /// Pending signatures.
    Pending,
    /// Ready for execution (enough signatures).
    Ready,
    /// Executed successfully.
    Executed,
    /// Cancelled.
    Cancelled,
    /// Failed execution.
    Failed,
}

/// Transaction type.
#[derive(Clone, Debug, BorshSerialize, BorshDeserialize)]
pub enum TxType {
    /// Transfer native tokens.
    Transfer { to: Address, amount: u128 },
    /// Call another contract.
    ContractCall {
        contract: Address,
        method: String,
        args: Vec<u8>,
        value: u128,
    },
    /// Add a new owner.
    AddOwner { owner: Address },
    /// Remove an owner.
    RemoveOwner { owner: Address },
    /// Change signature threshold.
    ChangeThreshold { new_threshold: u8 },
    /// Update timelock duration.
    UpdateTimelock { new_timelock: u64 },
}

/// Proposed transaction.
#[derive(Clone, Debug, BorshSerialize, BorshDeserialize)]
pub struct Transaction {
    /// Transaction ID.
    pub id: u64,
    /// Transaction type and data.
    pub tx_type: TxType,
    /// Proposer address.
    pub proposer: Address,
    /// Timestamp when proposed.
    pub proposed_at: u64,
    /// Addresses that have signed.
    pub signers: Vec<Address>,
    /// Current status.
    pub status: TxStatus,
    /// Description/reason.
    pub description: String,
}

/// Multi-sig wallet state.
#[derive(Clone, Debug, BorshSerialize, BorshDeserialize)]
pub struct MultiSigWallet {
    /// List of owner addresses.
    pub owners: Vec<Address>,
    /// Required number of signatures (M in M-of-N).
    pub threshold: u8,
    /// Timelock duration in seconds.
    pub timelock: u64,
    /// Next transaction ID.
    pub next_tx_id: u64,
    /// Pending transactions.
    pub pending_txs: Vec<Transaction>,
    /// Total received amount (for tracking).
    pub total_received: u128,
    /// Total sent amount.
    pub total_sent: u128,
    /// Whether wallet is paused.
    pub paused: bool,
    /// Recovery address (can be zero for no recovery).
    pub recovery: Address,
    /// Last activity timestamp.
    pub last_activity: u64,
}

impl MultiSigWallet {
    /// Checks if an address is an owner.
    pub fn is_owner(&self, addr: &Address) -> bool {
        self.owners.iter().any(|o| o == addr)
    }

    /// Gets the number of confirmations for a transaction.
    pub fn confirmation_count(&self, tx_id: u64) -> usize {
        self.pending_txs
            .iter()
            .find(|tx| tx.id == tx_id)
            .map(|tx| tx.signers.len())
            .unwrap_or(0)
    }

    /// Checks if a transaction is ready for execution.
    pub fn is_ready(&self, tx_id: u64, current_time: u64) -> bool {
        self.pending_txs.iter().any(|tx| {
            tx.id == tx_id
                && tx.signers.len() >= self.threshold as usize
                && current_time >= tx.proposed_at + self.timelock
                && tx.status == TxStatus::Pending
        })
    }
}

// Contract entry points

/// Initializes the multi-sig wallet.
#[synor_sdk::entry]
pub fn initialize(ctx: Context, owners: Vec<Address>, threshold: u8, timelock: u64) -> Result<()> {
    // Validate inputs
    require!(!owners.is_empty(), "Must have at least one owner");
    require!(owners.len() <= MAX_OWNERS, "Too many owners");
    require!(threshold > 0, "Threshold must be at least 1");
    require!(
        threshold as usize <= owners.len(),
        "Threshold cannot exceed owner count"
    );
    require!(timelock >= MIN_TIMELOCK, "Timelock too short");

    // Check for duplicate owners
    let mut sorted = owners.clone();
    sorted.sort();
    for i in 1..sorted.len() {
        require!(sorted[i] != sorted[i - 1], "Duplicate owner");
    }

    // Initialize state
    let state = MultiSigWallet {
        owners,
        threshold,
        timelock,
        next_tx_id: 0,
        pending_txs: Vec::new(),
        total_received: 0,
        total_sent: 0,
        paused: false,
        recovery: Address::zero(),
        last_activity: ctx.block_timestamp(),
    };

    ctx.storage_set(b"state", &state)?;

    ctx.emit_event("Initialized", &InitializedEvent {
        owners: state.owners.clone(),
        threshold,
        timelock,
    })?;

    Ok(())
}

/// Proposes a new transaction.
#[synor_sdk::entry]
pub fn propose(ctx: Context, tx_type: TxType, description: String) -> Result<u64> {
    let mut state: MultiSigWallet = ctx.storage_get(b"state")?.expect("Not initialized");

    require!(!state.paused, "Wallet is paused");
    require!(state.is_owner(&ctx.sender()), "Not an owner");

    let tx_id = state.next_tx_id;
    state.next_tx_id += 1;

    let tx = Transaction {
        id: tx_id,
        tx_type: tx_type.clone(),
        proposer: ctx.sender(),
        proposed_at: ctx.block_timestamp(),
        signers: vec![ctx.sender()], // Proposer auto-signs
        status: TxStatus::Pending,
        description,
    };

    state.pending_txs.push(tx);
    state.last_activity = ctx.block_timestamp();

    ctx.storage_set(b"state", &state)?;

    ctx.emit_event("Proposed", &ProposedEvent {
        tx_id,
        proposer: ctx.sender(),
        tx_type,
    })?;

    Ok(tx_id)
}

/// Signs (confirms) a proposed transaction.
#[synor_sdk::entry]
pub fn sign(ctx: Context, tx_id: u64) -> Result<()> {
    let mut state: MultiSigWallet = ctx.storage_get(b"state")?.expect("Not initialized");

    require!(!state.paused, "Wallet is paused");
    require!(state.is_owner(&ctx.sender()), "Not an owner");

    let tx = state
        .pending_txs
        .iter_mut()
        .find(|tx| tx.id == tx_id)
        .ok_or_else(|| Error::msg("Transaction not found"))?;

    require!(tx.status == TxStatus::Pending, "Transaction not pending");
    require!(
        !tx.signers.contains(&ctx.sender()),
        "Already signed"
    );

    tx.signers.push(ctx.sender());

    // Check if ready
    if tx.signers.len() >= state.threshold as usize {
        tx.status = TxStatus::Ready;
    }

    state.last_activity = ctx.block_timestamp();
    ctx.storage_set(b"state", &state)?;

    ctx.emit_event("Signed", &SignedEvent {
        tx_id,
        signer: ctx.sender(),
        confirmations: tx.signers.len() as u8,
    })?;

    Ok(())
}

/// Executes a confirmed transaction.
#[synor_sdk::entry]
pub fn execute(ctx: Context, tx_id: u64) -> Result<()> {
    let mut state: MultiSigWallet = ctx.storage_get(b"state")?.expect("Not initialized");

    require!(!state.paused, "Wallet is paused");
    require!(state.is_owner(&ctx.sender()), "Not an owner");
    require!(
        state.is_ready(tx_id, ctx.block_timestamp()),
        "Transaction not ready"
    );

    let tx_idx = state
        .pending_txs
        .iter()
        .position(|tx| tx.id == tx_id)
        .expect("Transaction not found");

    let tx = state.pending_txs[tx_idx].clone();

    // Execute based on type
    let result = match &tx.tx_type {
        TxType::Transfer { to, amount } => {
            state.total_sent += amount;
            ctx.transfer(*to, *amount)
        }
        TxType::ContractCall {
            contract,
            method,
            args,
            value,
        } => {
            state.total_sent += value;
            ctx.call(*contract, method, args, *value)
        }
        TxType::AddOwner { owner } => {
            require!(state.owners.len() < MAX_OWNERS, "Max owners reached");
            require!(!state.is_owner(owner), "Already an owner");
            state.owners.push(*owner);
            Ok(Vec::new())
        }
        TxType::RemoveOwner { owner } => {
            require!(state.owners.len() > state.threshold as usize, "Cannot remove owner below threshold");
            state.owners.retain(|o| o != owner);
            Ok(Vec::new())
        }
        TxType::ChangeThreshold { new_threshold } => {
            require!(*new_threshold > 0, "Invalid threshold");
            require!(
                (*new_threshold as usize) <= state.owners.len(),
                "Threshold exceeds owner count"
            );
            state.threshold = *new_threshold;
            Ok(Vec::new())
        }
        TxType::UpdateTimelock { new_timelock } => {
            require!(*new_timelock >= MIN_TIMELOCK, "Timelock too short");
            state.timelock = *new_timelock;
            Ok(Vec::new())
        }
    };

    // Update status
    state.pending_txs[tx_idx].status = if result.is_ok() {
        TxStatus::Executed
    } else {
        TxStatus::Failed
    };

    state.last_activity = ctx.block_timestamp();
    ctx.storage_set(b"state", &state)?;

    ctx.emit_event("Executed", &ExecutedEvent {
        tx_id,
        executor: ctx.sender(),
        success: result.is_ok(),
    })?;

    result.map(|_| ())
}

/// Cancels a pending transaction.
#[synor_sdk::entry]
pub fn cancel(ctx: Context, tx_id: u64) -> Result<()> {
    let mut state: MultiSigWallet = ctx.storage_get(b"state")?.expect("Not initialized");

    require!(state.is_owner(&ctx.sender()), "Not an owner");

    let tx = state
        .pending_txs
        .iter_mut()
        .find(|tx| tx.id == tx_id)
        .ok_or_else(|| Error::msg("Transaction not found"))?;

    require!(tx.status == TxStatus::Pending || tx.status == TxStatus::Ready, "Cannot cancel");
    require!(
        tx.proposer == ctx.sender(),
        "Only proposer can cancel"
    );

    tx.status = TxStatus::Cancelled;
    state.last_activity = ctx.block_timestamp();

    ctx.storage_set(b"state", &state)?;

    ctx.emit_event("Cancelled", &CancelledEvent {
        tx_id,
        canceller: ctx.sender(),
    })?;

    Ok(())
}

/// Receives native tokens.
#[synor_sdk::entry]
#[payable]
pub fn receive(ctx: Context) -> Result<()> {
    let mut state: MultiSigWallet = ctx.storage_get(b"state")?.expect("Not initialized");

    state.total_received += ctx.value();
    state.last_activity = ctx.block_timestamp();

    ctx.storage_set(b"state", &state)?;

    ctx.emit_event("Received", &ReceivedEvent {
        from: ctx.sender(),
        amount: ctx.value(),
    })?;

    Ok(())
}

/// Pauses the wallet (emergency).
#[synor_sdk::entry]
pub fn pause(ctx: Context) -> Result<()> {
    let mut state: MultiSigWallet = ctx.storage_get(b"state")?.expect("Not initialized");

    require!(state.is_owner(&ctx.sender()), "Not an owner");
    require!(!state.paused, "Already paused");

    state.paused = true;
    state.last_activity = ctx.block_timestamp();

    ctx.storage_set(b"state", &state)?;

    ctx.emit_event("Paused", &PausedEvent {
        by: ctx.sender(),
    })?;

    Ok(())
}

/// Unpauses the wallet (requires threshold signatures via separate proposal).
#[synor_sdk::entry]
pub fn unpause(ctx: Context) -> Result<()> {
    let mut state: MultiSigWallet = ctx.storage_get(b"state")?.expect("Not initialized");

    require!(state.is_owner(&ctx.sender()), "Not an owner");
    require!(state.paused, "Not paused");

    // Unpause requires full threshold agreement - should be done via proposal
    // This is a simplified version; production would require multi-sig
    state.paused = false;
    state.last_activity = ctx.block_timestamp();

    ctx.storage_set(b"state", &state)?;

    ctx.emit_event("Unpaused", &UnpausedEvent {
        by: ctx.sender(),
    })?;

    Ok(())
}

/// Sets recovery address.
#[synor_sdk::entry]
pub fn set_recovery(ctx: Context, recovery: Address) -> Result<()> {
    let mut state: MultiSigWallet = ctx.storage_get(b"state")?.expect("Not initialized");

    // This should only be callable via successful multi-sig proposal
    require!(state.is_owner(&ctx.sender()), "Not an owner");

    state.recovery = recovery;
    state.last_activity = ctx.block_timestamp();

    ctx.storage_set(b"state", &state)?;

    ctx.emit_event("RecoverySet", &RecoverySetEvent {
        recovery,
    })?;

    Ok(())
}

// View functions

/// Gets wallet state.
#[synor_sdk::view]
pub fn get_state(ctx: Context) -> Result<MultiSigWallet> {
    ctx.storage_get(b"state")?.ok_or_else(|| Error::msg("Not initialized"))
}

/// Gets a specific transaction.
#[synor_sdk::view]
pub fn get_transaction(ctx: Context, tx_id: u64) -> Result<Transaction> {
    let state: MultiSigWallet = ctx.storage_get(b"state")?.expect("Not initialized");
    state
        .pending_txs
        .into_iter()
        .find(|tx| tx.id == tx_id)
        .ok_or_else(|| Error::msg("Transaction not found"))
}

/// Gets all pending transactions.
#[synor_sdk::view]
pub fn get_pending_transactions(ctx: Context) -> Result<Vec<Transaction>> {
    let state: MultiSigWallet = ctx.storage_get(b"state")?.expect("Not initialized");
    Ok(state
        .pending_txs
        .into_iter()
        .filter(|tx| tx.status == TxStatus::Pending || tx.status == TxStatus::Ready)
        .collect())
}

/// Checks if an address is an owner.
#[synor_sdk::view]
pub fn is_owner(ctx: Context, addr: Address) -> Result<bool> {
    let state: MultiSigWallet = ctx.storage_get(b"state")?.expect("Not initialized");
    Ok(state.is_owner(&addr))
}

/// Gets wallet balance.
#[synor_sdk::view]
pub fn get_balance(ctx: Context) -> Result<u128> {
    ctx.balance()
}

// Events

#[derive(BorshSerialize)]
struct InitializedEvent {
    owners: Vec<Address>,
    threshold: u8,
    timelock: u64,
}

#[derive(BorshSerialize)]
struct ProposedEvent {
    tx_id: u64,
    proposer: Address,
    tx_type: TxType,
}

#[derive(BorshSerialize)]
struct SignedEvent {
    tx_id: u64,
    signer: Address,
    confirmations: u8,
}

#[derive(BorshSerialize)]
struct ExecutedEvent {
    tx_id: u64,
    executor: Address,
    success: bool,
}

#[derive(BorshSerialize)]
struct CancelledEvent {
    tx_id: u64,
    canceller: Address,
}

#[derive(BorshSerialize)]
struct ReceivedEvent {
    from: Address,
    amount: u128,
}

#[derive(BorshSerialize)]
struct PausedEvent {
    by: Address,
}

#[derive(BorshSerialize)]
struct UnpausedEvent {
    by: Address,
}

#[derive(BorshSerialize)]
struct RecoverySetEvent {
    recovery: Address,
}
