use tsift_quality::token_gate;

use token_gate::{
    MIN_TOKEN_GATE_SAMPLES, TOKEN_GATE_SURFACES, TokenGateDecision, TokenSurfaceVerdict,
    evaluate_token_gate, evaluate_token_regression, parse_token_history,
};

#[allow(clippy::too_many_arguments)]
fn synth_token_sample(
    id: &str,
    surface: &str,
    prompt_tokens: f64,
    envelope_bytes: f64,
    runtime_micros: f64,
    cache_hit_rate: f64,
    raw_read_avoidance: f64,
    useful_hit_density: f64,
) -> serde_json::Value {
    let mut metrics = serde_json::Map::new();
    metrics.insert(
        "prompt_tokens".into(),
        serde_json::Value::from(prompt_tokens),
    );
    metrics.insert(
        "envelope_bytes".into(),
        serde_json::Value::from(envelope_bytes),
    );
    metrics.insert(
        "runtime_micros".into(),
        serde_json::Value::from(runtime_micros),
    );
    metrics.insert(
        "cache_hit_rate_percent".into(),
        serde_json::Value::from(cache_hit_rate),
    );
    metrics.insert(
        "raw_read_avoidance".into(),
        serde_json::Value::from(raw_read_avoidance),
    );
    metrics.insert(
        "useful_hit_density".into(),
        serde_json::Value::from(useful_hit_density),
    );
    let mut entry = serde_json::Map::new();
    entry.insert(
        "label".into(),
        serde_json::Value::from(format!("synth {surface} sample")),
    );
    entry.insert("id".into(), serde_json::Value::from(id.to_string()));
    entry.insert(
        "timestamp".into(),
        serde_json::Value::from("2026-06-02T00:00:00Z"),
    );
    entry.insert(
        "surface".into(),
        serde_json::Value::from(surface.to_string()),
    );
    entry.insert("metrics".into(), serde_json::Value::Object(metrics));
    serde_json::Value::Object(entry)
}

fn build_token_history(samples: Vec<serde_json::Value>) -> String {
    let mut root = serde_json::Map::new();
    root.insert("entries".into(), serde_json::Value::Array(samples));
    serde_json::Value::Object(root).to_string()
}

fn full_token_history_three_samples_each() -> Vec<token_gate::TokenGateSample> {
    let mut entries = Vec::new();
    for surface in TOKEN_GATE_SURFACES {
        for i in 1..=3 {
            entries.push(synth_token_sample(
                &format!("synth-{surface}-2026-06-02-sample-{i}"),
                surface,
                500.0,
                2048.0,
                150_000.0,
                85.0,
                12.0,
                0.72,
            ));
        }
    }
    let raw = build_token_history(entries);
    parse_token_history(&raw).expect("synthetic history parses")
}

#[test]
fn token_gate_passes_when_all_surfaces_have_samples_with_signal() {
    let history = full_token_history_three_samples_each();
    let report = evaluate_token_gate(&history, 10.0);
    assert_eq!(report.decision, TokenGateDecision::Pass, "{report:?}");
    assert_eq!(report.surface_evaluations.len(), TOKEN_GATE_SURFACES.len());
    assert!(
        report
            .surface_evaluations
            .iter()
            .all(|s| s.verdict == TokenSurfaceVerdict::Pass)
    );
}

#[test]
fn token_gate_blocks_when_a_surface_is_missing() {
    let mut entries = Vec::new();
    for surface in &TOKEN_GATE_SURFACES[..4] {
        for i in 1..=3 {
            entries.push(synth_token_sample(
                &format!("synth-{surface}-2026-06-02-sample-{i}"),
                surface,
                500.0,
                2048.0,
                150_000.0,
                85.0,
                12.0,
                0.72,
            ));
        }
    }
    let raw = build_token_history(entries);
    let history = parse_token_history(&raw).unwrap();
    let report = evaluate_token_gate(&history, 10.0);
    assert_eq!(report.decision, TokenGateDecision::Block);
    let missing = report
        .surface_evaluations
        .iter()
        .filter(|s| s.verdict == TokenSurfaceVerdict::Missing)
        .count();
    assert_eq!(missing, 1);
}

