//! Gate threshold optimizer for conviction gates
//!
//! Adjusts conviction gate thresholds based on win-rate per confidence bucket.
//! Same safety rails pattern as the weight optimizer: bounded adjustments,
//! cooldown, freeze/unfreeze.

use super::conviction_gates::ConvictionGateConfig;
use std::time::{Duration, Instant};
use tracing::{info, warn};

/// Maximum threshold change per optimization cycle
const MAX_THRESHOLD_STEP: f64 = 0.03;
/// Minimum allowed threshold value
const MIN_THRESHOLD: f64 = 0.30;
/// Maximum allowed threshold value
const MAX_THRESHOLD: f64 = 0.90;
/// Minimum trades per bucket before adjustment
const MIN_BUCKET_TRADES: u64 = 50;

/// Win-rate metrics per confidence bucket
#[derive(Debug, Clone)]
pub struct GateBucketMetrics {
    /// Confidence bucket lower bound (e.g. 0.60)
    pub confidence_lower: f64,
    /// Confidence bucket upper bound (e.g. 0.70)
    pub confidence_upper: f64,
    /// Win rate in this bucket (0.0-1.0)
    pub win_rate: f64,
    /// Number of trades in this bucket
    pub trade_count: u64,
    /// Average P&L per trade in this bucket
    pub avg_pnl: f64,
}

/// Gate optimizer configuration
#[derive(Debug, Clone)]
pub struct GateOptimizerConfig {
    pub max_step: f64,
    pub min_threshold: f64,
    pub max_threshold: f64,
    pub min_bucket_trades: u64,
    pub cooldown: Duration,
    /// Target win rate -- thresholds tighten if below, loosen if above
    pub target_win_rate: f64,
}

impl Default for GateOptimizerConfig {
    fn default() -> Self {
        Self {
            max_step: MAX_THRESHOLD_STEP,
            min_threshold: MIN_THRESHOLD,
            max_threshold: MAX_THRESHOLD,
            min_bucket_trades: MIN_BUCKET_TRADES,
            cooldown: Duration::from_secs(24 * 3600),
            target_win_rate: 0.55,
        }
    }
}

/// Proposed threshold change
#[derive(Debug, Clone)]
pub struct ThresholdAdjustment {
    pub field_name: String,
    pub old_value: f64,
    pub new_value: f64,
    pub reason: String,
}

/// Result of a gate optimization cycle
#[derive(Debug, Clone)]
pub enum GateOptimizationResult {
    /// Thresholds adjusted
    Adjusted(Vec<ThresholdAdjustment>),
    /// Not enough data
    InsufficientData { total_trades: u64, required: u64 },
    /// Still in cooldown
    Cooldown { remaining: Duration },
    /// Frozen by kill switch
    KillSwitchActive,
}

/// Gate threshold optimizer
#[derive(Debug)]
pub struct GateOptimizer {
    config: GateOptimizerConfig,
    last_adjustment: Option<Instant>,
    frozen: bool,
}

impl GateOptimizer {
    pub fn new(config: GateOptimizerConfig) -> Self {
        Self {
            config,
            last_adjustment: None,
            frozen: false,
        }
    }

    /// Freeze all adjustments (kill switch)
    pub fn freeze(&mut self) {
        self.frozen = true;
        warn!("Gate optimizer frozen by kill switch");
    }

    /// Unfreeze adjustments
    pub fn unfreeze(&mut self) {
        self.frozen = false;
        info!("Gate optimizer unfrozen");
    }

    pub fn is_frozen(&self) -> bool {
        self.frozen
    }

