"""Tests for SARIF reporter."""

import json

from mcpaudit.models import Finding, ScanResult, Severity
from mcpaudit.reporters.sarif_reporter import (
    format_sarif,
    _severity_to_sarif_level,
    _severity_to_sarif_rank,
    _confidence_to_sarif_precision,
    SARIF_VERSION,
)


class TestSarifLevelMapping:
    def test_critical_maps_to_error(self):
        assert _severity_to_sarif_level(Severity.CRITICAL) == "error"

    def test_high_maps_to_error(self):
        assert _severity_to_sarif_level(Severity.HIGH) == "error"

    def test_medium_maps_to_warning(self):
        assert _severity_to_sarif_level(Severity.MEDIUM) == "warning"

    def test_low_maps_to_note(self):
        assert _severity_to_sarif_level(Severity.LOW) == "note"

    def test_info_maps_to_note(self):
        assert _severity_to_sarif_level(Severity.INFO) == "note"


class TestSarifRankMapping:
    def test_critical_is_highest(self):
        assert _severity_to_sarif_rank(Severity.CRITICAL) == 9.5

    def test_rank_ordering(self):
        ranks = [_severity_to_sarif_rank(s) for s in Severity]
        assert ranks == sorted(ranks, reverse=True)


class TestConfidencePrecision:
    def test_high_confidence(self):
        assert _confidence_to_sarif_precision("HIGH") == "high"

    def test_medium_confidence(self):
        assert _confidence_to_sarif_precision("MEDIUM") == "medium"

    def test_low_confidence(self):
        assert _confidence_to_sarif_precision("LOW") == "low"

    def test_unknown_defaults_to_medium(self):
        assert _confidence_to_sarif_precision("UNKNOWN") == "medium"


class TestSarifOutput:
    def _make_finding(self, rule_id="CMD-001", severity=Severity.CRITICAL,
                      line=42, confidence="HIGH"):
        return Finding(
            rule_id=rule_id,
            severity=severity,
            title="Command Injection",
            description="subprocess with shell=True",
            file="server.py",
            line=line,
            code_snippet="subprocess.run(cmd, shell=True)",
            recommendation="Use list args without shell=True",
            cwe="CWE-78",
            confidence=confidence,
        )

    def test_valid_sarif_structure(self):
        finding = self._make_finding()
        result = ScanResult(file="server.py", findings=[finding],
                            lines_scanned=100, scan_time_ms=50.0)
        sarif_str = format_sarif([result])
        sarif = json.loads(sarif_str)

        assert sarif["version"] == SARIF_VERSION
        assert "$schema" in sarif
        assert len(sarif["runs"]) == 1

    def test_tool_info(self):
        result = ScanResult(file="test.py", findings=[], lines_scanned=10)
        sarif = json.loads(format_sarif([result]))
        driver = sarif["runs"][0]["tool"]["driver"]

        assert driver["name"] == "mcpaudit"
        assert "version" in driver

    def test_rules_deduplication(self):
        f1 = self._make_finding(rule_id="CMD-001", line=10)
        f2 = self._make_finding(rule_id="CMD-001", line=20)
        f3 = self._make_finding(rule_id="PATH-001", severity=Severity.HIGH, line=5)
        result = ScanResult(file="server.py", findings=[f1, f2, f3],
                            lines_scanned=100)
        sarif = json.loads(format_sarif([result]))
        rules = sarif["runs"][0]["tool"]["driver"]["rules"]

        # CMD-001 should appear only once, plus PATH-001
        rule_ids = [r["id"] for r in rules]
        assert len(rule_ids) == 2
        assert "CMD-001" in rule_ids
        assert "PATH-001" in rule_ids

    def test_results_match_findings(self):
        f1 = self._make_finding(line=10)
        f2 = self._make_finding(rule_id="SEC-001", severity=Severity.CRITICAL,
                                line=20, confidence="MEDIUM")
        result = ScanResult(file="server.py", findings=[f1, f2],
                            lines_scanned=50)
        sarif = json.loads(format_sarif([result]))
        results = sarif["runs"][0]["results"]

        assert len(results) == 2
        assert results[0]["ruleId"] == "CMD-001"
        assert results[1]["ruleId"] == "SEC-001"

    def test_location_info(self):
        finding = self._make_finding(line=42)
        result = ScanResult(file="server.py", findings=[finding],
                            lines_scanned=100)
        sarif = json.loads(format_sarif([result]))
        loc = sarif["runs"][0]["results"][0]["locations"][0]

        assert loc["physicalLocation"]["region"]["startLine"] == 42
        assert loc["physicalLocation"]["artifactLocation"]["uri"] == "server.py"

    def test_empty_results(self):
        sarif = json.loads(format_sarif([]))
        assert sarif["runs"][0]["results"] == []
        assert sarif["runs"][0]["tool"]["driver"]["rules"] == []

    def test_fix_suggestion(self):
        finding = self._make_finding()
        result = ScanResult(file="server.py", findings=[finding],
                            lines_scanned=100)
        sarif = json.loads(format_sarif([result]))
        r = sarif["runs"][0]["results"][0]

        assert "fixes" in r
        assert len(r["fixes"]) == 1

    def test_cwe_in_tags(self):
        finding = self._make_finding()
        result = ScanResult(file="server.py", findings=[finding],
                            lines_scanned=100)
        sarif = json.loads(format_sarif([result]))
        rule = sarif["runs"][0]["tool"]["driver"]["rules"][0]

        assert "CWE-78" in rule["properties"]["tags"]

    def test_confidence_in_properties(self):
        finding = self._make_finding(confidence="MEDIUM")
        result = ScanResult(file="server.py", findings=[finding],
                            lines_scanned=100)
        sarif = json.loads(format_sarif([result]))
        r = sarif["runs"][0]["results"][0]

        assert r["properties"]["confidence"] == "MEDIUM"

    def test_invocation_metadata(self):
        result = ScanResult(file="test.py", findings=[],
                            lines_scanned=50, scan_time_ms=12.5)
        sarif = json.loads(format_sarif([result]))
        inv = sarif["runs"][0]["invocations"][0]

        assert inv["executionSuccessful"] is True
        assert inv["properties"]["filesScanned"] == 1
        assert inv["properties"]["linesScanned"] == 50

    def test_security_severity_as_string(self):
        finding = self._make_finding()
        result = ScanResult(file="server.py", findings=[finding],
                            lines_scanned=100)
        sarif = json.loads(format_sarif([result]))
        r = sarif["runs"][0]["results"][0]

        # SARIF spec requires security-severity as string
        assert isinstance(r["properties"]["security-severity"], str)
