//! SARIF 2.1.0 output format for CI integration
//!
//! Produces Static Analysis Results Interchange Format (SARIF) reports
//! compatible with GitHub Code Scanning, Azure DevOps, and other CI tools.

use serde::Serialize;

use super::{Severity, ValidationReport, ValidationResult};

/// SARIF 2.1.0 report
#[derive(Debug, Serialize)]
pub struct SarifReport {
    /// SARIF version (always "2.1.0")
    pub version: String,
    /// Schema URI
    #[serde(rename = "$schema")]
    pub schema: String,
    /// Analysis runs
    pub runs: Vec<SarifRun>,
}

/// A single analysis run
#[derive(Debug, Serialize)]
pub struct SarifRun {
    /// Tool that produced the results
    pub tool: SarifTool,
    /// Validation results
    pub results: Vec<SarifResult>,
}

/// Tool descriptor
#[derive(Debug, Serialize)]
pub struct SarifTool {
    /// Tool driver info
    pub driver: SarifDriver,
}

/// Tool driver metadata
#[derive(Debug, Serialize)]
pub struct SarifDriver {
    /// Tool name
    pub name: String,
    /// Tool version
    pub version: String,
    /// Rule descriptors
    pub rules: Vec<SarifRuleDescriptor>,
}

/// Rule metadata in SARIF
#[derive(Debug, Serialize)]
pub struct SarifRuleDescriptor {
    /// Rule identifier (e.g., "AX-001")
    pub id: String,
    /// Rule short name
    pub name: String,
    /// Short description
    #[serde(rename = "shortDescription")]
    pub short_description: SarifMessage,
}

/// A single SARIF result
#[derive(Debug, Serialize)]
pub struct SarifResult {
    /// Rule identifier
    #[serde(rename = "ruleId")]
    pub rule_id: String,
    /// Severity level ("error", "warning", "note")
    pub level: String,
    /// Result message
    pub message: SarifMessage,
    /// Locations where the issue was found
    pub locations: Vec<SarifLocation>,
}

/// SARIF message
#[derive(Debug, Serialize)]
pub struct SarifMessage {
    /// Message text
    pub text: String,
}

/// Location of a result
#[derive(Debug, Serialize)]
pub struct SarifLocation {
    /// Physical file location
    #[serde(rename = "physicalLocation")]
    pub physical_location: SarifPhysicalLocation,
}

/// Physical location in a file
#[derive(Debug, Serialize)]
pub struct SarifPhysicalLocation {
    /// File path
    #[serde(rename = "artifactLocation")]
    pub artifact_location: SarifArtifactLocation,
}

/// Artifact (file) location
#[derive(Debug, Serialize)]
pub struct SarifArtifactLocation {
    /// URI or file path
    pub uri: String,
}

/// Convert a `Severity` to SARIF level string
fn severity_to_sarif_level(severity: Severity) -> &'static str {
    match severity {
        Severity::Fail => "error",
        Severity::Warn => "warning",
        Severity::Info | Severity::Pass => "note",
    }
}

/// Convert a `ValidationReport` into a SARIF report.
///
/// Each failed or warned validation result becomes a SARIF result entry.
/// The `file_uri` is attached to every result location.
#[must_use]
pub fn to_sarif(report: &ValidationReport, file_uri: &str) -> SarifReport {
    let mut rule_descriptors = Vec::new();
    let mut seen_rules = std::collections::HashSet::new();

    // Collect unique rule descriptors
    for result in &report.results {
        if seen_rules.insert(result.rule_code.clone()) {
            rule_descriptors.push(SarifRuleDescriptor {
                id: result.rule_code.clone(),
                name: result.rule_name.clone(),
                short_description: SarifMessage {
                    text: result.rule_name.clone(),
                },
            });
        }
    }

    // Convert results (only non-passing)
    let sarif_results: Vec<SarifResult> = report
        .results
        .iter()
        .filter(|r| !r.passed)
        .map(|r| validation_result_to_sarif(r, file_uri))
        .collect();

    SarifReport {
        version: "2.1.0".to_string(),
        schema: "https://json.schemastore.org/sarif-2.1.0.json".to_string(),
        runs: vec![SarifRun {
            tool: SarifTool {
                driver: SarifDriver {
                    name: "mcp-gateway-validate".to_string(),
                    version: env!("CARGO_PKG_VERSION").to_string(),
                    rules: rule_descriptors,
                },
            },
            results: sarif_results,
        }],
    }
}

/// Convert a single `ValidationResult` to a SARIF result
fn validation_result_to_sarif(result: &ValidationResult, file_uri: &str) -> SarifResult {
    let message_text = if result.issues.is_empty() {
        result.rule_name.clone()
    } else {
        result.issues.join("; ")
    };

    SarifResult {
        rule_id: result.rule_code.clone(),
        level: severity_to_sarif_level(result.severity).to_string(),
        message: SarifMessage { text: message_text },
        locations: vec![SarifLocation {
            physical_location: SarifPhysicalLocation {
                artifact_location: SarifArtifactLocation {
                    uri: file_uri.to_string(),
                },
            },
        }],
    }
}

