//! JSON-formatted output for verification reports.

use crate::report::object_value_to_json;
use crate::verify::{AuditVerdict, VerifyReport};

/// Format a verification report as JSON.
pub fn format_verify_json(report: &VerifyReport, show_unclaimed: bool) -> String {
    let results: Vec<serde_json::Value> = report
        .results
        .iter()
        .filter(|r| r.verdict != AuditVerdict::Unclaimed || show_unclaimed)
        .map(|r| {
            let observations: Vec<serde_json::Value> = r
                .matching_observations
                .iter()
                .map(|o| {
                    serde_json::json!({
                        "concept_path": o.concept_path,
                        "predicate": o.predicate,
                        "value": object_value_to_json(&o.value),
                        "file": o.file,
                        "line": o.line,
                        "confidence": o.confidence,
                    })
                })
                .collect();

            if let Some(ref claim) = r.claim {
                serde_json::json!({
                    "claim_id": claim.id,
                    "concept_path": claim.concept_path,
                    "predicate": claim.predicate,
                    "verdict": r.verdict,
                    "comparison": claim.comparison.to_string(),
                    "explanation": r.explanation,
                    "matching_observations": observations,
                })
            } else {
                // Unclaimed observation — no backing claim
                let obs = r.matching_observations.first();
                serde_json::json!({
                    "claim_id": serde_json::Value::Null,
                    "concept_path": obs.map(|o| o.concept_path.as_str()).unwrap_or(""),
                    "predicate": obs.map(|o| o.predicate.as_str()).unwrap_or(""),
                    "verdict": r.verdict,
                    "comparison": serde_json::Value::Null,
                    "explanation": r.explanation,
                    "matching_observations": observations,
                })
            }
        })
        .collect();

    let output = serde_json::json!({
        "summary": {
            "total_claims": report.summary.total_claims,
            "pass": report.summary.pass,
            "conflict": report.summary.conflict,
            "missing": report.summary.missing,
            "unclaimed": report.summary.unclaimed,
        },
        "results": results,
    });

    serde_json::to_string_pretty(&output).unwrap_or_else(|_| "{}".to_string())
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::verify::{VerifyReport, VerifySummary};

    #[test]
    fn test_empty_report_json() {
        let report = VerifyReport { results: vec![], summary: VerifySummary::default() };
        let output = format_verify_json(&report, false);
        let parsed: serde_json::Value = serde_json::from_str(&output).expect("valid json");
        assert_eq!(parsed["summary"]["total_claims"], 0);
        assert_eq!(parsed["results"].as_array().map(|a| a.len()), Some(0));
    }
}
