//! Block reward calculations for Synor.
//!
//! Synor uses a chromatic halving schedule similar to Kaspa,
//! with smooth reduction rather than sudden halvings.
//!
//! Initial reward: 500 SYNOR per block
//! Total supply: 70,000,000 SYNOR (fixed)

use synor_types::Amount;
use thiserror::Error;

/// Initial block reward in sompi (500 SYNOR).
pub const INITIAL_REWARD: u64 = 500 * Amount::ONE_SYNOR;

/// Halving interval in blocks (~1 year at 10 bps).
pub const HALVING_INTERVAL: u64 = 315_360_000; // ~1 year

/// Number of halvings until reward becomes zero.
pub const MAX_HALVINGS: u32 = 8;

/// Chromatic halving period (smooth reduction).
pub const CHROMATIC_PERIOD: u64 = 1_000_000; // ~27 hours

/// Block reward information.
#[derive(Clone, Debug)]
pub struct BlockReward {
    /// Base subsidy (before fees).
    pub subsidy: Amount,
    /// Total fees collected.
    pub fees: Amount,
    /// Total reward (subsidy + fees).
    pub total: Amount,
    /// Current emission epoch.
    pub epoch: u32,
}

impl BlockReward {
    /// Creates a new block reward.
    pub fn new(subsidy: Amount, fees: Amount) -> Self {
        BlockReward {
            subsidy,
            fees,
            total: subsidy.saturating_add(fees),
            epoch: 0,
        }
    }

    /// Creates a genesis reward.
    pub fn genesis() -> Self {
        BlockReward {
            subsidy: Amount::from_sompi(INITIAL_REWARD),
            fees: Amount::ZERO,
            total: Amount::from_sompi(INITIAL_REWARD),
            epoch: 0,
        }
    }
}

/// Reward calculator.
pub struct RewardCalculator {
    /// Initial reward in sompi.
    initial_reward: u64,
    /// Halving interval.
    halving_interval: u64,
    /// Use chromatic (smooth) halving.
    chromatic: bool,
}

impl RewardCalculator {
    /// Creates a new reward calculator.
    pub fn new() -> Self {
        RewardCalculator {
            initial_reward: INITIAL_REWARD,
            halving_interval: HALVING_INTERVAL,
            chromatic: true,
        }
    }

    /// Creates a calculator for testing.
    pub fn for_testing() -> Self {
        RewardCalculator {
            initial_reward: 500 * 100_000_000, // 500 SYNOR
            halving_interval: 1000,            // Quick halvings
            chromatic: true,
        }
    }

    /// Calculates the block subsidy for a given DAA score.
    pub fn calculate_subsidy(&self, daa_score: u64) -> Amount {
        if self.chromatic {
            self.calculate_chromatic_subsidy(daa_score)
        } else {
            self.calculate_standard_subsidy(daa_score)
        }
    }

    /// Standard halving (like Bitcoin).
    fn calculate_standard_subsidy(&self, daa_score: u64) -> Amount {
        let halvings = (daa_score / self.halving_interval) as u32;

        if halvings >= MAX_HALVINGS {
            return Amount::ZERO;
        }

        let reward = self.initial_reward >> halvings;
        Amount::from_sompi(reward)
    }

    /// Chromatic halving (smooth reduction like Kaspa).
    fn calculate_chromatic_subsidy(&self, daa_score: u64) -> Amount {
        // Chromatic halving uses a smoother curve
        // Reward = initial * (1/2)^(daa_score / halving_interval)
        // For efficiency, we approximate this

        let halvings = daa_score / self.halving_interval;
        let position_in_epoch = daa_score % self.halving_interval;

        if halvings as u32 >= MAX_HALVINGS {
            return Amount::ZERO;
        }

        // Base reward at this epoch
        let base_reward = self.initial_reward >> halvings;

        // Chromatic adjustment within epoch
        // Linearly decrease toward next halving
        let next_reward = base_reward / 2;
        let epoch_progress = position_in_epoch as f64 / self.halving_interval as f64;

        // Smooth interpolation
        let adjusted = base_reward as f64 - (base_reward - next_reward) as f64 * epoch_progress;

        Amount::from_sompi(adjusted as u64)
    }

    /// Calculates total reward (subsidy + fees).
    pub fn calculate_reward(&self, daa_score: u64, fees: Amount) -> BlockReward {
        let subsidy = self.calculate_subsidy(daa_score);
        let halvings = (daa_score / self.halving_interval) as u32;

        BlockReward {
            subsidy,
            fees,
            total: subsidy.saturating_add(fees),
            epoch: halvings.min(MAX_HALVINGS),
        }
    }

    /// Estimates total supply at a given DAA score.
    pub fn estimate_supply_at(&self, daa_score: u64) -> Amount {
        // This is an approximation
        let mut total = 0u64;
        let mut current_score = 0u64;

        while current_score < daa_score {
            let subsidy = self.calculate_subsidy(current_score);
            if subsidy == Amount::ZERO {
                break;
            }

            // Add rewards for a chunk of blocks
            let chunk_size = (daa_score - current_score).min(10000);
            total = total.saturating_add(subsidy.as_sompi() * chunk_size);
            current_score += chunk_size;
        }

        Amount::from_sompi(total)
    }

