//! Token vesting contracts for Synor.
//!
//! Implements token lockup with:
//! - Cliff period (no tokens released)
//! - Linear vesting (gradual release after cliff)
//! - Revocation support (for employee departures)
//!
//! # Example Vesting Schedule
//!
//! ```text
//! 4-year vesting with 1-year cliff:
//!
//! Tokens
//! Released
//!    │
//! 100%│                                    ●────────
//!    │                               ●───●
//!    │                          ●───●
//!    │                     ●───●
//!    │                ●───●
//!  25%│           ●───●
//!    │      ●────●
//!   0%├─────●
//!    └──────┬──────┬──────┬──────┬──────► Time
//!           1yr    2yr    3yr    4yr
//!         (cliff)
//! ```

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

/// Vesting schedule parameters.
#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, Serialize, Deserialize)]
pub struct VestingSchedule {
    /// Total amount of tokens to vest.
    pub total_amount: u64,
    /// Cliff duration in seconds.
    pub cliff_duration: u64,
    /// Total vesting duration in seconds (including cliff).
    pub vesting_duration: u64,
    /// Start timestamp (Unix seconds).
    pub start_time: u64,
    /// Whether the schedule is revocable by the grantor.
    pub revocable: bool,
}

impl VestingSchedule {
    /// Creates a new vesting schedule.
    pub fn new(
        total_amount: u64,
        cliff_months: u32,
        vesting_months: u32,
        start_time: u64,
        revocable: bool,
    ) -> Self {
        let seconds_per_month = 30 * 24 * 60 * 60; // ~30 days
        VestingSchedule {
            total_amount,
            cliff_duration: cliff_months as u64 * seconds_per_month,
            vesting_duration: vesting_months as u64 * seconds_per_month,
            start_time,
            revocable,
        }
    }

    /// Standard 4-year vesting with 1-year cliff.
    pub fn standard_4year(total_amount: u64, start_time: u64) -> Self {
        Self::new(total_amount, 12, 48, start_time, true)
    }

    /// Advisor vesting (2-year with 6-month cliff).
    pub fn advisor(total_amount: u64, start_time: u64) -> Self {
        Self::new(total_amount, 6, 24, start_time, true)
    }

    /// Investor vesting (1-year linear, no cliff).
    pub fn investor(total_amount: u64, start_time: u64) -> Self {
        Self::new(total_amount, 0, 12, start_time, false)
    }

    /// Founder vesting (4-year with 1-year cliff, non-revocable).
    pub fn founder(total_amount: u64, start_time: u64) -> Self {
        Self::new(total_amount, 12, 48, start_time, false)
    }

    /// Calculates vested amount at a given timestamp.
    pub fn vested_at(&self, timestamp: u64) -> u64 {
        if timestamp < self.start_time {
            return 0;
        }

        let elapsed = timestamp - self.start_time;

        // Before cliff: nothing vested
        if elapsed < self.cliff_duration {
            return 0;
        }

        // After full vesting: everything vested
        if elapsed >= self.vesting_duration {
            return self.total_amount;
        }

        // Linear vesting between cliff and end
        // vested = total * elapsed / vesting_duration
        (self.total_amount as u128 * elapsed as u128 / self.vesting_duration as u128) as u64
    }

    /// Returns the cliff end timestamp.
    pub fn cliff_end(&self) -> u64 {
        self.start_time + self.cliff_duration
    }

    /// Returns the vesting end timestamp.
    pub fn vesting_end(&self) -> u64 {
        self.start_time + self.vesting_duration
    }

    /// Checks if the cliff has passed.
    pub fn cliff_passed(&self, timestamp: u64) -> bool {
        timestamp >= self.cliff_end()
    }

    /// Checks if vesting is complete.
    pub fn fully_vested(&self, timestamp: u64) -> bool {
        timestamp >= self.vesting_end()
    }
}

/// Current state of a vesting contract.
#[derive(
    Clone, Copy, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, Serialize, Deserialize,
)]
pub enum VestingState {
    /// Vesting is active.
    Active,
    /// Vesting was revoked by grantor.
    Revoked,
    /// All tokens have been claimed.
    Completed,
}

