"""Unit tests for SARIF generator."""

from unittest.mock import MagicMock

import pytest

from tools.export_sarif.generator import (
    LENS_TO_RULE_PREFIX,
    OPTIX_INFO_URI,
    OPTIX_VERSION,
    SARIF_SCHEMA,
    SARIF_VERSION,
    SEVERITY_TO_LEVEL,
    SarifGenerator,
)
from tools.generate_report.models import AuditLens
from tools.workflow.state import WorkflowState


class TestSeverityMapping:
    """Tests for severity to SARIF level mapping."""

    def test_critical_maps_to_error(self):
        assert SEVERITY_TO_LEVEL["critical"] == "error"

    def test_high_maps_to_error(self):
        assert SEVERITY_TO_LEVEL["high"] == "error"

    def test_medium_maps_to_warning(self):
        assert SEVERITY_TO_LEVEL["medium"] == "warning"

    def test_low_maps_to_note(self):
        assert SEVERITY_TO_LEVEL["low"] == "note"

    def test_info_maps_to_note(self):
        assert SEVERITY_TO_LEVEL["info"] == "note"


class TestLensToRulePrefix:
    """Tests for lens to rule prefix mapping."""

    def test_security_lens_prefix(self):
        assert LENS_TO_RULE_PREFIX[AuditLens.SECURITY] == "SEC"

    def test_a11y_lens_prefix(self):
        assert LENS_TO_RULE_PREFIX[AuditLens.A11Y] == "A11Y"

    def test_principal_lens_prefix(self):
        assert LENS_TO_RULE_PREFIX[AuditLens.PRINCIPAL] == "PRIN"

    def test_devops_lens_prefix(self):
        assert LENS_TO_RULE_PREFIX[AuditLens.DEVOPS] == "DEVOPS"


@pytest.fixture
def mock_consolidated():
    mock = MagicMock()
    mock.issues_found = []
    return mock


@pytest.fixture
def workflow_state(mock_consolidated):
    state = WorkflowState(
        continuation_id="test-uuid",
        tool_name="security_audit",
    )
    state.consolidated = mock_consolidated
    state.is_finished = True
    return state


