"""Tests for the Compliance Checker module."""

from __future__ import annotations

import json

import pytest

from mcpaudit.compliance_checker import (
    ComplianceChecker,
    ComplianceReport,
    Control,
    ControlResult,
    ControlStatus,
    Framework,
)


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

def _finding(rule_id: str = "SEC-001", severity: str = "HIGH") -> dict:
    return {"rule_id": rule_id, "severity": severity, "file": "test.py", "line": 1}


# ===========================================================================
# Framework
# ===========================================================================

class TestFramework:
    def test_all_values(self):
        assert len(Framework) == 5

    def test_values(self):
        assert Framework.OWASP_AGENTIC.value == "owasp_agentic"
        assert Framework.SOC2.value == "soc2"


# ===========================================================================
# ControlStatus
# ===========================================================================

class TestControlStatus:
    def test_all_values(self):
        assert len(ControlStatus) == 5


# ===========================================================================
# Control
# ===========================================================================

class TestControl:
    def test_to_dict(self):
        c = Control("C1", "Test", Framework.OWASP_AGENTIC, "desc", "HIGH", ("SEC-001",))
        d = c.to_dict()
        assert d["control_id"] == "C1"
        assert d["framework"] == "owasp_agentic"
        assert d["related_rules"] == ["SEC-001"]


# ===========================================================================
# ControlResult
# ===========================================================================

class TestControlResult:
    def test_to_dict(self):
        c = Control("C1", "Test", Framework.OWASP_AGENTIC)
        r = ControlResult(control=c, status=ControlStatus.PASS, evidence=["clean"])
        d = r.to_dict()
        assert d["status"] == "PASS"
        assert d["evidence"] == ["clean"]


# ===========================================================================
# ComplianceReport
# ===========================================================================

class TestComplianceReport:
    def test_to_dict(self):
        r = ComplianceReport(
            framework=Framework.OWASP_AGENTIC,
            controls_checked=5,
            controls_passed=3,
            score=60.0,
        )
        d = r.to_dict()
        assert d["framework"] == "owasp_agentic"
        assert d["score"] == 60.0

    def test_to_json(self):
        r = ComplianceReport(framework=Framework.SOC2, score=100.0)
        j = r.to_json()
        data = json.loads(j)
        assert data["framework"] == "soc2"


# ===========================================================================
# ComplianceChecker - basic
# ===========================================================================

class TestComplianceCheckerBasic:
    def test_get_frameworks(self):
        cc = ComplianceChecker()
        frameworks = cc.get_frameworks()
        assert Framework.OWASP_AGENTIC in frameworks
        assert len(frameworks) >= 5

    def test_get_controls(self):
        cc = ComplianceChecker()
        controls = cc.get_controls(Framework.OWASP_AGENTIC)
        assert len(controls) >= 5

    def test_add_control(self):
        cc = ComplianceChecker()
        custom = Control("CUSTOM-01", "Custom", Framework.OWASP_AGENTIC)
        initial = len(cc.get_controls(Framework.OWASP_AGENTIC))
        cc.add_control(custom)
        assert len(cc.get_controls(Framework.OWASP_AGENTIC)) == initial + 1


# ===========================================================================
# ComplianceChecker - check framework
# ===========================================================================

class TestComplianceCheckerCheckFramework:
    def test_clean_scan(self):
        cc = ComplianceChecker()
        report = cc.check_framework(Framework.OWASP_AGENTIC, [])
        assert report.controls_failed == 0
        assert report.score == 100.0

    def test_violations_detected(self):
        cc = ComplianceChecker()
        findings = [_finding("CMD-001"), _finding("CMD-002"), _finding("PRIV-001")]
        report = cc.check_framework(Framework.OWASP_AGENTIC, findings)
        assert report.controls_failed >= 1

    def test_partial_violation(self):
        cc = ComplianceChecker()
        findings = [_finding("CMD-001")]  # only one of the related rules
        report = cc.check_framework(Framework.OWASP_AGENTIC, findings)
        # OA-01 has rules CMD-001, CMD-002, PRIV-001 — only CMD-001 triggered = PARTIAL
        has_partial = any(r.status == ControlStatus.PARTIAL for r in report.results)
        assert has_partial

    def test_gaps_for_unmapped(self):
        cc = ComplianceChecker()
        report = cc.check_framework(Framework.NIST_AI_RMF, [])
        # NIST controls have no related_rules by default
        assert len(report.gaps) >= 1

    def test_soc2(self):
        cc = ComplianceChecker()
        report = cc.check_framework(Framework.SOC2, [])
        assert report.framework == Framework.SOC2

    def test_iso27001(self):
        cc = ComplianceChecker()
        report = cc.check_framework(Framework.ISO_27001, [])
        assert report.framework == Framework.ISO_27001

    def test_eu_ai_act(self):
        cc = ComplianceChecker()
        report = cc.check_framework(Framework.EU_AI_ACT, [])
        assert report.framework == Framework.EU_AI_ACT


# ===========================================================================
# ComplianceChecker - check all
# ===========================================================================

class TestComplianceCheckerCheckAll:
    def test_check_all(self):
        cc = ComplianceChecker()
        reports = cc.check_all([])
        assert len(reports) >= 5

    def test_check_all_with_findings(self):
        cc = ComplianceChecker()
        reports = cc.check_all([_finding("CMD-001")])
        owasp = next(r for r in reports if r.framework == Framework.OWASP_AGENTIC)
        assert owasp.controls_checked > 0


# ===========================================================================
# ComplianceChecker - report
# ===========================================================================

class TestComplianceCheckerReport:
    def test_markdown_clean(self):
        cc = ComplianceChecker()
        report = cc.check_framework(Framework.OWASP_AGENTIC, [])
        md = cc.markdown_report(report)
        assert "# Compliance Report" in md
        assert "OWASP_AGENTIC" in md
        assert "Score" in md

    def test_markdown_with_failures(self):
        cc = ComplianceChecker()
        findings = [_finding("CMD-001"), _finding("CMD-002"), _finding("PRIV-001")]
        report = cc.check_framework(Framework.OWASP_AGENTIC, findings)
        md = cc.markdown_report(report)
        assert "FAIL" in md

    def test_markdown_with_gaps(self):
        cc = ComplianceChecker()
        report = cc.check_framework(Framework.NIST_AI_RMF, [])
        md = cc.markdown_report(report)
        assert "Coverage Gaps" in md
