use crate::{Validator, RelayProof, BandwidthMetrics, UptimeMetrics, StorageMetrics};
use serde::{Deserialize, Serialize};

/// Proof-of-Contribution: OnionCoin's unique consensus mechanism
/// Combines stake + relay work + bandwidth + uptime + storage
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProofOfContribution {
    /// Validator public key
    pub validator_pubkey: [u8; 32],

    /// Contribution score breakdown
    pub score: ContributionScore,

    /// Timestamp
    pub timestamp: i64,
}

impl ProofOfContribution {
    /// Calculate contribution score for a validator
    pub fn calculate(
        validator: &Validator,
        relay_proof: Option<&RelayProof>,
        bandwidth: &BandwidthMetrics,
        uptime: &UptimeMetrics,
        storage: &StorageMetrics,
    ) -> Self {
        let score = ContributionScore::calculate(
            validator,
            relay_proof,
            bandwidth,
            uptime,
            storage,
        );

        Self {
            validator_pubkey: validator.pubkey,
            score,
            timestamp: chrono::Utc::now().timestamp(),
        }
    }

    /// Get total weighted score
    pub fn total_score(&self) -> f64 {
        self.score.weighted_total()
    }
}

/// Breakdown of contribution score components
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ContributionScore {
    /// Stake component score (0.0 - 1.0)
    pub stake_score: f64,

    /// Relay work score (0.0 - 1.0)
    pub relay_score: f64,

    /// Bandwidth score (0.0 - 1.0)
    pub bandwidth_score: f64,

    /// Uptime score (0.0 - 1.0)
    pub uptime_score: f64,

    /// Storage score (0.0 - 1.0)
    pub storage_score: f64,

    /// Weights for each component
    pub weights: ScoreWeights,
}

/// Weights for different contribution components
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ScoreWeights {
    pub stake: f64,
    pub relay: f64,
    pub bandwidth: f64,
    pub uptime: f64,
    pub storage: f64,
}

impl Default for ScoreWeights {
    fn default() -> Self {
        Self {
            stake: 0.40,     // 40% - Still important but not dominant
            relay: 0.30,     // 30% - UNIQUE: Tor relay work
            bandwidth: 0.15, // 15% - Network contribution
            uptime: 0.10,    // 10% - Reliability
            storage: 0.05,   // 5% - Data availability
        }
    }
}

impl ScoreWeights {
    /// Verify weights sum to 1.0
    pub fn is_valid(&self) -> bool {
        let sum = self.stake + self.relay + self.bandwidth + self.uptime + self.storage;
        (sum - 1.0).abs() < 0.001
    }
}

impl ContributionScore {
    /// Calculate contribution score from validator metrics
    pub fn calculate(
        validator: &Validator,
        relay_proof: Option<&RelayProof>,
        bandwidth: &BandwidthMetrics,
        uptime: &UptimeMetrics,
        storage: &StorageMetrics,
    ) -> Self {
        let weights = ScoreWeights::default();

        // 1. Stake score (logarithmic to reduce whale advantage)
        let stake_score = Self::calculate_stake_score(validator);

        // 2. Relay score (from Proof-of-Relay)
        let relay_score = Self::calculate_relay_score(relay_proof);

        // 3. Bandwidth score
        let bandwidth_score = bandwidth.score();

        // 4. Uptime score
        let uptime_score = uptime.score();

        // 5. Storage score
        let storage_score = storage.score();

        Self {
            stake_score,
            relay_score,
            bandwidth_score,
            uptime_score,
            storage_score,
            weights,
        }
    }

    /// Calculate stake score (logarithmic)
    /// This prevents whales from dominating
    fn calculate_stake_score(validator: &Validator) -> f64 {
        if validator.stake == 0 {
            return 0.0;
        }

        // Use log scale to reduce advantage of large stakes
        // 10 ONC = 0.1, 100 ONC = 0.2, 1000 ONC = 0.3, etc.
        let log_stake = (validator.stake as f64).log10();
        let max_log_stake = 5.0; // 100,000 ONC = max score

        // Apply tier multiplier
        let tier_multiplier = validator.tier.stake_multiplier();

        ((log_stake / max_log_stake) * tier_multiplier).clamp(0.0, 1.0)
    }

    /// Calculate relay score from relay proof
    fn calculate_relay_score(relay_proof: Option<&RelayProof>) -> f64 {
        match relay_proof {
            Some(proof) => {
                // Base score from bytes relayed
                let gb_relayed = proof.metrics.bytes_relayed as f64 / (1024.0 * 1024.0 * 1024.0);
                let base_score = (gb_relayed / 100.0).clamp(0.0, 0.8); // Max 0.8 from volume

                // Quality multiplier
                let quality = proof.metrics.quality_score();

                // Circuit diversity bonus
                let diversity_bonus = if proof.metrics.unique_circuits > 100 {
                    0.2
                } else if proof.metrics.unique_circuits > 50 {
                    0.1
                } else {
                    0.0
                };

                (base_score * quality + diversity_bonus).clamp(0.0, 1.0)
            }
            None => 0.0,
        }
    }

    /// Calculate weighted total score
    pub fn weighted_total(&self) -> f64 {
        self.stake_score * self.weights.stake
            + self.relay_score * self.weights.relay
            + self.bandwidth_score * self.weights.bandwidth
            + self.uptime_score * self.weights.uptime
            + self.storage_score * self.weights.storage
    }

