use serde::{Deserialize, Serialize};
use std::collections::HashMap;

/// Validator tier based on stake amount
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum ValidatorTier {
    /// Micro validator: 10 ONC minimum stake
    /// Perfect for Raspberry Pi Zero, old hardware
    Micro,

    /// Light validator: 100 ONC minimum stake
    /// Raspberry Pi 4, basic VPS
    Light,

    /// Standard validator: 1000 ONC minimum stake
    /// Desktop PC, decent VPS
    Standard,

    /// Power validator: 10000 ONC minimum stake
    /// Dedicated server, 24/7 operation
    Power,
}

impl ValidatorTier {
    /// Get minimum stake required for this tier
    pub fn min_stake(&self) -> u64 {
        match self {
            Self::Micro => 10,
            Self::Light => 100,
            Self::Standard => 1000,
            Self::Power => 10000,
        }
    }

    /// Get expected monthly return for this tier (approximate)
    pub fn expected_monthly_return(&self) -> f64 {
        match self {
            Self::Micro => 0.1,
            Self::Light => 1.0,
            Self::Standard => 10.0,
            Self::Power => 100.0,
        }
    }

    /// Determine tier from stake amount
    pub fn from_stake(stake: u64) -> Self {
        if stake >= 10000 {
            Self::Power
        } else if stake >= 1000 {
            Self::Standard
        } else if stake >= 100 {
            Self::Light
        } else {
            Self::Micro
        }
    }

    /// Get stake weight multiplier for this tier
    pub fn stake_multiplier(&self) -> f64 {
        match self {
            Self::Micro => 1.0,
            Self::Light => 1.1,    // 10% bonus
            Self::Standard => 1.2, // 20% bonus
            Self::Power => 1.3,    // 30% bonus
        }
    }
}

/// A validator in the OnionCoin network
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Validator {
    /// Validator's public key (also .onion identity)
    pub pubkey: [u8; 32],

    /// Staked amount
    pub stake: u64,

    /// Validator tier
    pub tier: ValidatorTier,

    /// When the stake was created (Unix timestamp)
    pub stake_time: i64,

    /// Lock period end time (Unix timestamp)
    pub unlock_time: i64,

    /// Onion address of the validator node
    pub onion_address: String,

    /// Is currently active?
    pub active: bool,

    /// Total blocks validated
    pub blocks_validated: u64,

    /// Total rewards earned
    pub total_rewards: u64,
}

impl Validator {
    /// Minimum lock period for stake (30 days)
    pub const LOCK_PERIOD_DAYS: i64 = 30;

    /// Create a new validator
    pub fn new(
        pubkey: [u8; 32],
        stake: u64,
        onion_address: String,
        current_time: i64,
    ) -> Result<Self, ValidatorError> {
        // Check minimum stake
        if stake < ValidatorTier::Micro.min_stake() {
            return Err(ValidatorError::InsufficientStake {
                provided: stake,
                minimum: ValidatorTier::Micro.min_stake(),
            });
        }

        let tier = ValidatorTier::from_stake(stake);
        let unlock_time = current_time + (Self::LOCK_PERIOD_DAYS * 24 * 3600);

        Ok(Self {
            pubkey,
            stake,
            tier,
            stake_time: current_time,
            unlock_time,
            onion_address,
            active: true,
            blocks_validated: 0,
            total_rewards: 0,
        })
    }

    /// Check if stake is locked
    pub fn is_locked(&self, current_time: i64) -> bool {
        current_time < self.unlock_time
    }

    /// Add stake to existing validator
    pub fn add_stake(&mut self, amount: u64, current_time: i64) {
        self.stake += amount;
        self.tier = ValidatorTier::from_stake(self.stake);

        // Extend lock period
        self.unlock_time = current_time + (Self::LOCK_PERIOD_DAYS * 24 * 3600);
    }

    /// Remove stake (only if unlocked)
    pub fn remove_stake(
        &mut self,
        amount: u64,
        current_time: i64,
    ) -> Result<(), ValidatorError> {
        if self.is_locked(current_time) {
            return Err(ValidatorError::StakeLocked {
                unlock_time: self.unlock_time,
            });
        }

        if amount > self.stake {
            return Err(ValidatorError::InsufficientStake {
                provided: self.stake,
                minimum: amount,
            });
        }

        self.stake -= amount;
        self.tier = ValidatorTier::from_stake(self.stake);

        // Deactivate if below minimum
        if self.stake < ValidatorTier::Micro.min_stake() {
            self.active = false;
        }

        Ok(())
    }

    /// Record a validated block
    pub fn record_block(&mut self, reward: u64) {
        self.blocks_validated += 1;
        self.total_rewards += reward;
    }

    /// Calculate ROI percentage
    pub fn roi_percentage(&self) -> f64 {
        if self.stake == 0 {
            0.0
        } else {
            (self.total_rewards as f64 / self.stake as f64) * 100.0
        }
    }
}

/// Registry of all validators in the network
#[derive(Debug, Default)]
pub struct ValidatorRegistry {
    validators: HashMap<[u8; 32], Validator>,
}

impl ValidatorRegistry {
    pub fn new() -> Self {
        Self {
            validators: HashMap::new(),
        }
    }