    /// Returns the current epoch (halving count).
    pub fn get_epoch(&self, daa_score: u64) -> u32 {
        ((daa_score / self.halving_interval) as u32).min(MAX_HALVINGS)
    }

    /// Returns the DAA score at which rewards end.
    pub fn emission_end_score(&self) -> u64 {
        self.halving_interval * MAX_HALVINGS as u64
    }

    /// Returns the initial reward.
    pub fn initial_reward(&self) -> Amount {
        Amount::from_sompi(self.initial_reward)
    }
}

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

impl std::fmt::Debug for RewardCalculator {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("RewardCalculator")
            .field("initial_reward", &Amount::from_sompi(self.initial_reward))
            .field("halving_interval", &self.halving_interval)
            .field("chromatic", &self.chromatic)
            .finish()
    }
}

/// Fee distribution for block rewards.
#[derive(Clone, Debug)]
pub struct FeeDistribution {
    /// Amount to burn (deflationary).
    pub burn: Amount,
    /// Amount to stakers.
    pub stakers: Amount,
    /// Amount to community pool.
    pub community: Amount,
    /// Amount to miner/validator.
    pub miner: Amount,
}

impl FeeDistribution {
    /// Distributes fees according to Synor's tokenomics.
    pub fn distribute(fees: Amount) -> Self {
        // 10% burn, 60% stakers, 20% community, 10% miner
        let fee_sompi = fees.as_sompi();
        let burn = Amount::from_sompi(fee_sompi * 10 / 100);
        let stakers = Amount::from_sompi(fee_sompi * 60 / 100);
        let community = Amount::from_sompi(fee_sompi * 20 / 100);
        let miner = fees
            .saturating_sub(burn)
            .saturating_sub(stakers)
            .saturating_sub(community);

        FeeDistribution {
            burn,
            stakers,
            community,
            miner,
        }
    }
}

/// Errors related to rewards.
#[derive(Debug, Error)]
pub enum RewardError {
    #[error("Invalid DAA score")]
    InvalidDaaScore,

    #[error("Reward overflow")]
    Overflow,
}

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

    #[test]
    fn test_initial_reward() {
        let calc = RewardCalculator::new();
        let reward = calc.calculate_subsidy(0);
        assert_eq!(reward, Amount::from_synor(500));
    }

    #[test]
    fn test_reward_decreases() {
        let calc = RewardCalculator::for_testing();

        let reward_0 = calc.calculate_subsidy(0);
        let reward_1000 = calc.calculate_subsidy(1000);

        // After one halving, reward should be about half
        assert!(reward_1000 < reward_0);
        assert!(reward_1000 >= Amount::from_synor(200)); // Roughly half of 500
    }

    #[test]
    fn test_chromatic_smooth() {
        let calc = RewardCalculator::for_testing();

        // Rewards should decrease smoothly within an epoch
        let reward_0 = calc.calculate_subsidy(0);
        let reward_500 = calc.calculate_subsidy(500); // Halfway through epoch
        let reward_999 = calc.calculate_subsidy(999); // End of epoch

        assert!(reward_0 > reward_500);
        assert!(reward_500 > reward_999);
    }

    #[test]
    fn test_max_halvings() {
        let calc = RewardCalculator::for_testing();

        // After max halvings, reward should be zero
        let end_score = calc.emission_end_score();
        let reward = calc.calculate_subsidy(end_score + 1000);

        assert_eq!(reward, Amount::ZERO);
    }

    #[test]
    fn test_block_reward() {
        let calc = RewardCalculator::new();
        let fees = Amount::from_synor(1);

        let reward = calc.calculate_reward(0, fees);

        assert_eq!(reward.subsidy, Amount::from_synor(500));
        assert_eq!(reward.fees, Amount::from_synor(1));
        assert_eq!(reward.total, Amount::from_synor(501));
        assert_eq!(reward.epoch, 0);
    }

    #[test]
    fn test_fee_distribution() {
        let fees = Amount::from_synor(100);
        let dist = FeeDistribution::distribute(fees);

        // Verify distribution adds up
        let total = dist
            .burn
            .saturating_add(dist.stakers)
            .saturating_add(dist.community)
            .saturating_add(dist.miner);

        assert_eq!(total, fees);

        // Verify percentages
        assert_eq!(dist.burn, Amount::from_synor(10));
        assert_eq!(dist.stakers, Amount::from_synor(60));
        assert_eq!(dist.community, Amount::from_synor(20));
        assert_eq!(dist.miner, Amount::from_synor(10));
    }

    #[test]
    fn test_get_epoch() {
        let calc = RewardCalculator::for_testing();

        assert_eq!(calc.get_epoch(0), 0);
        assert_eq!(calc.get_epoch(500), 0);
        assert_eq!(calc.get_epoch(1000), 1);
        assert_eq!(calc.get_epoch(2000), 2);
    }

    #[test]
    fn test_genesis_reward() {
        let reward = BlockReward::genesis();

        assert_eq!(reward.subsidy, Amount::from_synor(500));
        assert_eq!(reward.fees, Amount::ZERO);
        assert_eq!(reward.epoch, 0);
    }
}