#[test]
fn token_gate_blocks_on_insufficient_samples() {
    let mut entries = Vec::new();
    for surface in TOKEN_GATE_SURFACES {
        for i in 1..=2 {
            entries.push(synth_token_sample(
                &format!("synth-{surface}-2026-06-02-sample-{i}"),
                surface,
                500.0,
                2048.0,
                150_000.0,
                85.0,
                12.0,
                0.72,
            ));
        }
    }
    let raw = build_token_history(entries);
    let history = parse_token_history(&raw).unwrap();
    let report = evaluate_token_gate(&history, 10.0);
    assert_eq!(report.decision, TokenGateDecision::Block);
    assert!(
        report
            .surface_evaluations
            .iter()
            .all(|s| s.verdict == TokenSurfaceVerdict::InsufficientSamples)
    );
}

#[test]
fn token_regression_passes_when_candidate_matches_baseline() {
    let history = full_token_history_three_samples_each();
    let report = evaluate_token_regression(&history, &history, 10.0);
    assert_eq!(report.decision, TokenGateDecision::Pass, "{report:?}");
}

#[test]
fn token_regression_blocks_on_lower_is_better_regression() {
    let mut baseline_entries = Vec::new();
    let mut candidate_entries = Vec::new();
    for surface in TOKEN_GATE_SURFACES {
        for i in 1..=3 {
            baseline_entries.push(synth_token_sample(
                &format!("base-{surface}-sample-{i}"),
                surface,
                500.0,
                2048.0,
                150_000.0,
                85.0,
                12.0,
                0.72,
            ));
            candidate_entries.push(synth_token_sample(
                &format!("cand-{surface}-sample-{i}"),
                surface,
                5000.0,
                2048.0,
                150_000.0,
                85.0,
                12.0,
                0.72,
            ));
        }
    }
    let baseline =
        parse_token_history(&build_token_history(baseline_entries)).expect("baseline parses");
    let candidate =
        parse_token_history(&build_token_history(candidate_entries)).expect("candidate parses");
    let report = evaluate_token_regression(&baseline, &candidate, 10.0);
    assert_eq!(report.decision, TokenGateDecision::Block);
}

#[test]
fn token_regression_blocks_on_higher_is_better_regression() {
    let mut baseline_entries = Vec::new();
    let mut candidate_entries = Vec::new();
    for surface in TOKEN_GATE_SURFACES {
        for i in 1..=3 {
            baseline_entries.push(synth_token_sample(
                &format!("base-{surface}-sample-{i}"),
                surface,
                500.0,
                2048.0,
                150_000.0,
                85.0,
                12.0,
                0.72,
            ));
            candidate_entries.push(synth_token_sample(
                &format!("cand-{surface}-sample-{i}"),
                surface,
                500.0,
                2048.0,
                150_000.0,
                10.0,
                12.0,
                0.72,
            ));
        }
    }
    let baseline =
        parse_token_history(&build_token_history(baseline_entries)).expect("baseline parses");
    let candidate =
        parse_token_history(&build_token_history(candidate_entries)).expect("candidate parses");
    let report = evaluate_token_regression(&baseline, &candidate, 10.0);
    assert_eq!(report.decision, TokenGateDecision::Block);
}

#[test]
fn parse_token_history_rejects_missing_entries_field() {
    let raw = r#"{"runs": []}"#;
    assert!(parse_token_history(raw).is_err());
}

#[test]
fn parse_token_history_rejects_missing_surface() {
    let raw = r#"{"entries": [{"label": "test", "id": "t1", "metrics": {}}]}"#;
    assert!(parse_token_history(raw).is_err());
}

#[test]
fn min_token_gate_samples_is_three() {
    assert_eq!(MIN_TOKEN_GATE_SAMPLES, 3);
}