    /// Run one optimization cycle
    ///
    /// Analyzes win-rate per confidence bucket and adjusts min_confidence threshold.
    /// If win rate near the current threshold is below target, threshold increases
    /// (more selective). If well above target, threshold decreases (more permissive).
    pub fn optimize(
        &mut self,
        gate_config: &ConvictionGateConfig,
        buckets: &[GateBucketMetrics],
    ) -> GateOptimizationResult {
        if self.frozen {
            return GateOptimizationResult::KillSwitchActive;
        }

        // Check cooldown
        if let Some(last) = self.last_adjustment {
            let elapsed = last.elapsed();
            if elapsed < self.config.cooldown {
                return GateOptimizationResult::Cooldown {
                    remaining: self.config.cooldown - elapsed,
                };
            }
        }

        // Check minimum data
        let total_trades: u64 = buckets.iter().map(|b| b.trade_count).sum();
        let min_required = self.config.min_bucket_trades * 3; // At least 3 buckets worth
        if total_trades < min_required {
            return GateOptimizationResult::InsufficientData {
                total_trades,
                required: min_required,
            };
        }

        let mut adjustments = Vec::new();

        // Analyze min_confidence threshold
        // Find the bucket containing the current threshold
        let threshold_bucket = buckets.iter().find(|b| {
            b.confidence_lower <= gate_config.min_confidence
                && gate_config.min_confidence < b.confidence_upper
                && b.trade_count >= self.config.min_bucket_trades
        });

        if let Some(bucket) = threshold_bucket {
            let win_rate_delta = bucket.win_rate - self.config.target_win_rate;

            // If win rate is too low near threshold → tighten (increase threshold)
            // If win rate is high → loosen (decrease threshold)
            let direction = if win_rate_delta < -0.05 {
                // Win rate below target by > 5pp → tighten
                1.0
            } else if win_rate_delta > 0.10 {
                // Win rate above target by > 10pp → loosen
                -1.0
            } else {
                0.0 // In acceptable range
            };

            if direction != 0.0 {
                let step = (win_rate_delta.abs() * 0.1)
                    .min(self.config.max_step)
                    .max(0.005);
                let new_confidence = (gate_config.min_confidence + direction * step)
                    .clamp(self.config.min_threshold, self.config.max_threshold);

                if (new_confidence - gate_config.min_confidence).abs() > 1e-6 {
                    adjustments.push(ThresholdAdjustment {
                        field_name: "min_confidence".into(),
                        old_value: gate_config.min_confidence,
                        new_value: new_confidence,
                        reason: format!(
                            "bucket win_rate={:.3}, target={:.3}, delta={:.3}",
                            bucket.win_rate, self.config.target_win_rate, win_rate_delta
                        ),
                    });
                }
            }
        }

        // Analyze max_disagreement threshold
        // If overall win rate on low-disagreement trades is high, can loosen
        let low_disagree_buckets: Vec<&GateBucketMetrics> = buckets
            .iter()
            .filter(|b| b.trade_count >= self.config.min_bucket_trades)
            .collect();

        if !low_disagree_buckets.is_empty() {
            let weighted_win_rate: f64 = low_disagree_buckets
                .iter()
                .map(|b| b.win_rate * b.trade_count as f64)
                .sum::<f64>()
                / low_disagree_buckets
                    .iter()
                    .map(|b| b.trade_count as f64)
                    .sum::<f64>();

            if weighted_win_rate < self.config.target_win_rate - 0.05 {
                // Poor overall performance → tighten disagreement (lower max)
                let new_max = (gate_config.max_disagreement - 0.01)
                    .clamp(0.10, 0.60);

                if (new_max - gate_config.max_disagreement).abs() > 1e-6 {
                    adjustments.push(ThresholdAdjustment {
                        field_name: "max_disagreement".into(),
                        old_value: gate_config.max_disagreement,
                        new_value: new_max,
                        reason: format!(
                            "weighted win_rate={:.3} below target {:.3}",
                            weighted_win_rate, self.config.target_win_rate
                        ),
                    });
                }
            }
        }

        if !adjustments.is_empty() {
            self.last_adjustment = Some(Instant::now());
            info!(
                "Gate optimizer adjusted {} thresholds",
                adjustments.len()
            );
        }

        GateOptimizationResult::Adjusted(adjustments)
    }