class TestSarifGenerator:
    """Tests for SarifGenerator class."""

    def test_generate_basic_structure(self, workflow_state, mock_consolidated):
        generator = SarifGenerator(workflow_state, AuditLens.SECURITY)
        result = generator.generate()

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

    def test_generate_tool_driver_info(self, workflow_state, mock_consolidated):
        generator = SarifGenerator(workflow_state, AuditLens.SECURITY)
        result = generator.generate()

        driver = result["runs"][0]["tool"]["driver"]
        assert driver["name"] == "Optix"
        assert driver["version"] == OPTIX_VERSION
        assert driver["informationUri"] == OPTIX_INFO_URI
        assert "rules" in driver

    def test_generate_empty_results_when_no_issues(self, workflow_state, mock_consolidated):
        mock_consolidated.issues_found = []
        generator = SarifGenerator(workflow_state, AuditLens.SECURITY)
        result = generator.generate()

        assert result["runs"][0]["results"] == []

    def test_generate_single_finding(self, workflow_state, mock_consolidated):
        mock_consolidated.issues_found = [
            {
                "severity": "high",
                "category": "SQL Injection",
                "description": "Unsanitized user input in query",
                "affected_files": ["src/db.py"],
                "cwe_id": "CWE-89",
            }
        ]
        generator = SarifGenerator(workflow_state, AuditLens.SECURITY)
        result = generator.generate()

        results = result["runs"][0]["results"]
        assert len(results) == 1
        assert results[0]["ruleId"] == "CWE-89"
        assert results[0]["level"] == "error"
        assert results[0]["message"]["text"] == "Unsanitized user input in query"

    def test_generate_location_from_affected_files(self, workflow_state, mock_consolidated):
        mock_consolidated.issues_found = [
            {
                "severity": "medium",
                "category": "XSS",
                "description": "XSS vulnerability",
                "affected_files": ["src/views.py", "src/templates.py"],
            }
        ]
        generator = SarifGenerator(workflow_state, AuditLens.SECURITY)
        result = generator.generate()

        locations = result["runs"][0]["results"][0]["locations"]
        assert len(locations) == 2
        assert locations[0]["physicalLocation"]["artifactLocation"]["uri"] == "src/views.py"
        assert locations[1]["physicalLocation"]["artifactLocation"]["uri"] == "src/templates.py"

    def test_generate_location_with_line_numbers(self, workflow_state, mock_consolidated):
        mock_consolidated.issues_found = [
            {
                "severity": "critical",
                "category": "Hardcoded Secret",
                "description": "API key hardcoded",
                "affected_files": [
                    {"file_path": "src/config.py", "line_start": 42, "line_end": 45}
                ],
            }
        ]
        generator = SarifGenerator(workflow_state, AuditLens.SECURITY)
        result = generator.generate()

        location = result["runs"][0]["results"][0]["locations"][0]
        region = location["physicalLocation"]["region"]
        assert region["startLine"] == 42
        assert region["endLine"] == 45

    def test_generate_rules_for_cwe(self, workflow_state, mock_consolidated):
        mock_consolidated.issues_found = [
            {
                "severity": "high",
                "category": "SQL Injection",
                "description": "SQL injection",
                "affected_files": ["src/db.py"],
                "cwe_id": "CWE-89",
            }
        ]
        generator = SarifGenerator(workflow_state, AuditLens.SECURITY)
        result = generator.generate()

        rules = result["runs"][0]["tool"]["driver"]["rules"]
        assert len(rules) == 1
        assert rules[0]["id"] == "CWE-89"
        assert "cwe.mitre.org" in rules[0]["helpUri"]

    def test_generate_rules_for_wcag(self, workflow_state, mock_consolidated):
        workflow_state.tool_name = "a11y_audit"
        mock_consolidated.issues_found = [
            {
                "severity": "medium",
                "category": "ARIA",
                "description": "Missing ARIA label",
                "affected_files": ["src/Button.tsx"],
                "wcag_criterion": "1.1.1",
            }
        ]
        generator = SarifGenerator(workflow_state, AuditLens.A11Y)
        result = generator.generate()

        rules = result["runs"][0]["tool"]["driver"]["rules"]
        assert len(rules) == 1
        assert rules[0]["id"] == "WCAG-1.1.1"

    def test_generate_rules_fallback_prefix(self, workflow_state, mock_consolidated):
        mock_consolidated.issues_found = [
            {
                "severity": "low",
                "category": "Code Style",
                "description": "Inconsistent naming",
                "affected_files": ["src/utils.py"],
            }
        ]
        generator = SarifGenerator(workflow_state, AuditLens.PRINCIPAL)
        result = generator.generate()

        rules = result["runs"][0]["tool"]["driver"]["rules"]
        assert rules[0]["id"] == "PRIN-CODE_STYLE"

    def test_generate_deduplicates_rules(self, workflow_state, mock_consolidated):
        mock_consolidated.issues_found = [
            {
                "severity": "high",
                "category": "SQL Injection",
                "description": "First SQL injection",
                "affected_files": ["src/db1.py"],
                "cwe_id": "CWE-89",
            },
            {
                "severity": "high",
                "category": "SQL Injection",
                "description": "Second SQL injection",
                "affected_files": ["src/db2.py"],
                "cwe_id": "CWE-89",
            },
        ]
        generator = SarifGenerator(workflow_state, AuditLens.SECURITY)
        result = generator.generate()

        rules = result["runs"][0]["tool"]["driver"]["rules"]
        assert len(rules) == 1
        assert generator.get_rules_count() == 1

    def test_generate_fingerprints_from_finding_id(self, workflow_state, mock_consolidated):
        mock_consolidated.issues_found = [
            {
                "severity": "medium",
                "category": "Auth",
                "description": "Weak password policy",
                "affected_files": ["src/auth.py"],
                "finding_id": "OPX-ABC12345",
            }
        ]
        generator = SarifGenerator(workflow_state, AuditLens.SECURITY)
        result = generator.generate()

        fingerprints = result["runs"][0]["results"][0]["fingerprints"]
        assert fingerprints["optix/finding_id"] == "OPX-ABC12345"

    def test_generate_skips_empty_description(self, workflow_state, mock_consolidated):
        mock_consolidated.issues_found = [
            {
                "severity": "low",
                "category": "Test",
                "description": "",
                "affected_files": ["src/test.py"],
            }
        ]
        generator = SarifGenerator(workflow_state, AuditLens.SECURITY)
        result = generator.generate()

        assert len(result["runs"][0]["results"]) == 0

    def test_generate_handles_file_path_fallback(self, workflow_state, mock_consolidated):
        mock_consolidated.issues_found = [
            {
                "severity": "medium",
                "category": "Test",
                "description": "Test issue",
                "file_path": "src/legacy.py",
            }
        ]
        generator = SarifGenerator(workflow_state, AuditLens.SECURITY)
        result = generator.generate()

        locations = result["runs"][0]["results"][0]["locations"]
        assert len(locations) == 1
        assert locations[0]["physicalLocation"]["artifactLocation"]["uri"] == "src/legacy.py"

    def test_generate_handles_location_fallback(self, workflow_state, mock_consolidated):
        mock_consolidated.issues_found = [
            {
                "severity": "medium",
                "category": "Test",
                "description": "Test issue",
                "location": "src/old.py",
            }
        ]
        generator = SarifGenerator(workflow_state, AuditLens.SECURITY)
        result = generator.generate()

        locations = result["runs"][0]["results"][0]["locations"]
        assert len(locations) == 1
        assert locations[0]["physicalLocation"]["artifactLocation"]["uri"] == "src/old.py"

    def test_get_rules_count(self, workflow_state, mock_consolidated):
        mock_consolidated.issues_found = [
            {"severity": "high", "category": "Cat1", "description": "Issue 1", "affected_files": ["a.py"]},
            {"severity": "medium", "category": "Cat2", "description": "Issue 2", "affected_files": ["b.py"]},
            {"severity": "low", "category": "Cat1", "description": "Issue 3", "affected_files": ["c.py"]},
        ]
        generator = SarifGenerator(workflow_state, AuditLens.SECURITY)
        generator.generate()

        assert generator.get_rules_count() == 2

    def test_generate_no_consolidated_data(self, workflow_state):
        workflow_state.consolidated = None
        generator = SarifGenerator(workflow_state, AuditLens.SECURITY)
        result = generator.generate()

        assert result["runs"][0]["results"] == []