/// Convert a collection of validation results from multiple files to a single SARIF report.
///
/// Each entry in `file_results` is `(file_uri, results_for_that_file)`.
#[must_use]
pub fn to_sarif_multi(file_results: &[(&str, &[ValidationResult])]) -> SarifReport {
    let mut rule_descriptors = Vec::new();
    let mut seen_rules = std::collections::HashSet::new();
    let mut sarif_results = Vec::new();

    for &(file_uri, results) in file_results {
        for result in results {
            if seen_rules.insert(result.rule_code.clone()) {
                rule_descriptors.push(SarifRuleDescriptor {
                    id: result.rule_code.clone(),
                    name: result.rule_name.clone(),
                    short_description: SarifMessage {
                        text: result.rule_name.clone(),
                    },
                });
            }

            if !result.passed {
                sarif_results.push(validation_result_to_sarif(result, file_uri));
            }
        }
    }

    SarifReport {
        version: "2.1.0".to_string(),
        schema: "https://json.schemastore.org/sarif-2.1.0.json".to_string(),
        runs: vec![SarifRun {
            tool: SarifTool {
                driver: SarifDriver {
                    name: "mcp-gateway-validate".to_string(),
                    version: env!("CARGO_PKG_VERSION").to_string(),
                    rules: rule_descriptors,
                },
            },
            results: sarif_results,
        }],
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::validator::report::ValidationReport;

    fn make_result(
        code: &str,
        name: &str,
        tool: &str,
        passed: bool,
        severity: Severity,
    ) -> ValidationResult {
        let mut r = ValidationResult::new(code, name, tool);
        if !passed {
            r.add_issue("Test issue");
        }
        r.severity = severity;
        r
    }

    #[test]
    fn sarif_report_has_correct_version() {
        let report = ValidationReport::from_results(
            1,
            vec![make_result(
                "AX-001",
                "Rule 1",
                "tool1",
                true,
                Severity::Pass,
            )],
        );

        let sarif = to_sarif(&report, "test.yaml");
        assert_eq!(sarif.version, "2.1.0");
        assert_eq!(sarif.runs.len(), 1);
    }

    #[test]
    fn sarif_report_includes_only_failures() {
        let report = ValidationReport::from_results(
            1,
            vec![
                make_result("AX-001", "Rule 1", "tool1", true, Severity::Pass),
                make_result("AX-002", "Rule 2", "tool1", false, Severity::Fail),
                make_result("AX-003", "Rule 3", "tool1", false, Severity::Warn),
            ],
        );

        let sarif = to_sarif(&report, "test.yaml");
        assert_eq!(sarif.runs[0].results.len(), 2);
    }

    #[test]
    fn sarif_severity_mapping() {
        assert_eq!(severity_to_sarif_level(Severity::Fail), "error");
        assert_eq!(severity_to_sarif_level(Severity::Warn), "warning");
        assert_eq!(severity_to_sarif_level(Severity::Info), "note");
        assert_eq!(severity_to_sarif_level(Severity::Pass), "note");
    }

    #[test]
    fn sarif_result_has_file_location() {
        let report = ValidationReport::from_results(
            1,
            vec![make_result(
                "AX-001",
                "Rule 1",
                "tool1",
                false,
                Severity::Fail,
            )],
        );

        let sarif = to_sarif(&report, "capabilities/search/brave.yaml");
        let result = &sarif.runs[0].results[0];
        assert_eq!(
            result.locations[0].physical_location.artifact_location.uri,
            "capabilities/search/brave.yaml"
        );
    }

    #[test]
    fn sarif_rule_descriptors_are_unique() {
        let report = ValidationReport::from_results(
            2,
            vec![
                make_result("AX-001", "Rule 1", "tool1", false, Severity::Fail),
                make_result("AX-001", "Rule 1", "tool2", false, Severity::Fail),
            ],
        );

        let sarif = to_sarif(&report, "test.yaml");
        assert_eq!(sarif.runs[0].tool.driver.rules.len(), 1);
    }

    #[test]
    fn sarif_multi_aggregates_files() {
        let results_a = vec![make_result(
            "AX-001",
            "Rule 1",
            "tool1",
            false,
            Severity::Fail,
        )];
        let results_b = vec![make_result(
            "AX-002",
            "Rule 2",
            "tool2",
            false,
            Severity::Warn,
        )];

        let sarif = to_sarif_multi(&[("file_a.yaml", &results_a), ("file_b.yaml", &results_b)]);

        assert_eq!(sarif.runs[0].results.len(), 2);
        assert_eq!(sarif.runs[0].tool.driver.rules.len(), 2);
    }

    #[test]
    fn sarif_serializes_to_valid_json() {
        let report = ValidationReport::from_results(
            1,
            vec![make_result(
                "AX-001",
                "Rule 1",
                "tool1",
                false,
                Severity::Fail,
            )],
        );

        let sarif = to_sarif(&report, "test.yaml");
        let json = serde_json::to_string_pretty(&sarif);
        assert!(json.is_ok());

        let json_str = json.unwrap();
        assert!(json_str.contains("\"version\": \"2.1.0\""));
        assert!(json_str.contains("\"$schema\""));
    }
}
