//! JSON output format for programmatic consumption.
//!
//! Produces a complete JSON document with summary, conflicts,
//! and full detail for each conflict including claim and source info.

use super::{object_value_to_json, verdict_label, ReportFormatter};
use crate::types::{ScanResult, Verdict};

/// JSON report formatter.
pub struct JsonReport;

impl ReportFormatter for JsonReport {
    fn format(&self, result: &ScanResult) -> String {
        let conflicts_json: Vec<serde_json::Value> = result
            .conflicts
            .iter()
            .map(|conflict| {
                let sources: Vec<serde_json::Value> = conflict
                    .conflicts
                    .iter()
                    .map(|source| {
                        let mut source_json = serde_json::json!({
                            "path": source.path,
                            "source_class": format!("{:?}", source.source_class),
                            "tier": source.source_class.tier(),
                            "value": object_value_to_json(&source.value),
                            "confidence": source.confidence,
                        });
                        // Add RFC citation if available
                        if let Some(citation) = &source.rfc_citation {
                            source_json["rfc_citation"] =
                                serde_json::Value::String(citation.clone());
                        }
                        // Add policy source if this came from a Trust Pack
                        if let Some(ps) = &source.policy_source {
                            let mut policy_source = serde_json::json!({
                                "pack_name": ps.pack_name,
                                "pack_version": ps.pack_version,
                                "issuer_hex": ps.issuer_hex,
                            });
                            // Add signer_name if available
                            if let Some(signer) = &ps.signer_name {
                                policy_source["signer_name"] =
                                    serde_json::Value::String(signer.clone());
                            }
                            // Add contact if available
                            if let Some(contact) = &ps.contact {
                                policy_source["contact"] =
                                    serde_json::Value::String(contact.clone());
                            }
                            source_json["policy_source"] = policy_source;
                        }
                        source_json
                    })
                    .collect();

                let mut conflict_json = serde_json::json!({
                    "concept_path": conflict.claim.concept_path,
                    "predicate": conflict.claim.predicate,
                    "value": object_value_to_json(&conflict.claim.value),
                    "file": conflict.claim.file,
                    "line": conflict.claim.line,
                    "matched_text": conflict.claim.matched_text,
                    "confidence": conflict.claim.confidence,
                    "description": conflict.claim.description,
                    "conflict_score": conflict.conflict_score,
                    "verdict": verdict_label(conflict.verdict),
                    "sources": sources,
                });

                if let Some(ack) = &conflict.acknowledged {
                    let mut ack_json = serde_json::json!({
                        "timestamp": ack.timestamp,
                        "by": ack.by,
                        "reason": ack.reason,
                        "expired": ack.expired,
                    });
                    if let Some(exp) = &ack.expires_at {
                        ack_json["expires_at"] = serde_json::Value::String(exp.clone());
                    }
                    conflict_json["acknowledged"] = ack_json;
                }

                conflict_json
            })
            .collect();

        // Build drifts array
        let drifts_json: Vec<serde_json::Value> = result
            .drifts
            .iter()
            .map(|drift| {
                serde_json::json!({
                    "concept_path": drift.claim.concept_path,
                    "predicate": drift.claim.predicate,
                    "current_value": object_value_to_json(&drift.claim.value),
                    "prior_value": object_value_to_json(&drift.prior.value),
                    "current_file": drift.claim.file,
                    "current_line": drift.claim.line,
                    "prior_file": drift.prior.file,
                    "prior_line": drift.prior.line,
                    "prior_timestamp": drift.prior.timestamp,
                    "verdict": verdict_label(drift.verdict),
                })
            })
            .collect();

        // Build deprecated_usages array
        let deprecated_json: Vec<serde_json::Value> = result
            .deprecated_usages
            .iter()
            .map(|usage| {
                let mut json = serde_json::json!({
                    "pattern_id": usage.pattern_id.to_string(),
                    "pattern_name": usage.pattern_name,
                    "file_path": usage.file_path,
                    "line": usage.line,
                    "reason": usage.reason,
                    "severity": usage.severity(),
                });

                if let Some(ref s) = usage.superseded_by {
                    json["superseded_by"] = serde_json::Value::String(s.clone());
                }
                if let Some(ref g) = usage.migration_guide {
                    json["migration_guide"] = serde_json::Value::String(g.clone());
                }
                if let Some(days) = usage.days_until_sunset {
                    json["days_until_sunset"] = serde_json::json!(days);
                }

                json
            })
            .collect();

        let mut report = serde_json::json!({
            "project": result.project,
            "scan_id": result.scan_id,
            "summary": {
                "files_scanned": result.files_scanned,
                "claims_extracted": result.claims_extracted,
                "conflicts": result.conflicts.len(),
                "blocks": result.count_by_verdict(Verdict::Block),
                "flags": result.count_by_verdict(Verdict::Flag),
                "acks": result.count_by_verdict(Verdict::Ack),
                "passes": result.count_by_verdict(Verdict::Pass),
                "drifts": result.drift_count(),
                "deprecated_usages": result.deprecated_usage_count(),
                "observations_recorded": result.observations_recorded,
            },
            "conflicts": conflicts_json,
            "drifts": drifts_json,
            "deprecated_usages": deprecated_json,
        });

        // Add claims if present
        if let Some(claims) = &result.claims {
            let claims_json: Vec<serde_json::Value> = claims
                .iter()
                .map(|claim| {
                    serde_json::json!({
                        "concept_path": claim.concept_path,
                        "predicate": claim.predicate,
                        "value": object_value_to_json(&claim.value),
                        "file": claim.file,
                        "line": claim.line,
                        "matched_text": claim.matched_text,
                        "confidence": claim.confidence,
                        "description": claim.description,
                    })
                })
                .collect();
            report["claims"] = serde_json::json!(claims_json);
        }

        // Add timing if benchmark mode was enabled
        if let Some(timing) = &result.timing {
            let mut timing_json = serde_json::json!({
                "walk_ms": timing.walk_ms,
                "extraction_ms": timing.extraction_ms,
                "conflict_ms": timing.conflict_ms,
                "total_ms": timing.total_ms,
            });
            if let Some(loc) = timing.lines_of_code {
                timing_json["lines_of_code"] = serde_json::json!(loc);
            }
            report["timing"] = timing_json;
        }

        // Pretty-print for readability
        serde_json::to_string_pretty(&report).unwrap_or_else(|_| report.to_string())
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::types::{ConflictResult, ConflictingSource, ExtractedClaim};
    use stemedb_core::types::{ObjectValue, SourceClass};

    #[test]
    fn test_json_output_structure() {
        let formatter = JsonReport;
        let result = ScanResult {
            project: "testproject".to_string(),
            scan_id: "scan-456".to_string(),
            files_scanned: 10,
            claims_extracted: 3,
            conflicts: vec![ConflictResult {
                claim: ExtractedClaim {
                    concept_path: "code://rust/test/jwt/aud".to_string(),
                    predicate: "enabled".to_string(),
                    value: ObjectValue::Boolean(false),
                    file: "src/auth.rs".to_string(),
                    line: 15,
                    matched_text: "validate_aud = false".to_string(),
                    confidence: 1.0,
                    description: "JWT audience validation disabled".to_string(),
                },
                conflicts: vec![ConflictingSource {
                    path: "rfc://7519/jwt/audience_validation".to_string(),
                    source_class: SourceClass::Regulatory,
                    value: ObjectValue::Boolean(true),
                    confidence: 1.0,
                    rfc_citation: Some("RFC 7519".to_string()),
                    policy_source: None,
                }],
                conflict_score: 0.92,
                verdict: Verdict::Block,
                acknowledged: None,
                trace: None,
            }],
            drifts: vec![],
            format: "json".to_string(),
            debug: false,
            observations_recorded: 0,
            timing: None,
            claims: None,
            deprecated_usages: vec![],
        };

        let output = formatter.format(&result);
        let parsed: serde_json::Value = serde_json::from_str(&output).expect("valid json");

        assert_eq!(parsed["project"], "testproject");
        assert_eq!(parsed["summary"]["conflicts"], 1);
        assert_eq!(parsed["summary"]["deprecated_usages"], 0);
        assert_eq!(parsed["summary"]["blocks"], 1);
        assert_eq!(parsed["conflicts"][0]["verdict"], "BLOCK");
        assert_eq!(parsed["conflicts"][0]["file"], "src/auth.rs");
        assert_eq!(parsed["conflicts"][0]["sources"][0]["tier"], 0);
    }

    #[test]
    fn test_json_empty_conflicts() {
        let formatter = JsonReport;
        let result = ScanResult::stub(&std::path::PathBuf::from("empty"), "json");
        let output = formatter.format(&result);
        let parsed: serde_json::Value = serde_json::from_str(&output).expect("valid json");

        assert_eq!(parsed["conflicts"].as_array().map(|a| a.len()), Some(0));
    }
}