/// A vesting contract instance.
#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, Serialize, Deserialize)]
pub struct VestingContract {
    /// Unique identifier (hash of creation params).
    pub id: Hash256,
    /// Beneficiary who receives the tokens.
    pub beneficiary: Address,
    /// Grantor who created the vesting (can revoke if revocable).
    pub grantor: Address,
    /// Vesting schedule parameters.
    pub schedule: VestingSchedule,
    /// Amount already claimed by beneficiary.
    pub claimed_amount: u64,
    /// Current state.
    pub state: VestingState,
    /// Timestamp when revoked (if applicable).
    pub revoked_at: Option<u64>,
    /// Description/purpose of this vesting.
    pub description: String,
}

/// Vesting errors.
#[derive(Debug, Error)]
pub enum VestingError {
    #[error("Vesting contract not found")]
    NotFound,

    #[error("Not authorized to perform this action")]
    Unauthorized,

    #[error("Nothing available to claim")]
    NothingToClaim,

    #[error("Vesting is not revocable")]
    NotRevocable,

    #[error("Vesting already revoked")]
    AlreadyRevoked,

    #[error("Vesting already completed")]
    AlreadyCompleted,

    #[error("Invalid vesting parameters")]
    InvalidParameters,

    #[error("Cliff not yet passed")]
    CliffNotPassed,

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

impl VestingContract {
    /// Creates a new vesting contract.
    pub fn new(
        beneficiary: Address,
        grantor: Address,
        schedule: VestingSchedule,
        description: String,
    ) -> Result<Self, VestingError> {
        // Validate parameters
        if schedule.total_amount == 0 {
            return Err(VestingError::InvalidParameters);
        }
        if schedule.vesting_duration == 0 {
            return Err(VestingError::InvalidParameters);
        }
        if schedule.cliff_duration > schedule.vesting_duration {
            return Err(VestingError::InvalidParameters);
        }

        // Generate unique ID
        let mut id_data = Vec::new();
        id_data.extend_from_slice(beneficiary.payload());
        id_data.extend_from_slice(grantor.payload());
        id_data.extend_from_slice(&schedule.start_time.to_le_bytes());
        id_data.extend_from_slice(&schedule.total_amount.to_le_bytes());
        let id = Hash256::blake3(&id_data);

        Ok(VestingContract {
            id,
            beneficiary,
            grantor,
            schedule,
            claimed_amount: 0,
            state: VestingState::Active,
            revoked_at: None,
            description,
        })
    }

    /// Creates a vesting contract for a team member.
    pub fn for_team_member(
        beneficiary: Address,
        grantor: Address,
        amount: u64,
        start_time: u64,
        name: &str,
    ) -> Result<Self, VestingError> {
        Self::new(
            beneficiary,
            grantor,
            VestingSchedule::standard_4year(amount, start_time),
            format!("Team allocation for {}", name),
        )
    }

    /// Creates a vesting contract for an advisor.
    pub fn for_advisor(
        beneficiary: Address,
        grantor: Address,
        amount: u64,
        start_time: u64,
        name: &str,
    ) -> Result<Self, VestingError> {
        Self::new(
            beneficiary,
            grantor,
            VestingSchedule::advisor(amount, start_time),
            format!("Advisor allocation for {}", name),
        )
    }

    /// Creates a vesting contract for a founder.
    pub fn for_founder(
        beneficiary: Address,
        amount: u64,
        start_time: u64,
    ) -> Result<Self, VestingError> {
        Self::new(
            beneficiary.clone(),
            beneficiary, // Founder is their own grantor
            VestingSchedule::founder(amount, start_time),
            "Founder allocation".to_string(),
        )
    }

