// Copyright (c) Subzero Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

//! A temporary crate that contains minimal timing configuration for Solana compatability.
//! It replaces poh-config but needs to be replaced soon.
//!
//! The values are pre-calculated from Solana's default PoH parameters to
//! maintain compatibility while reducing dependencies.

/// Solana-compatible timing constants
pub const SECONDS_PER_YEAR: f64 = 365.242_199 * 24.0 * 60.0 * 60.0; // 31_556_925.216

/// Default timing configuration that matches Solana's PoH defaults
const DEFAULT_TICKS_PER_SECOND: u64 = 160;
const DEFAULT_TICKS_PER_SLOT: u64 = 64;
const DEFAULT_TICK_DURATION_NANOS: u128 = 6_250_000; // 6250 microseconds

/// Pre-calculated derived values from Solana defaults:
/// - tick_duration = 6250 microseconds (1_000_000 / 160 ticks per second)
/// - ns_per_slot = tick_duration * ticks_per_slot = 6_250_000 * 64 = 400_000_000 nanos
/// - slots_per_year = SECONDS_PER_YEAR * ticks_per_second / ticks_per_slot = 31_556_925.216 * 160 / 64
const DEFAULT_NS_PER_SLOT: u128 = DEFAULT_TICK_DURATION_NANOS * DEFAULT_TICKS_PER_SLOT as u128;
const DEFAULT_SLOTS_PER_YEAR: f64 =
    SECONDS_PER_YEAR * DEFAULT_TICKS_PER_SECOND as f64 / DEFAULT_TICKS_PER_SLOT as f64;

/// Minimal timing configuration containing only the essential values needed by Rialo.
///
/// This replaces dependence on Solana's PohConfig while maintaining compatibility
/// with the timing calculations used throughout the system.
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[derive(Clone, Debug, PartialEq)]
pub struct RialoTimingConfig {
    /// Nanoseconds per slot - used for Bank::clock() timestamp calculations
    pub ns_per_slot: u128,

    /// Slots per year - used for RentCollector rent duration calculations
    pub slots_per_year: f64,

    /// Ticks per slot - used for some slot-to-tick conversions
    pub ticks_per_slot: u64,
}

impl Default for RialoTimingConfig {
    /// Create timing config that exactly matches Solana's default PoH configuration.
    ///
    /// Values calculated from:
    /// - 160 ticks per second
    /// - 64 ticks per slot
    /// - 6250 microseconds per tick
    fn default() -> Self {
        Self {
            ns_per_slot: DEFAULT_NS_PER_SLOT,       // 400_000_000 nanoseconds
            slots_per_year: DEFAULT_SLOTS_PER_YEAR, // ~78_892_313 slots
            ticks_per_slot: DEFAULT_TICKS_PER_SLOT, // 64 ticks
        }
    }
}

impl RialoTimingConfig {
    /// Create custom timing config from tick duration and ticks per slot.
    ///
    /// This provides the same customization capability as PohConfig while maintaining
    /// the pre-calculated derived values needed by the system.
    pub fn from_tick_duration(tick_duration_nanos: u128, ticks_per_slot: u64) -> Self {
        let ns_per_slot = tick_duration_nanos * ticks_per_slot as u128;
        let ticks_per_second = (1_000_000_000_u128)
            .checked_div(tick_duration_nanos)
            .unwrap_or(0);
        let slots_per_year = if ticks_per_slot > 0 {
            SECONDS_PER_YEAR * ticks_per_second as f64 / ticks_per_slot as f64
        } else {
            0.0
        };

        Self {
            ns_per_slot,
            slots_per_year,
            ticks_per_slot,
        }
    }

    /// Create timing config equivalent to program-test's fast configuration.
    ///
    /// Uses 100 microsecond tick duration to match program-test setup.
    pub fn program_test() -> Self {
        Self::from_tick_duration(100_000, DEFAULT_TICKS_PER_SLOT) // 100 microseconds per tick
    }

