//! Shadow mode testing configuration.
//!
//! Controls how auto-promoted extractors are tested in shadow mode
//! before full deployment to production.

use std::path::PathBuf;

use serde::Deserialize;

/// Shadow mode testing configuration.
///
/// Auto-promoted extractors run in "shadow mode" alongside production
/// extractors to measure false positive rates before full deployment.
///
/// # Safety Design
///
/// - **Isolation**: Shadow matches stored separately from production output
/// - **Metrics transparency**: FP rate visible via `shadow-status`
/// - **Auto-rollback**: High FP rate (>15%) triggers automatic rollback
/// - **Manual control**: `rollback` command for immediate removal
/// - **Audit trail**: All decisions logged to `decisions.jsonl`
/// - **Graduation gate**: Must meet min_scans + max_fp_rate criteria
///
/// # Configuration
///
/// ```toml
/// [shadow]
/// enabled = true            # Enable shadow mode (default: true)
/// min_scans = 100           # Minimum scans before graduation eligible
/// max_fp_rate = 0.05        # Maximum false positive rate for graduation
/// rollback_threshold = 0.15 # FP rate that triggers auto-rollback
/// auto_rollback_enabled = true # Enable automatic rollback (default: true)
/// min_rollback_samples = 10 # Minimum samples before auto-rollback (default: 10)
/// retention_days = 30       # Days to retain shadow data
/// ```
#[derive(Debug, Clone, Deserialize)]
#[serde(default)]
pub struct ShadowConfig {
    /// Enable shadow mode for auto-promoted extractors.
    ///
    /// When enabled, auto-promoted extractors enter shadow mode instead
    /// of going directly to production. Default: true (safety by default).
    pub enabled: bool,

    /// Minimum number of scans before graduation eligible.
    ///
    /// Default: 100. The extractor must run on at least this many files
    /// before it can be graduated to production.
    pub min_scans: usize,

    /// Maximum false positive rate for graduation.
    ///
    /// Default: 0.05 (5%). Extractors with FP rates above this threshold
    /// cannot be graduated to production.
    pub max_fp_rate: f32,

    /// False positive rate that triggers automatic rollback.
    ///
    /// Default: 0.15 (15%). Extractors with FP rates above this threshold
    /// are automatically rolled back and removed from shadow mode.
    pub rollback_threshold: f32,

    /// Enable automatic rollback when threshold exceeded.
    ///
    /// Default: true. When enabled, extractors exceeding rollback_threshold
    /// are automatically rolled back immediately after feedback is recorded.
    /// Set to false for manual-only rollback workflows.
    pub auto_rollback_enabled: bool,

    /// Minimum reviewed samples before auto-rollback can trigger.
    ///
    /// Default: 10. Prevents auto-rollback from firing on small sample sizes
    /// where FP rate may be noisy. High-traffic deployments may want 50+,
    /// low-traffic deployments might be fine with 5.
    pub min_rollback_samples: usize,

    /// Shadow results directory.
    ///
    /// Default: `~/.aphoria/shadow/`
    pub shadow_dir: Option<PathBuf>,

    /// Days to retain shadow data.
    ///
    /// Default: 30. Shadow test data older than this is pruned.
    pub retention_days: u32,
}

impl ShadowConfig {
    /// Get the shadow directory, using defaults if not specified.
    pub fn get_shadow_dir(&self) -> PathBuf {
        if let Some(ref dir) = self.shadow_dir {
            dir.clone()
        } else if let Some(home) = dirs::home_dir() {
            home.join(".aphoria").join("shadow")
        } else {
            PathBuf::from(".aphoria/shadow")
        }
    }

    /// Check if an FP rate meets graduation criteria.
    pub fn meets_graduation_fp_rate(&self, fp_rate: f32) -> bool {
        fp_rate <= self.max_fp_rate
    }

    /// Check if an FP rate exceeds rollback threshold.
    pub fn exceeds_rollback_threshold(&self, fp_rate: f32) -> bool {
        fp_rate >= self.rollback_threshold
    }
}

impl Default for ShadowConfig {
    fn default() -> Self {
        Self {
            enabled: true, // Safety by default - shadow mode on
            min_scans: 100,
            max_fp_rate: 0.05,
            rollback_threshold: 0.15,
            auto_rollback_enabled: true, // Auto-rollback enabled by default
            min_rollback_samples: 10,    // Minimum samples before auto-rollback
            shadow_dir: None,
            retention_days: 30,
        }
    }
}

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

    #[test]
    fn test_default_is_enabled() {
        let config = ShadowConfig::default();
        assert!(config.enabled, "Shadow mode should be enabled by default for safety");
    }

    #[test]
    fn test_default_thresholds() {
        let config = ShadowConfig::default();
        assert_eq!(config.min_scans, 100);
        assert!((config.max_fp_rate - 0.05).abs() < 0.001);
        assert!((config.rollback_threshold - 0.15).abs() < 0.001);
        assert_eq!(config.min_rollback_samples, 10);
        assert_eq!(config.retention_days, 30);
    }

    #[test]
    fn test_meets_graduation_fp_rate() {
        let config = ShadowConfig::default();
        assert!(config.meets_graduation_fp_rate(0.03));
        assert!(config.meets_graduation_fp_rate(0.05));
        assert!(!config.meets_graduation_fp_rate(0.06));
    }

    #[test]
    fn test_exceeds_rollback_threshold() {
        let config = ShadowConfig::default();
        assert!(!config.exceeds_rollback_threshold(0.10));
        assert!(config.exceeds_rollback_threshold(0.15));
        assert!(config.exceeds_rollback_threshold(0.20));
    }

    #[test]
    fn test_get_shadow_dir_with_explicit() {
        let config = ShadowConfig {
            shadow_dir: Some(PathBuf::from("/custom/shadow")),
            ..Default::default()
        };
        assert_eq!(config.get_shadow_dir(), PathBuf::from("/custom/shadow"));
    }

    #[test]
    fn test_get_shadow_dir_uses_home() {
        let config = ShadowConfig::default();
        let dir = config.get_shadow_dir();
        // Should end with shadow
        assert!(dir.ends_with("shadow"));
    }

    #[test]
    fn test_deserialize_with_defaults() {
        let toml = r#"
            enabled = true
            min_scans = 200
        "#;

        let config: ShadowConfig = toml::from_str(toml).expect("parse");
        assert!(config.enabled);
        assert_eq!(config.min_scans, 200);
        // Other fields should use defaults
        assert!((config.max_fp_rate - 0.05).abs() < 0.001);
        assert!((config.rollback_threshold - 0.15).abs() < 0.001);
        assert_eq!(config.min_rollback_samples, 10);
    }

    #[test]
    fn test_custom_min_rollback_samples() {
        let toml = r#"
            enabled = true
            min_rollback_samples = 50
        "#;

        let config: ShadowConfig = toml::from_str(toml).expect("parse");
        assert_eq!(config.min_rollback_samples, 50);
    }
}