    /// Returns the total vested amount at current time.
    pub fn vested_amount(&self, timestamp: u64) -> u64 {
        match self.state {
            VestingState::Active => self.schedule.vested_at(timestamp),
            VestingState::Revoked => {
                // If revoked, vested amount is frozen at revocation time
                if let Some(revoked_at) = self.revoked_at {
                    self.schedule.vested_at(revoked_at)
                } else {
                    0
                }
            }
            VestingState::Completed => self.schedule.total_amount,
        }
    }

    /// Returns the claimable amount (vested - already claimed).
    pub fn claimable_amount(&self, timestamp: u64) -> u64 {
        let vested = self.vested_amount(timestamp);
        vested.saturating_sub(self.claimed_amount)
    }

    /// Returns the unvested amount.
    pub fn unvested_amount(&self, timestamp: u64) -> u64 {
        self.schedule
            .total_amount
            .saturating_sub(self.vested_amount(timestamp))
    }

    /// Claims available tokens.
    pub fn claim(&mut self, caller: &Address, timestamp: u64) -> Result<u64, VestingError> {
        // Only beneficiary can claim
        if caller != &self.beneficiary {
            return Err(VestingError::Unauthorized);
        }

        // Check state
        if self.state == VestingState::Completed {
            return Err(VestingError::AlreadyCompleted);
        }

        // Calculate claimable
        let claimable = self.claimable_amount(timestamp);
        if claimable == 0 {
            return Err(VestingError::NothingToClaim);
        }

        // Update claimed amount
        self.claimed_amount += claimable;

        // Check if completed
        if self.claimed_amount >= self.schedule.total_amount {
            self.state = VestingState::Completed;
        }

        Ok(claimable)
    }

    /// Revokes the vesting contract (only by grantor, if revocable).
    pub fn revoke(&mut self, caller: &Address, timestamp: u64) -> Result<u64, VestingError> {
        // Only grantor can revoke
        if caller != &self.grantor {
            return Err(VestingError::Unauthorized);
        }

        // Check if revocable
        if !self.schedule.revocable {
            return Err(VestingError::NotRevocable);
        }

        // Check state
        if self.state == VestingState::Revoked {
            return Err(VestingError::AlreadyRevoked);
        }
        if self.state == VestingState::Completed {
            return Err(VestingError::AlreadyCompleted);
        }

        // Calculate unvested amount to return to grantor
        let unvested = self.unvested_amount(timestamp);

        // Update state
        self.state = VestingState::Revoked;
        self.revoked_at = Some(timestamp);

        Ok(unvested)
    }

    /// Returns vesting progress as a percentage (0-100).
    pub fn progress_percent(&self, timestamp: u64) -> f64 {
        let vested = self.vested_amount(timestamp);
        (vested as f64 / self.schedule.total_amount as f64) * 100.0
    }

    /// Returns summary information.
    pub fn summary(&self, timestamp: u64) -> VestingSummary {
        VestingSummary {
            id: self.id,
            beneficiary: self.beneficiary.clone(),
            total: self.schedule.total_amount,
            vested: self.vested_amount(timestamp),
            claimed: self.claimed_amount,
            claimable: self.claimable_amount(timestamp),
            unvested: self.unvested_amount(timestamp),
            state: self.state,
            cliff_passed: self.schedule.cliff_passed(timestamp),
            fully_vested: self.schedule.fully_vested(timestamp),
            progress_percent: self.progress_percent(timestamp),
        }
    }
}

/// Summary of a vesting contract's current state.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct VestingSummary {
    pub id: Hash256,
    pub beneficiary: Address,
    pub total: u64,
    pub vested: u64,
    pub claimed: u64,
    pub claimable: u64,
    pub unvested: u64,
    pub state: VestingState,
    pub cliff_passed: bool,
    pub fully_vested: bool,
    pub progress_percent: f64,
}

/// Manages all vesting contracts.
pub struct VestingManager {
    contracts: hashbrown::HashMap<Hash256, VestingContract>,
    by_beneficiary: hashbrown::HashMap<Address, Vec<Hash256>>,
    by_grantor: hashbrown::HashMap<Address, Vec<Hash256>>,
}

