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

/// Genesis mining: fair launch system for OnionCoin
/// Phase 1 (first 6 months): Earn ONC by running Tor relays
/// No pre-mine, no ICO, completely fair distribution
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GenesisMining {
    /// Genesis phase configuration
    config: GenesisConfig,

    /// Registered relay nodes
    relay_nodes: HashMap<[u8; 32], RelayNode>,

    /// Start time of genesis phase
    start_time: i64,

    /// Total ONC distributed so far
    total_distributed: u64,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GenesisConfig {
    /// Duration of genesis phase (seconds)
    /// Default: 6 months = 15,552,000 seconds
    pub duration_seconds: i64,

    /// Total ONC to distribute in genesis phase
    /// Default: 1,000,000 ONC (fair initial distribution)
    pub total_genesis_supply: u64,

    /// Minimum relay activity to qualify (bytes)
    /// Default: 1 GB
    pub min_relay_bytes: u64,

    /// Bonus multiplier for early participants
    /// First month: 2x, decreases linearly to 1x
    pub early_bird_multiplier: bool,
}

impl Default for GenesisConfig {
    fn default() -> Self {
        Self {
            duration_seconds: 6 * 30 * 24 * 3600, // ~6 months
            total_genesis_supply: 1_000_000,       // 1M ONC
            min_relay_bytes: 1024 * 1024 * 1024,   // 1 GB
            early_bird_multiplier: true,
        }
    }
}

impl GenesisMining {
    pub fn new(config: GenesisConfig, start_time: i64) -> Self {
        Self {
            config,
            relay_nodes: HashMap::new(),
            start_time,
            total_distributed: 0,
        }
    }

    /// Register a relay node for genesis mining
    pub fn register_relay(&mut self, pubkey: [u8; 32], onion_address: String, current_time: i64) {
        if !self.is_active(current_time) {
            return;
        }

        self.relay_nodes.entry(pubkey).or_insert_with(|| RelayNode {
            pubkey,
            onion_address,
            registration_time: current_time,
            total_bytes_relayed: 0,
            total_uptime_seconds: 0,
            rewards_earned: 0,
        });
    }

    /// Record relay activity
    pub fn record_relay_activity(
        &mut self,
        pubkey: &[u8; 32],
        bytes_relayed: u64,
        uptime_seconds: u64,
    ) {
        if let Some(node) = self.relay_nodes.get_mut(pubkey) {
            node.total_bytes_relayed += bytes_relayed;
            node.total_uptime_seconds += uptime_seconds;
        }
    }

    /// Calculate rewards for a relay node
    pub fn calculate_rewards(&self, pubkey: &[u8; 32], current_time: i64) -> u64 {
        let node = match self.relay_nodes.get(pubkey) {
            Some(n) => n,
            None => return 0,
        };

        // Must meet minimum requirement
        if node.total_bytes_relayed < self.config.min_relay_bytes {
            return 0;
        }

        // Base reward proportional to relay work
        let total_relay_work: u64 = self
            .relay_nodes
            .values()
            .map(|n| n.total_bytes_relayed)
            .sum();

        if total_relay_work == 0 {
            return 0;
        }

        let base_reward = (self.config.total_genesis_supply as f64
            * (node.total_bytes_relayed as f64 / total_relay_work as f64))
            as u64;

        // Early bird bonus
        let multiplier = if self.config.early_bird_multiplier {
            self.early_bird_multiplier(node.registration_time, current_time)
        } else {
            1.0
        };

        // Uptime bonus (up to 20% extra)
        let uptime_bonus = self.uptime_bonus(node);

        ((base_reward as f64) * multiplier * (1.0 + uptime_bonus)) as u64
    }

    /// Early bird multiplier: 2x for first month, linear decay to 1x
    fn early_bird_multiplier(&self, registration_time: i64, _current_time: i64) -> f64 {
        let elapsed_since_genesis = registration_time - self.start_time;
        let one_month = 30 * 24 * 3600;

        if elapsed_since_genesis < 0 {
            return 2.0; // Registered before/at genesis
        }

        if elapsed_since_genesis >= one_month {
            return 1.0; // After first month
        }

        // Linear decay: 2.0 -> 1.0 over first month
        2.0 - (elapsed_since_genesis as f64 / one_month as f64)
    }

    /// Uptime bonus: up to 20% for 99%+ uptime
    fn uptime_bonus(&self, node: &RelayNode) -> f64 {
        let total_possible_uptime = chrono::Utc::now().timestamp() - node.registration_time;

        if total_possible_uptime <= 0 {
            return 0.0;
        }

        let uptime_ratio = node.total_uptime_seconds as f64 / total_possible_uptime as f64;

        if uptime_ratio >= 0.99 {
            0.20 // 20% bonus
        } else if uptime_ratio >= 0.95 {
            0.10 // 10% bonus
        } else if uptime_ratio >= 0.90 {
            0.05 // 5% bonus
        } else {
            0.0
        }
    }

    /// Distribute rewards to all qualifying nodes
    pub fn distribute_rewards(&mut self, current_time: i64) -> HashMap<[u8; 32], u64> {
        let mut rewards = HashMap::new();

        for pubkey in self.relay_nodes.keys().copied().collect::<Vec<_>>() {
            let reward = self.calculate_rewards(&pubkey, current_time);

            if reward > 0 {
                if let Some(node) = self.relay_nodes.get_mut(&pubkey) {
                    node.rewards_earned = reward;
                    self.total_distributed += reward;
                }
                rewards.insert(pubkey, reward);
            }
        }

        rewards
    }

    /// Check if genesis phase is still active
    pub fn is_active(&self, current_time: i64) -> bool {
        let elapsed = current_time - self.start_time;
        elapsed >= 0 && elapsed < self.config.duration_seconds
    }

    /// Get phase progress (0.0 - 1.0)
    pub fn progress(&self, current_time: i64) -> f64 {
        let elapsed = (current_time - self.start_time).max(0);
        let progress = elapsed as f64 / self.config.duration_seconds as f64;
        progress.clamp(0.0, 1.0)
    }

    /// Get leaderboard of top relay nodes
    pub fn leaderboard(&self, limit: usize) -> Vec<(&RelayNode, u64)> {
        let mut nodes: Vec<_> = self
            .relay_nodes
            .values()
            .map(|node| {
                let reward = self.calculate_rewards(&node.pubkey, chrono::Utc::now().timestamp());
                (node, reward)
            })
            .collect();

        nodes.sort_by(|a, b| b.1.cmp(&a.1));
        nodes.into_iter().take(limit).collect()
    }

    /// Get statistics
    pub fn stats(&self) -> GenesisStats {
        GenesisStats {
            total_relay_nodes: self.relay_nodes.len(),
            total_bytes_relayed: self.relay_nodes.values().map(|n| n.total_bytes_relayed).sum(),
            total_distributed: self.total_distributed,
            remaining_supply: self.config.total_genesis_supply.saturating_sub(self.total_distributed),
        }
    }
}

/// A relay node participating in genesis mining
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RelayNode {
    pub pubkey: [u8; 32],
    pub onion_address: String,
    pub registration_time: i64,
    pub total_bytes_relayed: u64,
    pub total_uptime_seconds: u64,
    pub rewards_earned: u64,
}

/// Statistics about genesis mining
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GenesisStats {
    pub total_relay_nodes: usize,
    pub total_bytes_relayed: u64,
    pub total_distributed: u64,
    pub remaining_supply: u64,
}

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

    #[test]
    fn test_genesis_mining_registration() {
        let config = GenesisConfig::default();
        let mut genesis = GenesisMining::new(config, 0);

        genesis.register_relay([1u8; 32], "test1.onion".to_string(), 0);
        genesis.register_relay([2u8; 32], "test2.onion".to_string(), 100);

        assert_eq!(genesis.relay_nodes.len(), 2);
    }

    #[test]
    fn test_relay_activity_recording() {
        let config = GenesisConfig::default();
        let mut genesis = GenesisMining::new(config, 0);

        genesis.register_relay([1u8; 32], "test.onion".to_string(), 0);
        genesis.record_relay_activity(&[1u8; 32], 1_000_000_000, 3600);

        let node = genesis.relay_nodes.get(&[1u8; 32]).unwrap();
        assert_eq!(node.total_bytes_relayed, 1_000_000_000);
        assert_eq!(node.total_uptime_seconds, 3600);
    }

    #[test]
    fn test_reward_calculation() {
        let config = GenesisConfig::default();
        let mut genesis = GenesisMining::new(config, 0);

        // Node 1: 50% of relay work
        genesis.register_relay([1u8; 32], "test1.onion".to_string(), 0);
        genesis.record_relay_activity(&[1u8; 32], 50 * 1024 * 1024 * 1024, 86400);

        // Node 2: 50% of relay work
        genesis.register_relay([2u8; 32], "test2.onion".to_string(), 0);
        genesis.record_relay_activity(&[2u8; 32], 50 * 1024 * 1024 * 1024, 86400);

        let reward1 = genesis.calculate_rewards(&[1u8; 32], 100);
        let reward2 = genesis.calculate_rewards(&[2u8; 32], 100);

        // Should get roughly equal rewards (both ~50% of total)
        assert!(reward1 > 400_000 && reward1 < 600_000);
        assert!(reward2 > 400_000 && reward2 < 600_000);
    }

    #[test]
    fn test_early_bird_bonus() {
        let config = GenesisConfig::default();
        let genesis = GenesisMining::new(config, 0);

        // Registered at genesis
        let multiplier_early = genesis.early_bird_multiplier(0, 100);
        assert_eq!(multiplier_early, 2.0);

        // Registered after 1 month
        let one_month = 30 * 24 * 3600;
        let multiplier_late = genesis.early_bird_multiplier(one_month, one_month + 100);
        assert_eq!(multiplier_late, 1.0);

        // Registered mid-month
        let multiplier_mid = genesis.early_bird_multiplier(one_month / 2, one_month / 2 + 100);
        assert!(multiplier_mid > 1.0 && multiplier_mid < 2.0);
    }

    #[test]
    fn test_minimum_relay_requirement() {
        let config = GenesisConfig::default();
        let mut genesis = GenesisMining::new(config, 0);

        genesis.register_relay([1u8; 32], "test.onion".to_string(), 0);

        // Below minimum
        genesis.record_relay_activity(&[1u8; 32], 1_000_000, 3600);
        let reward = genesis.calculate_rewards(&[1u8; 32], 100);
        assert_eq!(reward, 0);

        // Above minimum
        genesis.record_relay_activity(&[1u8; 32], 2 * 1024 * 1024 * 1024, 3600);
        let reward = genesis.calculate_rewards(&[1u8; 32], 100);
        assert!(reward > 0);
    }

    #[test]
    fn test_phase_progress() {
        let config = GenesisConfig::default();
        let genesis = GenesisMining::new(config.clone(), 0);

        assert_eq!(genesis.progress(0), 0.0);

        let half_duration = config.duration_seconds / 2;
        let mid_progress = genesis.progress(half_duration);
        assert!((mid_progress - 0.5).abs() < 0.01);

        let end_progress = genesis.progress(config.duration_seconds);
        assert_eq!(end_progress, 1.0);
    }

    #[test]
    fn test_leaderboard() {
        let config = GenesisConfig::default();
        let mut genesis = GenesisMining::new(config, 0);

        // Register 3 nodes with different relay amounts
        genesis.register_relay([1u8; 32], "test1.onion".to_string(), 0);
        genesis.record_relay_activity(&[1u8; 32], 100 * 1024 * 1024 * 1024, 86400);

        genesis.register_relay([2u8; 32], "test2.onion".to_string(), 0);
        genesis.record_relay_activity(&[2u8; 32], 50 * 1024 * 1024 * 1024, 86400);

        genesis.register_relay([3u8; 32], "test3.onion".to_string(), 0);
        genesis.record_relay_activity(&[3u8; 32], 25 * 1024 * 1024 * 1024, 86400);

        let leaderboard = genesis.leaderboard(3);

        assert_eq!(leaderboard.len(), 3);
        assert_eq!(leaderboard[0].0.pubkey, [1u8; 32]); // Top relay
        assert!(leaderboard[0].1 > leaderboard[1].1); // Top has higher reward
    }
}
