//! 7-Gate Conviction System
//!
//! Evaluates ensemble predictions through a series of quality gates before
//! allowing trade execution. Gates are evaluated in order; any failure
//! results in HOLD.

use serde::{Deserialize, Serialize};

/// Trading session windows (Eastern Time)
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum TradingSession {
    PreMarket,
    Regular,
    AfterHours,
}

/// Configuration for the 7-gate conviction system
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConvictionGateConfig {
    pub model_health_threshold: f64,
    pub allowed_sessions: Vec<TradingSession>,
    pub min_confidence: f64,
    pub max_disagreement: f64,
    pub min_quorum: f64,
    pub regime_tightening_factor: f64,
    pub high_vol_threshold: f64,
    pub conviction_scaling_enabled: bool,
}

impl Default for ConvictionGateConfig {
    fn default() -> Self {
        Self {
            model_health_threshold: 0.70,
            allowed_sessions: vec![TradingSession::Regular],
            min_confidence: 0.60,
            max_disagreement: 0.40,
            min_quorum: 0.60,
            regime_tightening_factor: 0.80,
            high_vol_threshold: 0.03,
            conviction_scaling_enabled: true,
        }
    }
}

/// Which gate rejected the decision (if any)
#[derive(Debug, Clone, PartialEq)]
pub enum GateRejection {
    ModelHealth {
        healthy_ratio: f64,
        threshold: f64,
    },
    TimeOfDay {
        current_session: TradingSession,
    },
    Confidence {
        confidence: f64,
        threshold: f64,
    },
    Agreement {
        disagreement: f64,
        threshold: f64,
    },
    Quorum {
        quorum_ratio: f64,
        threshold: f64,
    },
    Regime {
        adjusted_confidence: f64,
        adjusted_threshold: f64,
    },
}

/// Result of passing all conviction gates
#[derive(Debug, Clone)]
pub struct GatePassResult {
    pub conviction_score: f64,
    pub gate_details: Vec<GateEvaluation>,
}

/// Individual gate evaluation result
#[derive(Debug, Clone)]
pub struct GateEvaluation {
    pub gate_name: String,
    pub value: f64,
    pub threshold: f64,
    pub passed: bool,
}

/// Outcome of evaluating all gates
#[derive(Debug, Clone)]
pub enum ConvictionGateOutcome {
    Passed(GatePassResult),
    Rejected(GateRejection),
}

/// Input data for gate evaluation
#[derive(Debug, Clone)]
pub struct GateInput {
    pub confidence: f64,
    pub disagreement_rate: f64,
    pub quorum_ratio: f64,
    pub healthy_models: usize,
    pub total_models: usize,
    pub current_session: TradingSession,
    pub regime_volatility: f64,
}

/// Evaluates ensemble decisions through 7 conviction gates
#[derive(Debug, Clone)]
pub struct ConvictionGateEvaluator {
    config: ConvictionGateConfig,
}

impl ConvictionGateEvaluator {
    pub fn new(config: ConvictionGateConfig) -> Self {
        Self { config }
    }

    pub fn config(&self) -> &ConvictionGateConfig {
        &self.config
    }

    pub fn config_mut(&mut self) -> &mut ConvictionGateConfig {
        &mut self.config
    }

