use crate::report::EvalReport;
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct EvalThresholds {
    pub min_retrieval_recall: f32,
    pub min_citation_precision: f32,
    pub min_refusal_accuracy: f32,
    pub min_schema_validity: f32,
}

impl Default for EvalThresholds {
    fn default() -> Self {
        Self {
            min_retrieval_recall: 0.90,
            min_citation_precision: 0.20,
            min_refusal_accuracy: 0.90,
            min_schema_validity: 0.95,
        }
    }
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct EvalThresholdAssessment {
    pub passed: bool,
    pub failures: Vec<String>,
}

pub fn assess_eval_thresholds(
    report: &EvalReport,
    thresholds: &EvalThresholds,
) -> EvalThresholdAssessment {
    let mut failures = Vec::new();
    if report.retrieval_recall < thresholds.min_retrieval_recall {
        failures.push(format!(
            "retrieval_recall {:.4} is below threshold {:.4}",
            report.retrieval_recall, thresholds.min_retrieval_recall
        ));
    }
    if report.citation_precision < thresholds.min_citation_precision {
        failures.push(format!(
            "citation_precision {:.4} is below threshold {:.4}",
            report.citation_precision, thresholds.min_citation_precision
        ));
    }
    if report.refusal_accuracy < thresholds.min_refusal_accuracy {
        failures.push(format!(
            "refusal_accuracy {:.4} is below threshold {:.4}",
            report.refusal_accuracy, thresholds.min_refusal_accuracy
        ));
    }
    if report.schema_validity < thresholds.min_schema_validity {
        failures.push(format!(
            "schema_validity {:.4} is below threshold {:.4}",
            report.schema_validity, thresholds.min_schema_validity
        ));
    }

    EvalThresholdAssessment {
        passed: failures.is_empty(),
        failures,
    }
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct LoadTestSample {
    pub latency_ms: f32,
    pub success: bool,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct LoadTestThresholds {
    pub min_requests: usize,
    pub max_error_rate: f32,
    pub max_p95_latency_ms: f32,
}

impl Default for LoadTestThresholds {
    fn default() -> Self {
        Self {
            min_requests: 20,
            max_error_rate: 0.10,
            max_p95_latency_ms: 100.0,
        }
    }
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct LoadTestReport {
    pub total_requests: usize,
    pub success_count: usize,
    pub error_rate: f32,
    pub p95_latency_ms: f32,
    pub passed: bool,
    pub failures: Vec<String>,
}

pub fn evaluate_load_test(
    samples: &[LoadTestSample],
    thresholds: &LoadTestThresholds,
) -> LoadTestReport {
    if samples.is_empty() {
        return LoadTestReport {
            total_requests: 0,
            success_count: 0,
            error_rate: 1.0,
            p95_latency_ms: 0.0,
            passed: false,
            failures: vec!["load test has no samples".into()],
        };
    }

    let total_requests = samples.len();
    let success_count = samples.iter().filter(|sample| sample.success).count();
    let error_rate = 1.0 - (success_count as f32 / total_requests as f32);
    let p95_latency_ms = p95(samples.iter().map(|sample| sample.latency_ms).collect());

    let mut failures = Vec::new();
    if total_requests < thresholds.min_requests {
        failures.push(format!(
            "total_requests {} is below threshold {}",
            total_requests, thresholds.min_requests
        ));
    }
    if error_rate > thresholds.max_error_rate {
        failures.push(format!(
            "error_rate {:.4} exceeds threshold {:.4}",
            error_rate, thresholds.max_error_rate
        ));
    }
    if p95_latency_ms > thresholds.max_p95_latency_ms {
        failures.push(format!(
            "p95_latency_ms {:.2} exceeds threshold {:.2}",
            p95_latency_ms, thresholds.max_p95_latency_ms
        ));
    }

    LoadTestReport {
        total_requests,
        success_count,
        error_rate,
        p95_latency_ms,
        passed: failures.is_empty(),
        failures,
    }
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SloTargets {
    pub min_availability: f32,
    pub max_p95_latency_ms: f32,
    pub min_eval_composite: f32,
}

impl Default for SloTargets {
    fn default() -> Self {
        Self {
            min_availability: 0.90,
            max_p95_latency_ms: 100.0,
            min_eval_composite: 0.75,
        }
    }
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SloDashboard {
    pub availability: f32,
    pub p95_latency_ms: f32,
    pub eval_composite: f32,
    pub passed: bool,
    pub failures: Vec<String>,
}

pub fn build_slo_dashboard(
    load_report: &LoadTestReport,
    eval_report: &EvalReport,
    targets: &SloTargets,
) -> SloDashboard {
    let availability = if load_report.total_requests == 0 {
        0.0
    } else {
        load_report.success_count as f32 / load_report.total_requests as f32
    };
    let eval_composite = (eval_report.retrieval_recall
        + eval_report.citation_precision
        + eval_report.refusal_accuracy
        + eval_report.schema_validity)
        / 4.0;

    let mut failures = Vec::new();
    if availability < targets.min_availability {
        failures.push(format!(
            "availability {:.4} is below target {:.4}",
            availability, targets.min_availability
        ));
    }
    if load_report.p95_latency_ms > targets.max_p95_latency_ms {
        failures.push(format!(
            "p95_latency_ms {:.2} exceeds target {:.2}",
            load_report.p95_latency_ms, targets.max_p95_latency_ms
        ));
    }
    if eval_composite < targets.min_eval_composite {
        failures.push(format!(
            "eval_composite {:.4} is below target {:.4}",
            eval_composite, targets.min_eval_composite
        ));
    }

    SloDashboard {
        availability,
        p95_latency_ms: load_report.p95_latency_ms,
        eval_composite,
        passed: failures.is_empty(),
        failures,
    }
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ReliabilitySample {
    pub succeeded: bool,
    pub replayable: bool,
    pub node_budget_violations: usize,
    pub run_budget_violations: usize,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ReliabilityThresholds {
    pub min_runs: usize,
    pub max_failure_rate: f32,
    pub min_replayability_rate: f32,
    pub max_budget_violation_rate: f32,
}

impl Default for ReliabilityThresholds {
    fn default() -> Self {
        Self {
            min_runs: 1,
            max_failure_rate: 0.20,
            min_replayability_rate: 1.0,
            max_budget_violation_rate: 0.05,
        }
    }
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ReliabilityReport {
    pub total_runs: usize,
    pub failure_rate: f32,
    pub replayability_rate: f32,
    pub budget_violation_rate: f32,
    pub passed: bool,
    pub failures: Vec<String>,
}

pub fn evaluate_reliability(
    samples: &[ReliabilitySample],
    thresholds: &ReliabilityThresholds,
) -> ReliabilityReport {
    if samples.is_empty() {
        return ReliabilityReport {
            total_runs: 0,
            failure_rate: 1.0,
            replayability_rate: 0.0,
            budget_violation_rate: 1.0,
            passed: false,
            failures: vec!["reliability report has no samples".into()],
        };
    }

    let total_runs = samples.len();
    let failures_count = samples.iter().filter(|sample| !sample.succeeded).count();
    let replayable_count = samples.iter().filter(|sample| sample.replayable).count();
    let budget_violation_count = samples
        .iter()
        .filter(|sample| sample.node_budget_violations > 0 || sample.run_budget_violations > 0)
        .count();

    let failure_rate = failures_count as f32 / total_runs as f32;
    let replayability_rate = replayable_count as f32 / total_runs as f32;
    let budget_violation_rate = budget_violation_count as f32 / total_runs as f32;

    let mut failures = Vec::new();
    if total_runs < thresholds.min_runs {
        failures.push(format!(
            "total_runs {} is below threshold {}",
            total_runs, thresholds.min_runs
        ));
    }
    if failure_rate > thresholds.max_failure_rate {
        failures.push(format!(
            "failure_rate {:.4} exceeds threshold {:.4}",
            failure_rate, thresholds.max_failure_rate
        ));
    }
    if replayability_rate < thresholds.min_replayability_rate {
        failures.push(format!(
            "replayability_rate {:.4} is below threshold {:.4}",
            replayability_rate, thresholds.min_replayability_rate
        ));
    }
    if budget_violation_rate > thresholds.max_budget_violation_rate {
        failures.push(format!(
            "budget_violation_rate {:.4} exceeds threshold {:.4}",
            budget_violation_rate, thresholds.max_budget_violation_rate
        ));
    }

    ReliabilityReport {
        total_runs,
        failure_rate,
        replayability_rate,
        budget_violation_rate,
        passed: failures.is_empty(),
        failures,
    }
}

fn p95(mut values: Vec<f32>) -> f32 {
    if values.is_empty() {
        return 0.0;
    }
    values.sort_by(f32::total_cmp);
    let n = values.len();
    let index = ((n * 95).div_ceil(100)).saturating_sub(1);
    values[index]
}

#[cfg(test)]
mod tests {
    use super::{
        assess_eval_thresholds, build_slo_dashboard, evaluate_load_test, evaluate_reliability,
        EvalThresholds, LoadTestSample, LoadTestThresholds, ReliabilitySample,
        ReliabilityThresholds, SloTargets,
    };
    use crate::report::{EvalCaseReport, EvalReport};

    fn sample_report() -> EvalReport {
        EvalReport::from_cases(
            "dataset.jsonl",
            vec![
                EvalCaseReport {
                    id: "one".into(),
                    input: "q1".into(),
                    expected_sources: vec!["a".into()],
                    actual_sources: vec!["a".into()],
                    retrieval_recall: 1.0,
                    citation_precision: 0.5,
                    refusal_expected: false,
                    refusal_actual: false,
                    refusal_accuracy: 1.0,
                    schema_valid: true,
                    schema_validity: 1.0,
                    latency_ms: 10.0,
                    notes: Vec::new(),
                },
                EvalCaseReport {
                    id: "two".into(),
                    input: "q2".into(),
                    expected_sources: vec!["b".into()],
                    actual_sources: vec!["b".into()],
                    retrieval_recall: 1.0,
                    citation_precision: 0.25,
                    refusal_expected: true,
                    refusal_actual: true,
                    refusal_accuracy: 1.0,
                    schema_valid: true,
                    schema_validity: 1.0,
                    latency_ms: 20.0,
                    notes: Vec::new(),
                },
            ],
        )
    }

    #[test]
    fn eval_thresholds_pass_for_sample_report() {
        let report = sample_report();
        let result = assess_eval_thresholds(&report, &EvalThresholds::default());
        assert!(result.passed);
        assert!(result.failures.is_empty());
    }

    #[test]
    fn eval_thresholds_fail_when_precision_is_too_low() {
        let thresholds = EvalThresholds {
            min_citation_precision: 0.8,
            ..EvalThresholds::default()
        };
        let report = sample_report();
        let result = assess_eval_thresholds(&report, &thresholds);
        assert!(!result.passed);
        assert!(result
            .failures
            .iter()
            .any(|failure| failure.contains("citation_precision")));
    }

    #[test]
    fn load_test_report_computes_error_rate_and_p95() {
        let samples = vec![
            LoadTestSample {
                latency_ms: 10.0,
                success: true,
            },
            LoadTestSample {
                latency_ms: 20.0,
                success: true,
            },
            LoadTestSample {
                latency_ms: 30.0,
                success: true,
            },
            LoadTestSample {
                latency_ms: 40.0,
                success: false,
            },
            LoadTestSample {
                latency_ms: 50.0,
                success: true,
            },
        ];
        let thresholds = LoadTestThresholds {
            min_requests: 5,
            max_error_rate: 0.25,
            max_p95_latency_ms: 50.0,
        };
        let report = evaluate_load_test(&samples, &thresholds);
        assert_eq!(report.total_requests, 5);
        assert_eq!(report.success_count, 4);
        assert!(report.error_rate <= 0.25);
        assert_eq!(report.p95_latency_ms, 50.0);
        assert!(report.passed);
    }

    #[test]
    fn load_test_fails_when_error_rate_exceeds_threshold() {
        let samples = vec![
            LoadTestSample {
                latency_ms: 10.0,
                success: false,
            },
            LoadTestSample {
                latency_ms: 12.0,
                success: false,
            },
            LoadTestSample {
                latency_ms: 14.0,
                success: true,
            },
        ];
        let thresholds = LoadTestThresholds {
            min_requests: 3,
            max_error_rate: 0.2,
            max_p95_latency_ms: 50.0,
        };
        let report = evaluate_load_test(&samples, &thresholds);
        assert!(!report.passed);
        assert!(report
            .failures
            .iter()
            .any(|failure| failure.contains("error_rate")));
    }

    #[test]
    fn slo_dashboard_uses_eval_and_load_signals() {
        let report = sample_report();
        let load_report = evaluate_load_test(
            &vec![
                LoadTestSample {
                    latency_ms: 30.0,
                    success: true,
                };
                20
            ],
            &LoadTestThresholds::default(),
        );
        let dashboard = build_slo_dashboard(&load_report, &report, &SloTargets::default());
        assert!(dashboard.passed);
        assert!(dashboard.failures.is_empty());
    }

    #[test]
    fn reliability_report_passes_for_replayable_low_failure_samples() {
        let samples = vec![
            ReliabilitySample {
                succeeded: true,
                replayable: true,
                node_budget_violations: 0,
                run_budget_violations: 0,
            },
            ReliabilitySample {
                succeeded: true,
                replayable: true,
                node_budget_violations: 0,
                run_budget_violations: 0,
            },
            ReliabilitySample {
                succeeded: true,
                replayable: true,
                node_budget_violations: 0,
                run_budget_violations: 0,
            },
            ReliabilitySample {
                succeeded: true,
                replayable: true,
                node_budget_violations: 0,
                run_budget_violations: 0,
            },
            ReliabilitySample {
                succeeded: true,
                replayable: true,
                node_budget_violations: 0,
                run_budget_violations: 0,
            },
        ];
        let report = evaluate_reliability(&samples, &ReliabilityThresholds::default());
        assert!(report.passed);
        assert_eq!(report.failure_rate, 0.0);
        assert_eq!(report.replayability_rate, 1.0);
    }

    #[test]
    fn reliability_report_fails_when_failure_or_budget_rates_are_too_high() {
        let samples = vec![
            ReliabilitySample {
                succeeded: false,
                replayable: true,
                node_budget_violations: 1,
                run_budget_violations: 0,
            },
            ReliabilitySample {
                succeeded: false,
                replayable: false,
                node_budget_violations: 1,
                run_budget_violations: 1,
            },
            ReliabilitySample {
                succeeded: true,
                replayable: true,
                node_budget_violations: 0,
                run_budget_violations: 0,
            },
            ReliabilitySample {
                succeeded: true,
                replayable: true,
                node_budget_violations: 0,
                run_budget_violations: 0,
            },
            ReliabilitySample {
                succeeded: true,
                replayable: true,
                node_budget_violations: 0,
                run_budget_violations: 0,
            },
        ];
        let report = evaluate_reliability(&samples, &ReliabilityThresholds::default());
        assert!(!report.passed);
        assert!(report
            .failures
            .iter()
            .any(|failure| failure.contains("failure_rate")));
        assert!(report
            .failures
            .iter()
            .any(|failure| failure.contains("budget_violation_rate")));
    }
}