    /// Get human-readable breakdown
    pub fn breakdown(&self) -> String {
        format!(
            "Stake: {:.2}×{:.0}% = {:.3}\n\
             Relay: {:.2}×{:.0}% = {:.3}\n\
             Bandwidth: {:.2}×{:.0}% = {:.3}\n\
             Uptime: {:.2}×{:.0}% = {:.3}\n\
             Storage: {:.2}×{:.0}% = {:.3}\n\
             ───────────────────────\n\
             TOTAL: {:.3}",
            self.stake_score,
            self.weights.stake * 100.0,
            self.stake_score * self.weights.stake,
            self.relay_score,
            self.weights.relay * 100.0,
            self.relay_score * self.weights.relay,
            self.bandwidth_score,
            self.weights.bandwidth * 100.0,
            self.bandwidth_score * self.weights.bandwidth,
            self.uptime_score,
            self.weights.uptime * 100.0,
            self.uptime_score * self.weights.uptime,
            self.storage_score,
            self.weights.storage * 100.0,
            self.storage_score * self.weights.storage,
            self.weighted_total()
        )
    }
}

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

    #[test]
    fn test_score_weights_valid() {
        let weights = ScoreWeights::default();
        assert!(weights.is_valid());
    }

    #[test]
    fn test_stake_score_logarithmic() {
        let validator_small = create_test_validator(100); // 100 ONC
        let validator_large = create_test_validator(10000); // 10,000 ONC

        let score_small = ContributionScore::calculate_stake_score(&validator_small);
        let score_large = ContributionScore::calculate_stake_score(&validator_large);

        // Large stake should have higher score but not 100x higher
        assert!(score_large > score_small);
        assert!(score_large < score_small * 2.0); // Logarithmic dampening
    }

    #[test]
    fn test_relay_score() {
        let mut metrics = RelayMetrics::new();
        metrics.bytes_relayed = 50 * 1024 * 1024 * 1024; // 50 GB
        metrics.unique_circuits = 150;
        metrics.packets_relayed = 1_000_000;

        let proof = RelayProof::new([1u8; 32], 0, 3600, metrics);
        let score = ContributionScore::calculate_relay_score(Some(&proof));

        assert!(score > 0.0);
        assert!(score <= 1.0);
    }

    #[test]
    fn test_contribution_score_calculation() {
        let validator = create_test_validator(1000);

        let mut relay_metrics = RelayMetrics::new();
        relay_metrics.bytes_relayed = 10 * 1024 * 1024 * 1024; // 10 GB
        relay_metrics.unique_circuits = 50;
        relay_metrics.packets_relayed = 100_000;

        let relay_proof = RelayProof::new([1u8; 32], 0, 3600, relay_metrics);

        let mut bandwidth = BandwidthMetrics::new();
        bandwidth.update(1_000_000, 1_000_000, 100);

        let uptime = UptimeMetrics::new(0);

        let mut storage = StorageMetrics::new();
        storage.update(20 * 1024 * 1024 * 1024, 10 * 1024 * 1024 * 1024, 1000, 10000);

        let score = ContributionScore::calculate(
            &validator,
            Some(&relay_proof),
            &bandwidth,
            &uptime,
            &storage,
        );

        let total = score.weighted_total();
        assert!(total > 0.0);
        assert!(total <= 1.0);

        println!("{}", score.breakdown());
    }

    #[test]
    fn test_micro_validator_can_compete() {
        // Micro validator with great relay work
        let micro = create_test_validator(10); // Only 10 ONC
        let mut relay_metrics = RelayMetrics::new();
        relay_metrics.bytes_relayed = 100 * 1024 * 1024 * 1024; // 100 GB relay!
        relay_metrics.unique_circuits = 200;
        relay_metrics.packets_relayed = 1_000_000;

        let relay_proof = RelayProof::new([1u8; 32], 0, 3600, relay_metrics);

        let mut bandwidth = BandwidthMetrics::new();
        bandwidth.update(10_000_000, 10_000_000, 100); // Good bandwidth

        let mut uptime = UptimeMetrics::new(0);
        for i in 1..=1000 {
            uptime.record_heartbeat(i * 60); // Great uptime
        }

        let storage = StorageMetrics::new();

        let micro_score = ContributionScore::calculate(
            &micro,
            Some(&relay_proof),
            &bandwidth,
            &uptime,
            &storage,
        );

        // Power validator with just stake
        let power = create_test_validator(10000); // 10,000 ONC
        let power_score = ContributionScore::calculate(
            &power,
            None, // No relay work
            &BandwidthMetrics::new(),
            &UptimeMetrics::new(0),
            &StorageMetrics::new(),
        );

        println!("Micro validator (10 ONC + relay work):\n{}", micro_score.breakdown());
        println!("\nPower validator (10,000 ONC, no work):\n{}", power_score.breakdown());

        // Micro validator should be competitive!
        assert!(micro_score.weighted_total() > power_score.weighted_total() * 0.5);
    }

    fn create_test_validator(stake: u64) -> Validator {
        Validator::new([1u8; 32], stake, "test.onion".to_string(), 0).unwrap()
    }
}