    /// Register a new validator
    pub fn register(&mut self, validator: Validator) -> Result<(), ValidatorError> {
        if self.validators.contains_key(&validator.pubkey) {
            return Err(ValidatorError::AlreadyRegistered);
        }

        self.validators.insert(validator.pubkey, validator);
        Ok(())
    }

    /// Get a validator by pubkey
    pub fn get(&self, pubkey: &[u8; 32]) -> Option<&Validator> {
        self.validators.get(pubkey)
    }

    /// Get a mutable validator by pubkey
    pub fn get_mut(&mut self, pubkey: &[u8; 32]) -> Option<&mut Validator> {
        self.validators.get_mut(pubkey)
    }

    /// Get all active validators
    pub fn get_active(&self) -> Vec<&Validator> {
        self.validators
            .values()
            .filter(|v| v.active)
            .collect()
    }

    /// Get validators by tier
    pub fn get_by_tier(&self, tier: ValidatorTier) -> Vec<&Validator> {
        self.validators
            .values()
            .filter(|v| v.tier == tier && v.active)
            .collect()
    }

    /// Total staked amount across all validators
    pub fn total_staked(&self) -> u64 {
        self.validators
            .values()
            .filter(|v| v.active)
            .map(|v| v.stake)
            .sum()
    }

    /// Count of active validators
    pub fn active_count(&self) -> usize {
        self.validators
            .values()
            .filter(|v| v.active)
            .count()
    }

    /// Count by tier
    pub fn count_by_tier(&self) -> HashMap<ValidatorTier, usize> {
        let mut counts = HashMap::new();

        for validator in self.validators.values() {
            if validator.active {
                *counts.entry(validator.tier).or_insert(0) += 1;
            }
        }

        counts
    }

    /// Deactivate validator
    pub fn deactivate(&mut self, pubkey: &[u8; 32]) -> Result<(), ValidatorError> {
        let validator = self.validators.get_mut(pubkey)
            .ok_or(ValidatorError::NotFound)?;

        validator.active = false;
        Ok(())
    }

    /// Remove validator (only if stake is 0)
    pub fn remove(&mut self, pubkey: &[u8; 32]) -> Result<(), ValidatorError> {
        let validator = self.validators.get(pubkey)
            .ok_or(ValidatorError::NotFound)?;

        if validator.stake > 0 {
            return Err(ValidatorError::CannotRemoveWithStake);
        }

        self.validators.remove(pubkey);
        Ok(())
    }
}

#[derive(Debug, thiserror::Error)]
pub enum ValidatorError {
    #[error("Insufficient stake: provided {provided}, minimum {minimum}")]
    InsufficientStake { provided: u64, minimum: u64 },

    #[error("Stake is locked until timestamp {unlock_time}")]
    StakeLocked { unlock_time: i64 },

    #[error("Validator already registered")]
    AlreadyRegistered,

    #[error("Validator not found")]
    NotFound,

    #[error("Cannot remove validator with non-zero stake")]
    CannotRemoveWithStake,
}

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

    #[test]
    fn test_validator_tiers() {
        assert_eq!(ValidatorTier::from_stake(10), ValidatorTier::Micro);
        assert_eq!(ValidatorTier::from_stake(100), ValidatorTier::Light);
        assert_eq!(ValidatorTier::from_stake(1000), ValidatorTier::Standard);
        assert_eq!(ValidatorTier::from_stake(10000), ValidatorTier::Power);
    }

    #[test]
    fn test_validator_creation() {
        let pubkey = [1u8; 32];
        let validator = Validator::new(
            pubkey,
            1000,
            "test.onion:9333".to_string(),
            0,
        ).unwrap();

        assert_eq!(validator.stake, 1000);
        assert_eq!(validator.tier, ValidatorTier::Standard);
        assert!(validator.active);
    }

    #[test]
    fn test_stake_locking() {
        let pubkey = [1u8; 32];
        let mut validator = Validator::new(
            pubkey,
            1000,
            "test.onion:9333".to_string(),
            0,
        ).unwrap();

        // Should be locked
        assert!(validator.is_locked(1000));

        // Should be unlocked after lock period
        let unlock_time = Validator::LOCK_PERIOD_DAYS * 24 * 3600;
        assert!(!validator.is_locked(unlock_time + 1));

        // Cannot remove stake while locked
        assert!(validator.remove_stake(100, 1000).is_err());

        // Can remove stake after unlock
        assert!(validator.remove_stake(100, unlock_time + 1).is_ok());
        assert_eq!(validator.stake, 900);
    }

    #[test]
    fn test_validator_registry() {
        let mut registry = ValidatorRegistry::new();

        let validator = Validator::new(
            [1u8; 32],
            1000,
            "test.onion:9333".to_string(),
            0,
        ).unwrap();

        registry.register(validator).unwrap();

        assert_eq!(registry.active_count(), 1);
        assert_eq!(registry.total_staked(), 1000);
    }

    #[test]
    fn test_validator_roi() {
        let mut validator = Validator::new(
            [1u8; 32],
            1000,
            "test.onion:9333".to_string(),
            0,
        ).unwrap();

        validator.record_block(5);
        validator.record_block(5);

        assert_eq!(validator.blocks_validated, 2);
        assert_eq!(validator.total_rewards, 10);
        assert_eq!(validator.roi_percentage(), 1.0); // 10/1000 = 1%
    }
}
