//! Graduated recovery after emergency risk events.
//!
//! After an emergency (close-all), trading doesn't resume at full capacity.
//! Instead, position sizes ramp up over `ramp_days`:
//! Day 1 = 25%, Day 5 = 50%, Day 10 = 75%, Day 15 = 100%.

use chrono::{DateTime, Utc};

/// Recovery mode configuration.
#[derive(Debug, Clone)]
pub struct RecoveryConfig {
    /// Number of days to ramp from 25% to 100%.
    pub ramp_days: u32,
    /// Starting multiplier (default 0.25).
    pub start_multiplier: f64,
}

impl Default for RecoveryConfig {
    fn default() -> Self {
        Self {
            ramp_days: 15,
            start_multiplier: 0.25,
        }
    }
}

/// Tracks recovery state after an emergency event.
#[derive(Debug, Clone)]
pub struct RecoveryMode {
    config: RecoveryConfig,
    start_time: DateTime<Utc>,
    reset_count: u32,
}

impl RecoveryMode {
    /// Start a new recovery period.
    pub fn start(config: RecoveryConfig) -> Self {
        Self {
            config,
            start_time: Utc::now(),
            reset_count: 0,
        }
    }

    /// Start recovery at a specific time (for testing).
    pub fn start_at(config: RecoveryConfig, start_time: DateTime<Utc>) -> Self {
        Self {
            config,
            start_time,
            reset_count: 0,
        }
    }

    /// Compute the position size multiplier for the given time.
    /// Linear interpolation from start_multiplier to 1.0 over ramp_days.
    pub fn multiplier_at(&self, now: DateTime<Utc>) -> f64 {
        let elapsed = now.signed_duration_since(self.start_time);
        let days = elapsed.num_seconds() as f64 / 86400.0;

        if days <= 0.0 {
            return self.config.start_multiplier;
        }

        let progress = (days / self.config.ramp_days as f64).clamp(0.0, 1.0);
        let multiplier =
            self.config.start_multiplier + (1.0 - self.config.start_multiplier) * progress;
        multiplier.clamp(self.config.start_multiplier, 1.0)
    }

    /// Check if recovery is complete at the given time.
    pub fn is_complete(&self, now: DateTime<Utc>) -> bool {
        (self.multiplier_at(now) - 1.0).abs() < f64::EPSILON
    }

    /// Reset the recovery ramp (e.g. if drawdown re-enters warning during recovery).
    pub fn reset(&mut self) {
        self.start_time = Utc::now();
        self.reset_count += 1;
    }

    /// Reset at a specific time (for testing).
    pub fn reset_at(&mut self, now: DateTime<Utc>) {
        self.start_time = now;
        self.reset_count += 1;
    }

    /// Number of times recovery has been reset.
    pub fn reset_count(&self) -> u32 {
        self.reset_count
    }

    /// When recovery started.
    pub fn start_time(&self) -> DateTime<Utc> {
        self.start_time
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use chrono::{Duration, TimeZone};

    fn base_time() -> DateTime<Utc> {
        Utc.with_ymd_and_hms(2024, 6, 1, 0, 0, 0)
            .single()
            .unwrap_or_else(Utc::now)
    }

    #[test]
    fn test_day_one_multiplier_is_start() {
        let recovery = RecoveryMode::start_at(RecoveryConfig::default(), base_time());
        let mult = recovery.multiplier_at(base_time());
        assert!(
            (mult - 0.25).abs() < 0.01,
            "Day 0 should be ~0.25, got {mult}"
        );
    }

    #[test]
    fn test_day_five_multiplier() {
        let recovery = RecoveryMode::start_at(RecoveryConfig::default(), base_time());
        let day5 = base_time() + Duration::days(5);
        let mult = recovery.multiplier_at(day5);
        assert!(
            (mult - 0.50).abs() < 0.05,
            "Day 5 should be ~0.50, got {mult}"
        );
    }

    #[test]
    fn test_day_fifteen_multiplier_is_one() {
        let recovery = RecoveryMode::start_at(RecoveryConfig::default(), base_time());
        let day15 = base_time() + Duration::days(15);
        let mult = recovery.multiplier_at(day15);
        assert!(
            (mult - 1.0).abs() < 0.01,
            "Day 15 should be ~1.0, got {mult}"
        );
    }

    #[test]
    fn test_is_complete_after_ramp() {
        let recovery = RecoveryMode::start_at(RecoveryConfig::default(), base_time());
        assert!(!recovery.is_complete(base_time()));
        assert!(recovery.is_complete(base_time() + Duration::days(15)));
    }

    #[test]
    fn test_reset_restarts_ramp() {
        let mut recovery = RecoveryMode::start_at(RecoveryConfig::default(), base_time());
        let day10 = base_time() + Duration::days(10);
        let mult_before = recovery.multiplier_at(day10);
        assert!(mult_before > 0.5);

        recovery.reset_at(day10);
        let mult_after = recovery.multiplier_at(day10);
        assert!(
            (mult_after - 0.25).abs() < 0.01,
            "After reset, should be back to start, got {mult_after}"
        );
        assert_eq!(recovery.reset_count(), 1);
    }
}