    /// Calculate nanoseconds per tick.
    pub fn ns_per_tick(&self) -> u128 {
        if self.ticks_per_slot != 0 {
            self.ns_per_slot / self.ticks_per_slot as u128
        } else {
            0
        }
    }
}

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

    #[test]
    fn test_default_values() {
        let config = RialoTimingConfig::default();

        // Verify the calculated constants match expected Solana values
        assert_eq!(config.ns_per_slot, 400_000_000);
        assert_eq!(config.ticks_per_slot, 64);

        // slots_per_year should be approximately 78.89 million
        assert!((config.slots_per_year - 78_892_314.984).abs() < 0.01);
    }

    #[test]
    fn test_constants() {
        // Verify our pre-calculated constants are correct
        assert_eq!(SECONDS_PER_YEAR, 365.242_199 * 24.0 * 60.0 * 60.0);
        assert_eq!(DEFAULT_TICKS_PER_SECOND, 160);
        assert_eq!(DEFAULT_TICKS_PER_SLOT, 64);
        assert_eq!(DEFAULT_TICK_DURATION_NANOS, 6_250_000);

        // Verify derived calculations
        assert_eq!(DEFAULT_NS_PER_SLOT, 6_250_000 * 64);
        assert_eq!(DEFAULT_NS_PER_SLOT, 400_000_000);

        let expected_slots_per_year = SECONDS_PER_YEAR * 160.0 / 64.0;
        assert!((DEFAULT_SLOTS_PER_YEAR - expected_slots_per_year).abs() < 0.001);
    }

    #[test]
    fn test_from_tick_duration() {
        // Test with default Solana values
        let config = RialoTimingConfig::from_tick_duration(6_250_000, 64);
        let default_config = RialoTimingConfig::default();

        assert_eq!(config.ns_per_slot, default_config.ns_per_slot);
        assert_eq!(config.ticks_per_slot, default_config.ticks_per_slot);
        assert!((config.slots_per_year - default_config.slots_per_year).abs() < 1.0);

        // Test with program-test values (100 microseconds per tick)
        let fast_config = RialoTimingConfig::from_tick_duration(100_000, 64);
        assert_eq!(fast_config.ns_per_slot, 6_400_000); // 100_000 * 64
        assert_eq!(fast_config.ticks_per_slot, 64);
        // 10,000 ticks per second (1_000_000_000 / 100_000)
        // 156.25 slots per second (10,000 / 64)
        // ~4,930,769,686 slots per year
        assert!((fast_config.slots_per_year - 4_930_769_686.5).abs() < 1.0);
    }

    #[test]
    fn test_from_tick_duration_edge_cases() {
        // Test zero tick duration
        let zero_tick_config = RialoTimingConfig::from_tick_duration(0, 64);
        assert_eq!(zero_tick_config.ns_per_slot, 0);
        assert_eq!(zero_tick_config.ticks_per_slot, 64);
        assert_eq!(zero_tick_config.slots_per_year, 0.0);

        // Test zero ticks per slot
        let zero_slot_config = RialoTimingConfig::from_tick_duration(6_250_000, 0);
        assert_eq!(zero_slot_config.ns_per_slot, 0);
        assert_eq!(zero_slot_config.ticks_per_slot, 0);
        assert_eq!(zero_slot_config.slots_per_year, 0.0);

        // Test both zero
        let both_zero_config = RialoTimingConfig::from_tick_duration(0, 0);
        assert_eq!(both_zero_config.ns_per_slot, 0);
        assert_eq!(both_zero_config.ticks_per_slot, 0);
        assert_eq!(both_zero_config.slots_per_year, 0.0);
    }

    #[test]
    fn test_program_test_config() {
        let config = RialoTimingConfig::program_test();
        let expected = RialoTimingConfig::from_tick_duration(100_000, 64);

        assert_eq!(config.ns_per_slot, expected.ns_per_slot);
        assert_eq!(config.ticks_per_slot, expected.ticks_per_slot);
        assert_eq!(config.slots_per_year, expected.slots_per_year);
    }

    #[test]
    fn test_ns_per_tick() {
        let config = RialoTimingConfig::default();

        // 400_000_000 ns per slot / 64 ticks per slot = 6_250_000 ns per tick
        assert_eq!(config.ns_per_tick(), 6_250_000);

        // Test zero case
        let zero_config = RialoTimingConfig {
            ticks_per_slot: 0,
            ..config
        };
        assert_eq!(zero_config.ns_per_tick(), 0);
    }
}
