//! SARIF v2.1.0 schema validation tests.
//!
//! Validate every flavor of report we render against a vendored subset of
//! the official SARIF v2.1.0 JSON Schema. The full schema is several
//! thousand lines, so we vendor only the structural surface mcptest
//! populates and pin its source of truth in the fixture header. When the
//! reporter starts emitting a new field, extend the fixture in the same
//! commit so the gate stays meaningful.

use jsonschema::Validator;
use mcptest_core::report::reporters::SarifReporter;
use mcptest_core::report::{Report, Reporter};
use serde_json::Value;

fn load_schema() -> Validator {
    let schema_path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
        .join("tests")
        .join("fixtures")
        .join("sarif-v2.1.0.schema.json");
    let body = std::fs::read_to_string(&schema_path).expect("vendored SARIF schema exists");
    let schema: Value = serde_json::from_str(&body).expect("schema parses as JSON");
    jsonschema::validator_for(&schema).expect("SARIF schema compiles")
}

fn render_sarif(report: &Report) -> Value {
    let mut buf = Vec::new();
    SarifReporter::default()
        .render(report, &mut buf)
        .expect("SARIF render");
    let s = String::from_utf8(buf).expect("UTF-8");
    serde_json::from_str(&s).expect("rendered SARIF parses")
}

#[test]
fn rendered_sarif_for_sample_report_validates_against_schema() {
    let validator = load_schema();
    let doc = render_sarif(&Report::sample());
    let errors: Vec<_> = validator.iter_errors(&doc).collect();
    if !errors.is_empty() {
        for e in &errors {
            eprintln!("SARIF validation error: {e}");
        }
        panic!("rendered SARIF did not validate; see errors above");
    }
}

#[test]
fn rendered_sarif_for_sample_full_report_validates_against_schema() {
    let validator = load_schema();
    let doc = render_sarif(&Report::sample_full());
    let errors: Vec<_> = validator.iter_errors(&doc).collect();
    if !errors.is_empty() {
        for e in &errors {
            eprintln!("SARIF validation error: {e}");
        }
        panic!("rendered SARIF (sample_full) did not validate; see errors above");
    }
}

#[test]
fn rendered_sarif_for_all_pass_report_validates_against_schema() {
    let validator = load_schema();
    let doc = render_sarif(&Report::sample_all_pass());
    let errors: Vec<_> = validator.iter_errors(&doc).collect();
    if !errors.is_empty() {
        for e in &errors {
            eprintln!("SARIF validation error: {e}");
        }
        panic!("rendered SARIF (sample_all_pass) did not validate; see errors above");
    }
}

#[test]
fn rendered_sarif_carries_compliance_rule_definitions() {
    let doc = render_sarif(&Report::sample_full());
    let rules = doc["runs"][0]["tool"]["driver"]["rules"]
        .as_array()
        .expect("rules array present");
    // Every rule id we emit in sample_full() must appear in the rules
    // definition list with a populated short description and help URI.
    let mut ids: Vec<&str> = rules.iter().map(|r| r["id"].as_str().unwrap()).collect();
    ids.sort();
    assert_eq!(ids, vec!["EDGE-005", "PROTO-001", "RES-001", "TOOL-001"]);
    for rule in rules {
        assert!(!rule["shortDescription"]["text"]
            .as_str()
            .unwrap_or("")
            .is_empty());
        let help = rule["helpUri"].as_str().unwrap();
        assert!(help.contains(rule["id"].as_str().unwrap()));
    }
}

#[test]
fn rendered_sarif_level_is_one_of_error_warning_note_or_none() {
    let doc = render_sarif(&Report::sample_full());
    for result in doc["runs"][0]["results"].as_array().unwrap() {
        let level = result["level"].as_str().unwrap();
        assert!(matches!(level, "error" | "warning" | "note" | "none"));
    }
}

#[test]
fn malformed_sarif_fails_schema_validation() {
    // Sanity check: hand-construct a SARIF doc missing the required
    // `tool` field and prove the validator rejects it. Otherwise a
    // future regression could leave the validator green by accident.
    let validator = load_schema();
    let bad: Value = serde_json::json!({
        "$schema": "https://example.com/sarif.json",
        "version": "2.1.0",
        "runs": [{ "results": [] }]
    });
    let errors: Vec<_> = validator.iter_errors(&bad).collect();
    assert!(
        !errors.is_empty(),
        "validator should reject a run missing `tool`"
    );
}