    /// Evaluate all 7 gates in order. Returns on first rejection.
    pub fn evaluate(&self, input: &GateInput) -> ConvictionGateOutcome {
        let mut gate_details = Vec::with_capacity(7);

        // Gate 1: Model Health - healthy_ratio >= threshold
        let healthy_ratio = if input.total_models == 0 {
            0.0
        } else {
            input.healthy_models as f64 / input.total_models as f64
        };

        let gate1_passed = healthy_ratio >= self.config.model_health_threshold;
        gate_details.push(GateEvaluation {
            gate_name: "model_health".to_owned(),
            value: healthy_ratio,
            threshold: self.config.model_health_threshold,
            passed: gate1_passed,
        });
        if !gate1_passed {
            return ConvictionGateOutcome::Rejected(GateRejection::ModelHealth {
                healthy_ratio,
                threshold: self.config.model_health_threshold,
            });
        }

        // Gate 2: Time-of-Day - current_session in allowed_sessions
        let gate2_passed = self.config.allowed_sessions.contains(&input.current_session);
        gate_details.push(GateEvaluation {
            gate_name: "time_of_day".to_owned(),
            value: if gate2_passed { 1.0 } else { 0.0 },
            threshold: 1.0,
            passed: gate2_passed,
        });
        if !gate2_passed {
            return ConvictionGateOutcome::Rejected(GateRejection::TimeOfDay {
                current_session: input.current_session,
            });
        }

        // Gate 6 prep: If regime_volatility > high_vol_threshold, tighten thresholds
        let high_vol = input.regime_volatility > self.config.high_vol_threshold;
        let (adj_min_confidence, adj_max_disagreement, adj_min_quorum) = if high_vol {
            let factor = self.config.regime_tightening_factor;
            let adj_conf = (self.config.min_confidence / factor).min(0.95);
            let adj_disagree = (self.config.max_disagreement * factor).max(0.05);
            let adj_quorum = (self.config.min_quorum / factor).min(0.95);
            (adj_conf, adj_disagree, adj_quorum)
        } else {
            (
                self.config.min_confidence,
                self.config.max_disagreement,
                self.config.min_quorum,
            )
        };

        // Gate 3: Confidence >= adj_min_confidence
        let gate3_passed = input.confidence >= adj_min_confidence;
        gate_details.push(GateEvaluation {
            gate_name: "confidence".to_owned(),
            value: input.confidence,
            threshold: adj_min_confidence,
            passed: gate3_passed,
        });
        if !gate3_passed {
            return ConvictionGateOutcome::Rejected(GateRejection::Confidence {
                confidence: input.confidence,
                threshold: adj_min_confidence,
            });
        }

        // Gate 4: Disagreement <= adj_max_disagreement
        let gate4_passed = input.disagreement_rate <= adj_max_disagreement;
        gate_details.push(GateEvaluation {
            gate_name: "agreement".to_owned(),
            value: input.disagreement_rate,
            threshold: adj_max_disagreement,
            passed: gate4_passed,
        });
        if !gate4_passed {
            return ConvictionGateOutcome::Rejected(GateRejection::Agreement {
                disagreement: input.disagreement_rate,
                threshold: adj_max_disagreement,
            });
        }

        // Gate 5: Quorum >= adj_min_quorum
        let gate5_passed = input.quorum_ratio >= adj_min_quorum;
        gate_details.push(GateEvaluation {
            gate_name: "quorum".to_owned(),
            value: input.quorum_ratio,
            threshold: adj_min_quorum,
            passed: gate5_passed,
        });
        if !gate5_passed {
            return ConvictionGateOutcome::Rejected(GateRejection::Quorum {
                quorum_ratio: input.quorum_ratio,
                threshold: adj_min_quorum,
            });
        }

        // Gate 6: Regime (already applied via threshold adjustments)
        // Record the regime gate evaluation
        let regime_value = input.confidence;
        let regime_threshold = adj_min_confidence;
        gate_details.push(GateEvaluation {
            gate_name: "regime".to_owned(),
            value: regime_value,
            threshold: regime_threshold,
            passed: true, // Already enforced via adjusted thresholds in gates 3-5
        });

        // Gate 7: Conviction Sizing
        let conviction_score = if self.config.conviction_scaling_enabled {
            input.confidence
                * (1.0 - input.disagreement_rate)
                * input.quorum_ratio
                * healthy_ratio
        } else {
            1.0
        };
        gate_details.push(GateEvaluation {
            gate_name: "conviction_sizing".to_owned(),
            value: conviction_score,
            threshold: 0.0, // No minimum threshold for conviction sizing
            passed: true,
        });

        ConvictionGateOutcome::Passed(GatePassResult {
            conviction_score,
            gate_details,
        })
    }
}

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

    fn default_passing_input() -> GateInput {
        GateInput {
            confidence: 0.80,
            disagreement_rate: 0.10,
            quorum_ratio: 0.80,
            healthy_models: 8,
            total_models: 10,
            current_session: TradingSession::Regular,
            regime_volatility: 0.01,
        }
    }

    #[test]
    fn test_default_config_values() {
        let config = ConvictionGateConfig::default();
        assert!((config.model_health_threshold - 0.70).abs() < f64::EPSILON);
        assert_eq!(config.allowed_sessions, vec![TradingSession::Regular]);
        assert!((config.min_confidence - 0.60).abs() < f64::EPSILON);
        assert!((config.max_disagreement - 0.40).abs() < f64::EPSILON);
        assert!((config.min_quorum - 0.60).abs() < f64::EPSILON);
        assert!((config.regime_tightening_factor - 0.80).abs() < f64::EPSILON);
        assert!((config.high_vol_threshold - 0.03).abs() < f64::EPSILON);
        assert!(config.conviction_scaling_enabled);
    }

    #[test]
    fn test_gate_rejection_variants() {
        let r1 = GateRejection::ModelHealth {
            healthy_ratio: 0.5,
            threshold: 0.7,
        };
        let r2 = GateRejection::ModelHealth {
            healthy_ratio: 0.5,
            threshold: 0.7,
        };
        assert_eq!(r1, r2);

        let r3 = GateRejection::TimeOfDay {
            current_session: TradingSession::PreMarket,
        };
        assert_ne!(r1, r3);
    }

    #[test]
    fn test_gate1_model_health_passes() {
        let evaluator = ConvictionGateEvaluator::new(ConvictionGateConfig::default());
        let input = GateInput {
            healthy_models: 8,
            total_models: 10,
            ..default_passing_input()
        };
        let outcome = evaluator.evaluate(&input);
        assert!(matches!(outcome, ConvictionGateOutcome::Passed(_)));
    }

    #[test]
    fn test_gate1_model_health_rejects() {
        let evaluator = ConvictionGateEvaluator::new(ConvictionGateConfig::default());
        let input = GateInput {
            healthy_models: 5,
            total_models: 10,
            ..default_passing_input()
        };
        let outcome = evaluator.evaluate(&input);
        if let ConvictionGateOutcome::Rejected(GateRejection::ModelHealth {
            healthy_ratio,
            threshold,
        }) = outcome
        {
            assert!((healthy_ratio - 0.5).abs() < f64::EPSILON);
            assert!((threshold - 0.70).abs() < f64::EPSILON);
        } else {
            panic!("Expected ModelHealth rejection, got {:?}", outcome);
        }
    }

    #[test]
    fn test_gate2_time_of_day_rejects_premarket() {
        let evaluator = ConvictionGateEvaluator::new(ConvictionGateConfig::default());
        let input = GateInput {
            current_session: TradingSession::PreMarket,
            ..default_passing_input()
        };
        let outcome = evaluator.evaluate(&input);
        if let ConvictionGateOutcome::Rejected(GateRejection::TimeOfDay { current_session }) =
            outcome
        {
            assert_eq!(current_session, TradingSession::PreMarket);
        } else {
            panic!("Expected TimeOfDay rejection, got {:?}", outcome);
        }
    }

    #[test]
    fn test_gate3_confidence_rejects_low() {
        let evaluator = ConvictionGateEvaluator::new(ConvictionGateConfig::default());
        let input = GateInput {
            confidence: 0.45,
            ..default_passing_input()
        };
        let outcome = evaluator.evaluate(&input);
        if let ConvictionGateOutcome::Rejected(GateRejection::Confidence {
            confidence,
            threshold,
        }) = outcome
        {
            assert!((confidence - 0.45).abs() < f64::EPSILON);
            assert!((threshold - 0.60).abs() < f64::EPSILON);
        } else {
            panic!("Expected Confidence rejection, got {:?}", outcome);
        }
    }

    #[test]
    fn test_gate4_agreement_rejects_high_disagreement() {
        let evaluator = ConvictionGateEvaluator::new(ConvictionGateConfig::default());
        let input = GateInput {
            disagreement_rate: 0.55,
            ..default_passing_input()
        };
        let outcome = evaluator.evaluate(&input);
        if let ConvictionGateOutcome::Rejected(GateRejection::Agreement {
            disagreement,
            threshold,
        }) = outcome
        {
            assert!((disagreement - 0.55).abs() < f64::EPSILON);
            assert!((threshold - 0.40).abs() < f64::EPSILON);
        } else {
            panic!("Expected Agreement rejection, got {:?}", outcome);
        }
    }

    #[test]
    fn test_gate5_quorum_rejects_low() {
        let evaluator = ConvictionGateEvaluator::new(ConvictionGateConfig::default());
        let input = GateInput {
            quorum_ratio: 0.40,
            ..default_passing_input()
        };
        let outcome = evaluator.evaluate(&input);
        if let ConvictionGateOutcome::Rejected(GateRejection::Quorum {
            quorum_ratio,
            threshold,
        }) = outcome
        {
            assert!((quorum_ratio - 0.40).abs() < f64::EPSILON);
            assert!((threshold - 0.60).abs() < f64::EPSILON);
        } else {
            panic!("Expected Quorum rejection, got {:?}", outcome);
        }
    }

    #[test]
    fn test_conviction_score_calculation() {
        let evaluator = ConvictionGateEvaluator::new(ConvictionGateConfig::default());
        let input = GateInput {
            confidence: 0.80,
            disagreement_rate: 0.10,
            quorum_ratio: 0.80,
            healthy_models: 9,
            total_models: 10,
            current_session: TradingSession::Regular,
            regime_volatility: 0.01,
        };
        let outcome = evaluator.evaluate(&input);
        if let ConvictionGateOutcome::Passed(result) = outcome {
            // score = 0.80 * (1.0 - 0.10) * 0.80 * (9/10) = 0.80 * 0.90 * 0.80 * 0.90 = 0.5184
            let expected = 0.80 * 0.90 * 0.80 * 0.90;
            assert!(
                (result.conviction_score - expected).abs() < 1e-10,
                "Expected {}, got {}",
                expected,
                result.conviction_score
            );
        } else {
            panic!("Expected Passed outcome, got {:?}", outcome);
        }
    }

    #[test]
    fn test_gate6_regime_tightens_in_high_vol() {
        let evaluator = ConvictionGateEvaluator::new(ConvictionGateConfig::default());
        // With default config: min_confidence=0.60, tightening=0.80
        // High vol: adj_min_confidence = 0.60 / 0.80 = 0.75
        // 0.70 confidence < 0.75 threshold => rejected
        let input = GateInput {
            confidence: 0.70,
            regime_volatility: 0.05,
            ..default_passing_input()
        };
        let outcome = evaluator.evaluate(&input);
        if let ConvictionGateOutcome::Rejected(GateRejection::Confidence {
            confidence,
            threshold,
        }) = outcome
        {
            assert!((confidence - 0.70).abs() < f64::EPSILON);
            assert!((threshold - 0.75).abs() < f64::EPSILON);
        } else {
            panic!(
                "Expected Confidence rejection due to regime tightening, got {:?}",
                outcome
            );
        }
    }

    #[test]
    fn test_gate6_regime_no_effect_in_low_vol() {
        let evaluator = ConvictionGateEvaluator::new(ConvictionGateConfig::default());
        // Low vol: thresholds unchanged, min_confidence=0.60
        // 0.65 confidence >= 0.60 => passes
        let input = GateInput {
            confidence: 0.65,
            regime_volatility: 0.01,
            ..default_passing_input()
        };
        let outcome = evaluator.evaluate(&input);
        assert!(
            matches!(outcome, ConvictionGateOutcome::Passed(_)),
            "Expected Passed in low vol, got {:?}",
            outcome
        );
    }

    #[test]
    fn test_zero_models_rejects() {
        let evaluator = ConvictionGateEvaluator::new(ConvictionGateConfig::default());
        let input = GateInput {
            healthy_models: 0,
            total_models: 0,
            ..default_passing_input()
        };
        let outcome = evaluator.evaluate(&input);
        if let ConvictionGateOutcome::Rejected(GateRejection::ModelHealth {
            healthy_ratio,
            threshold,
        }) = outcome
        {
            assert!((healthy_ratio - 0.0).abs() < f64::EPSILON);
            assert!((threshold - 0.70).abs() < f64::EPSILON);
        } else {
            panic!("Expected ModelHealth rejection for 0/0 models, got {:?}", outcome);
        }
    }

    #[test]
    fn test_conviction_scaling_disabled() {
        let mut config = ConvictionGateConfig::default();
        config.conviction_scaling_enabled = false;
        let evaluator = ConvictionGateEvaluator::new(config);
        let input = default_passing_input();
        let outcome = evaluator.evaluate(&input);
        if let ConvictionGateOutcome::Passed(result) = outcome {
            assert!(
                (result.conviction_score - 1.0).abs() < f64::EPSILON,
                "Expected 1.0 when scaling disabled, got {}",
                result.conviction_score
            );
        } else {
            panic!("Expected Passed outcome, got {:?}", outcome);
        }
    }

    #[test]
    fn test_gate_details_count() {
        let evaluator = ConvictionGateEvaluator::new(ConvictionGateConfig::default());
        let input = default_passing_input();
        let outcome = evaluator.evaluate(&input);
        if let ConvictionGateOutcome::Passed(result) = outcome {
            assert_eq!(
                result.gate_details.len(),
                7,
                "Expected 7 gate evaluations, got {}",
                result.gate_details.len()
            );
        } else {
            panic!("Expected Passed outcome, got {:?}", outcome);
        }
    }

    #[test]
    fn test_after_hours_allowed_when_configured() {
        let mut config = ConvictionGateConfig::default();
        config.allowed_sessions.push(TradingSession::AfterHours);
        let evaluator = ConvictionGateEvaluator::new(config);
        let input = GateInput {
            current_session: TradingSession::AfterHours,
            ..default_passing_input()
        };
        let outcome = evaluator.evaluate(&input);
        assert!(
            matches!(outcome, ConvictionGateOutcome::Passed(_)),
            "Expected Passed for AfterHours when configured, got {:?}",
            outcome
        );
    }
}