impl VestingManager {
    /// Creates a new vesting manager.
    pub fn new() -> Self {
        VestingManager {
            contracts: hashbrown::HashMap::new(),
            by_beneficiary: hashbrown::HashMap::new(),
            by_grantor: hashbrown::HashMap::new(),
        }
    }

    /// Creates a new vesting contract.
    pub fn create_vesting(
        &mut self,
        beneficiary: Address,
        grantor: Address,
        schedule: VestingSchedule,
        description: String,
    ) -> Result<Hash256, VestingError> {
        let contract =
            VestingContract::new(beneficiary.clone(), grantor.clone(), schedule, description)?;
        let id = contract.id;

        // Index by beneficiary
        self.by_beneficiary.entry(beneficiary).or_default().push(id);

        // Index by grantor
        self.by_grantor.entry(grantor).or_default().push(id);

        self.contracts.insert(id, contract);
        Ok(id)
    }

    /// Gets a vesting contract by ID.
    pub fn get(&self, id: &Hash256) -> Option<&VestingContract> {
        self.contracts.get(id)
    }

    /// Gets mutable reference to a vesting contract.
    pub fn get_mut(&mut self, id: &Hash256) -> Option<&mut VestingContract> {
        self.contracts.get_mut(id)
    }

    /// Gets all vesting contracts for a beneficiary.
    pub fn get_by_beneficiary(&self, beneficiary: &Address) -> Vec<&VestingContract> {
        self.by_beneficiary
            .get(beneficiary)
            .map(|ids| ids.iter().filter_map(|id| self.contracts.get(id)).collect())
            .unwrap_or_default()
    }

    /// Gets all vesting contracts created by a grantor.
    pub fn get_by_grantor(&self, grantor: &Address) -> Vec<&VestingContract> {
        self.by_grantor
            .get(grantor)
            .map(|ids| ids.iter().filter_map(|id| self.contracts.get(id)).collect())
            .unwrap_or_default()
    }

    /// Claims tokens from a vesting contract.
    pub fn claim(
        &mut self,
        id: &Hash256,
        caller: &Address,
        timestamp: u64,
    ) -> Result<u64, VestingError> {
        let contract = self.contracts.get_mut(id).ok_or(VestingError::NotFound)?;
        contract.claim(caller, timestamp)
    }

    /// Revokes a vesting contract.
    pub fn revoke(
        &mut self,
        id: &Hash256,
        caller: &Address,
        timestamp: u64,
    ) -> Result<u64, VestingError> {
        let contract = self.contracts.get_mut(id).ok_or(VestingError::NotFound)?;
        contract.revoke(caller, timestamp)
    }

    /// Returns total vested amount across all contracts for a beneficiary.
    pub fn total_vested_for(&self, beneficiary: &Address, timestamp: u64) -> u64 {
        self.get_by_beneficiary(beneficiary)
            .iter()
            .map(|c| c.vested_amount(timestamp))
            .sum()
    }

    /// Returns total claimable amount across all contracts for a beneficiary.
    pub fn total_claimable_for(&self, beneficiary: &Address, timestamp: u64) -> u64 {
        self.get_by_beneficiary(beneficiary)
            .iter()
            .map(|c| c.claimable_amount(timestamp))
            .sum()
    }

    /// Returns all active contracts.
    pub fn active_contracts(&self) -> Vec<&VestingContract> {
        self.contracts
            .values()
            .filter(|c| c.state == VestingState::Active)
            .collect()
    }

    /// Returns statistics.
    pub fn stats(&self) -> VestingStats {
        let contracts: Vec<_> = self.contracts.values().collect();
        VestingStats {
            total_contracts: contracts.len(),
            active_contracts: contracts
                .iter()
                .filter(|c| c.state == VestingState::Active)
                .count(),
            revoked_contracts: contracts
                .iter()
                .filter(|c| c.state == VestingState::Revoked)
                .count(),
            completed_contracts: contracts
                .iter()
                .filter(|c| c.state == VestingState::Completed)
                .count(),
            total_amount: contracts.iter().map(|c| c.schedule.total_amount).sum(),
            total_claimed: contracts.iter().map(|c| c.claimed_amount).sum(),
        }
    }
}

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