    /// Apply adjustments to a ConvictionGateConfig (returns modified copy)
    pub fn apply(config: &ConvictionGateConfig, adjustments: &[ThresholdAdjustment]) -> ConvictionGateConfig {
        let mut new_config = config.clone();
        for adj in adjustments {
            match adj.field_name.as_str() {
                "min_confidence" => new_config.min_confidence = adj.new_value,
                "max_disagreement" => new_config.max_disagreement = adj.new_value,
                "min_quorum" => new_config.min_quorum = adj.new_value,
                _ => {}
            }
        }
        new_config
    }
}

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

    fn make_buckets(data: &[(f64, f64, f64, u64)]) -> Vec<GateBucketMetrics> {
        data.iter()
            .map(|(lower, upper, win_rate, trades)| GateBucketMetrics {
                confidence_lower: *lower,
                confidence_upper: *upper,
                win_rate: *win_rate,
                trade_count: *trades,
                avg_pnl: 0.0,
            })
            .collect()
    }

    #[test]
    fn test_insufficient_data() {
        let mut opt = GateOptimizer::new(GateOptimizerConfig::default());
        let config = ConvictionGateConfig::default();
        let buckets = make_buckets(&[(0.50, 0.60, 0.55, 10), (0.60, 0.70, 0.60, 10)]);
        let result = opt.optimize(&config, &buckets);
        assert!(matches!(
            result,
            GateOptimizationResult::InsufficientData { .. }
        ));
    }

    #[test]
    fn test_cooldown_enforced() {
        let mut opt = GateOptimizer::new(GateOptimizerConfig::default());
        let config = ConvictionGateConfig::default();
        let buckets = make_buckets(&[
            (0.50, 0.60, 0.45, 100),
            (0.60, 0.70, 0.40, 100),
            (0.70, 0.80, 0.55, 100),
        ]);

        let _ = opt.optimize(&config, &buckets);
        let result2 = opt.optimize(&config, &buckets);
        assert!(matches!(result2, GateOptimizationResult::Cooldown { .. }));
    }

    #[test]
    fn test_kill_switch() {
        let mut opt = GateOptimizer::new(GateOptimizerConfig::default());
        opt.freeze();
        let config = ConvictionGateConfig::default();
        let buckets = make_buckets(&[(0.50, 0.60, 0.45, 200)]);
        let result = opt.optimize(&config, &buckets);
        assert!(matches!(result, GateOptimizationResult::KillSwitchActive));
    }

    #[test]
    fn test_tightens_on_low_win_rate() {
        let mut config_opt = GateOptimizerConfig::default();
        config_opt.cooldown = Duration::ZERO;
        let mut opt = GateOptimizer::new(config_opt);
        let config = ConvictionGateConfig::default(); // min_confidence = 0.60

        // Win rate at threshold bucket is poor (0.40 < 0.55 target)
        let buckets = make_buckets(&[
            (0.50, 0.60, 0.40, 100),
            (0.60, 0.70, 0.40, 100), // Threshold bucket
            (0.70, 0.80, 0.60, 100),
        ]);

        let result = opt.optimize(&config, &buckets);
        if let GateOptimizationResult::Adjusted(adjustments) = result {
            let confidence_adj = adjustments
                .iter()
                .find(|a| a.field_name == "min_confidence");
            assert!(
                confidence_adj.is_some(),
                "Expected min_confidence adjustment"
            );
            if let Some(adj) = confidence_adj {
                assert!(
                    adj.new_value > adj.old_value,
                    "Expected threshold to increase (tighten) on low win rate"
                );
            }
        }
    }

    #[test]
    fn test_loosens_on_high_win_rate() {
        let mut config_opt = GateOptimizerConfig::default();
        config_opt.cooldown = Duration::ZERO;
        let mut opt = GateOptimizer::new(config_opt);
        let config = ConvictionGateConfig::default(); // min_confidence = 0.60

        // Win rate at threshold bucket is very good (0.75 >> 0.55 target)
        let buckets = make_buckets(&[
            (0.50, 0.60, 0.70, 100),
            (0.60, 0.70, 0.75, 100), // Threshold bucket
            (0.70, 0.80, 0.80, 100),
        ]);

        let result = opt.optimize(&config, &buckets);
        if let GateOptimizationResult::Adjusted(adjustments) = result {
            let confidence_adj = adjustments
                .iter()
                .find(|a| a.field_name == "min_confidence");
            if let Some(adj) = confidence_adj {
                assert!(
                    adj.new_value < adj.old_value,
                    "Expected threshold to decrease (loosen) on high win rate"
                );
            }
        }
    }

    #[test]
    fn test_bounds_enforced() {
        let mut config_opt = GateOptimizerConfig::default();
        config_opt.cooldown = Duration::ZERO;
        let mut opt = GateOptimizer::new(config_opt);

        // Config with threshold already near maximum
        let mut config = ConvictionGateConfig::default();
        config.min_confidence = 0.89;

        // Very low win rate to force tightening
        let buckets = make_buckets(&[
            (0.80, 0.90, 0.30, 100),
            (0.89, 0.95, 0.30, 100), // Threshold bucket
            (0.70, 0.80, 0.40, 100),
        ]);

        let result = opt.optimize(&config, &buckets);
        if let GateOptimizationResult::Adjusted(adjustments) = result {
            for adj in &adjustments {
                assert!(
                    adj.new_value >= MIN_THRESHOLD,
                    "Below minimum: {}",
                    adj.new_value
                );
                assert!(
                    adj.new_value <= MAX_THRESHOLD,
                    "Above maximum: {}",
                    adj.new_value
                );
            }
        }
    }

    #[test]
    fn test_no_change_in_acceptable_range() {
        let mut config_opt = GateOptimizerConfig::default();
        config_opt.cooldown = Duration::ZERO;
        let mut opt = GateOptimizer::new(config_opt);
        let config = ConvictionGateConfig::default();

        // Win rate is in acceptable range (target ± tolerance)
        let buckets = make_buckets(&[
            (0.50, 0.60, 0.56, 100),
            (0.60, 0.70, 0.58, 100), // Just above target, within tolerance
            (0.70, 0.80, 0.60, 100),
        ]);

        let result = opt.optimize(&config, &buckets);
        if let GateOptimizationResult::Adjusted(adjustments) = result {
            let confidence_adj = adjustments
                .iter()
                .find(|a| a.field_name == "min_confidence");
            assert!(
                confidence_adj.is_none(),
                "Expected no min_confidence adjustment when in acceptable range"
            );
        }
    }

    #[test]
    fn test_apply_adjustments() {
        let config = ConvictionGateConfig::default();
        let adjustments = vec![
            ThresholdAdjustment {
                field_name: "min_confidence".into(),
                old_value: 0.60,
                new_value: 0.65,
                reason: "test".into(),
            },
            ThresholdAdjustment {
                field_name: "max_disagreement".into(),
                old_value: 0.40,
                new_value: 0.35,
                reason: "test".into(),
            },
        ];

        let new_config = GateOptimizer::apply(&config, &adjustments);
        assert!((new_config.min_confidence - 0.65).abs() < 1e-10);
        assert!((new_config.max_disagreement - 0.35).abs() < 1e-10);
        // Unchanged fields preserved
        assert!((new_config.min_quorum - 0.60).abs() < 1e-10);
    }
}