/// Vesting statistics.
#[derive(Clone, Debug)]
pub struct VestingStats {
    pub total_contracts: usize,
    pub active_contracts: usize,
    pub revoked_contracts: usize,
    pub completed_contracts: usize,
    pub total_amount: u64,
    pub total_claimed: u64,
}

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

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

    #[test]
    fn test_vesting_schedule_cliff() {
        let schedule = VestingSchedule::standard_4year(1_000_000, 0);

        // Implementation uses 30-day months, so 12 months = 360 days
        let seconds_per_month: u64 = 30 * 24 * 60 * 60;
        let cliff_12_months = 12 * seconds_per_month; // 31104000
        let vesting_48_months = 48 * seconds_per_month; // 124416000

        // Before cliff
        assert_eq!(schedule.vested_at(0), 0);
        assert_eq!(schedule.vested_at(6 * seconds_per_month), 0); // 6 months
        assert_eq!(schedule.vested_at(cliff_12_months - 1), 0); // Just before cliff

        // At cliff - some tokens should be vested (linear from 0)
        assert!(schedule.vested_at(cliff_12_months) > 0);

        // After full vesting (48 months)
        assert_eq!(schedule.vested_at(vesting_48_months), 1_000_000);
    }

    #[test]
    fn test_vesting_contract_claim() {
        let beneficiary = test_address(1);
        let grantor = test_address(2);
        let start_time = 0;

        let mut contract = VestingContract::new(
            beneficiary.clone(),
            grantor,
            VestingSchedule::new(1_000_000, 0, 12, start_time, true), // 1 year, no cliff
            "Test vesting".to_string(),
        )
        .unwrap();

        // After 6 months, should have ~50% vested
        let six_months = 6 * 30 * 24 * 60 * 60;
        let claimable = contract.claimable_amount(six_months);
        assert!(claimable > 400_000 && claimable < 600_000);

        // Claim
        let claimed = contract.claim(&beneficiary, six_months).unwrap();
        assert_eq!(claimed, claimable);
        assert_eq!(contract.claimed_amount, claimable);

        // No more claimable immediately
        assert_eq!(contract.claimable_amount(six_months), 0);
    }

    #[test]
    fn test_vesting_revocation() {
        let beneficiary = test_address(1);
        let grantor = test_address(2);

        let mut contract = VestingContract::new(
            beneficiary,
            grantor.clone(),
            VestingSchedule::new(1_000_000, 0, 12, 0, true),
            "Revocable".to_string(),
        )
        .unwrap();

        // Revoke at 6 months
        let six_months = 6 * 30 * 24 * 60 * 60;
        let unvested = contract.revoke(&grantor, six_months).unwrap();

        assert!(unvested > 400_000); // ~50% unvested returned
        assert_eq!(contract.state, VestingState::Revoked);
    }

    #[test]
    fn test_non_revocable() {
        let beneficiary = test_address(1);
        let grantor = test_address(2);

        let mut contract = VestingContract::new(
            beneficiary,
            grantor.clone(),
            VestingSchedule::new(1_000_000, 0, 12, 0, false), // non-revocable
            "Non-revocable".to_string(),
        )
        .unwrap();

        let result = contract.revoke(&grantor, 0);
        assert!(matches!(result, Err(VestingError::NotRevocable)));
    }

    #[test]
    fn test_vesting_manager() {
        let mut manager = VestingManager::new();
        let beneficiary = test_address(1);
        let grantor = test_address(2);

        let id = manager
            .create_vesting(
                beneficiary.clone(),
                grantor,
                VestingSchedule::new(1_000_000, 0, 12, 0, true),
                "Test".to_string(),
            )
            .unwrap();

        assert!(manager.get(&id).is_some());
        assert_eq!(manager.get_by_beneficiary(&beneficiary).len(), 1);

        let stats = manager.stats();
        assert_eq!(stats.total_contracts, 1);
        assert_eq!(stats.active_contracts, 1);
    }
}
