"""Tests for MCP Compliance Certificate Generator."""

from __future__ import annotations

import json

import pytest

from mcpaudit.compliance_cert import (
    AuditorInfo,
    CertificateOptions,
    CertificateResult,
    FrameworkCompliance,
    ScanResultSummary,
    _compute_content_hash,
    _compute_expiry,
    _generate_cert_id,
    _overall_status,
    _score_grade,
    _status_color,
    _status_label,
    format_certificate_json,
    generate_certificate,
    generate_certificate_with_metadata,
)


# ---- FrameworkCompliance ----

class TestFrameworkCompliance:
    def test_defaults(self):
        fc = FrameworkCompliance(name="OWASP", score=85.0)
        assert fc.status == "compliant"

    def test_auto_status_compliant(self):
        fc = FrameworkCompliance(name="test", score=90.0, status="")
        assert fc.status == "compliant"

    def test_auto_status_partial(self):
        fc = FrameworkCompliance(name="test", score=60.0, status="")
        assert fc.status == "partial"

    def test_auto_status_non_compliant(self):
        fc = FrameworkCompliance(name="test", score=30.0, status="")
        assert fc.status == "non_compliant"

    def test_explicit_status(self):
        fc = FrameworkCompliance(name="test", score=90.0, status="partial")
        assert fc.status == "partial"

    def test_to_dict(self):
        fc = FrameworkCompliance(name="OWASP", score=85.5, controls_passed=8, controls_total=10)
        d = fc.to_dict()
        assert d["name"] == "OWASP"
        assert d["score"] == 85.5
        assert d["controls_passed"] == 8

    def test_from_dict(self):
        fc = FrameworkCompliance.from_dict({"name": "SOC2", "score": 70.0, "status": "partial"})
        assert fc.name == "SOC2"
        assert fc.score == 70.0

    def test_from_dict_defaults(self):
        fc = FrameworkCompliance.from_dict({})
        assert fc.name == ""
        assert fc.score == 0.0

    def test_roundtrip(self):
        fc = FrameworkCompliance(name="ISO", score=92.0, controls_passed=18, controls_total=20)
        d = fc.to_dict()
        fc2 = FrameworkCompliance.from_dict(d)
        assert fc2.name == "ISO"
        assert fc2.score == 92.0


# ---- ScanResultSummary ----

class TestScanResultSummary:
    def test_defaults(self):
        sr = ScanResultSummary()
        assert sr.total_findings == 0
        assert sr.scan_date != ""  # auto-generated

    def test_all_fields(self):
        sr = ScanResultSummary(
            scan_date="2024-01-15T10:00:00Z",
            total_findings=25,
            critical=2, high=5, medium=10, low=8,
            score=72.5,
            target="my-server",
        )
        assert sr.total_findings == 25
        assert sr.target == "my-server"

    def test_to_dict(self):
        sr = ScanResultSummary(total_findings=10, score=80.0)
        d = sr.to_dict()
        assert d["total_findings"] == 10
        assert d["score"] == 80.0

    def test_from_dict(self):
        sr = ScanResultSummary.from_dict({"critical": 3, "score": 60.0})
        assert sr.critical == 3
        assert sr.score == 60.0

    def test_roundtrip(self):
        sr = ScanResultSummary(total_findings=5, critical=1, high=2, medium=1, low=1, score=75.0)
        d = sr.to_dict()
        sr2 = ScanResultSummary.from_dict(d)
        assert sr2.total_findings == 5
        assert sr2.critical == 1


# ---- AuditorInfo ----

class TestAuditorInfo:
    def test_defaults(self):
        a = AuditorInfo()
        assert a.name == "MCP Security Audit"
        assert a.organization == "mcpaudit"

    def test_custom(self):
        a = AuditorInfo(name="John", organization="Acme", title="Senior Auditor")
        assert a.name == "John"
        assert a.organization == "Acme"

    def test_to_dict(self):
        a = AuditorInfo(name="Test")
        d = a.to_dict()
        assert d["name"] == "Test"

    def test_from_dict(self):
        a = AuditorInfo.from_dict({"name": "Alice", "organization": "Corp"})
        assert a.name == "Alice"

    def test_from_dict_defaults(self):
        a = AuditorInfo.from_dict({})
        assert a.name == "MCP Security Audit"


# ---- CertificateOptions ----

class TestCertificateOptions:
    def test_defaults(self):
        o = CertificateOptions()
        assert o.validity_days == 90
        assert o.include_seal is True
        assert o.include_signature is True

    def test_custom(self):
        o = CertificateOptions(validity_days=30, include_seal=False)
        assert o.validity_days == 30
        assert o.include_seal is False

    def test_to_dict(self):
        o = CertificateOptions(custom_note="Test note")
        d = o.to_dict()
        assert d["custom_note"] == "Test note"


# ---- Helper functions ----

class TestHelpers:
    def test_generate_cert_id_format(self):
        cert_id = _generate_cert_id("2024-01-01", "server")
        assert cert_id.startswith("CERT-")
        assert len(cert_id) == 24  # CERT-XXXX-XXXX-XXXX-XXXX

    def test_generate_cert_id_unique(self):
        id1 = _generate_cert_id("2024-01-01", "a")
        id2 = _generate_cert_id("2024-01-02", "b")
        # Different inputs should produce different IDs
        # (though time component also differs)
        assert isinstance(id1, str)
        assert isinstance(id2, str)

    def test_compute_expiry_90_days(self):
        result = _compute_expiry("2024-01-01T00:00:00Z", 90)
        assert result.startswith("2024-03-31")

    def test_compute_expiry_30_days(self):
        result = _compute_expiry("2024-01-01T00:00:00Z", 30)
        assert result.startswith("2024-01-31")

    def test_compute_expiry_year_wrap(self):
        result = _compute_expiry("2024-12-01T00:00:00Z", 90)
        assert result.startswith("2025-")

    def test_compute_expiry_bad_format(self):
        result = _compute_expiry("bad-date", 30)
        assert result == "bad-date"

    def test_compute_content_hash(self):
        h = _compute_content_hash("test content")
        assert len(h) == 64  # SHA-256 hex

    def test_content_hash_deterministic(self):
        h1 = _compute_content_hash("same input")
        h2 = _compute_content_hash("same input")
        assert h1 == h2

    def test_content_hash_different(self):
        h1 = _compute_content_hash("input a")
        h2 = _compute_content_hash("input b")
        assert h1 != h2


# ---- _overall_status ----

class TestOverallStatus:
    def test_empty(self):
        assert _overall_status([]) == "non_compliant"

    def test_all_compliant(self):
        fws = [
            FrameworkCompliance(name="a", score=90, status="compliant"),
            FrameworkCompliance(name="b", score=85, status="compliant"),
        ]
        assert _overall_status(fws) == "compliant"

    def test_mixed(self):
        fws = [
            FrameworkCompliance(name="a", score=90, status="compliant"),
            FrameworkCompliance(name="b", score=60, status="partial"),
        ]
        assert _overall_status(fws) == "partial"

    def test_any_non_compliant(self):
        fws = [
            FrameworkCompliance(name="a", score=90, status="compliant"),
            FrameworkCompliance(name="b", score=30, status="non_compliant"),
        ]
        assert _overall_status(fws) == "non_compliant"


# ---- _status_color ----

class TestStatusColor:
    def test_compliant(self):
        assert _status_color("compliant") == "#22c55e"

    def test_partial(self):
        assert _status_color("partial") == "#f59e0b"

    def test_non_compliant(self):
        assert _status_color("non_compliant") == "#ef4444"

    def test_unknown(self):
        color = _status_color("unknown")
        assert isinstance(color, str)


# ---- _status_label ----

class TestStatusLabel:
    def test_compliant(self):
        assert _status_label("compliant") == "COMPLIANT"

    def test_partial(self):
        assert _status_label("partial") == "PARTIALLY COMPLIANT"

    def test_non_compliant(self):
        assert _status_label("non_compliant") == "NON-COMPLIANT"


# ---- _score_grade ----

class TestScoreGrade:
    def test_a(self):
        assert _score_grade(95) == "A"

    def test_b(self):
        assert _score_grade(85) == "B"

    def test_c(self):
        assert _score_grade(75) == "C"

    def test_d(self):
        assert _score_grade(65) == "D"

    def test_f(self):
        assert _score_grade(50) == "F"

    def test_boundary_90(self):
        assert _score_grade(90) == "A"

    def test_boundary_80(self):
        assert _score_grade(80) == "B"


# ---- generate_certificate ----

class TestGenerateCertificate:
    def test_minimal(self):
        html = generate_certificate({"score": 80.0})
        assert "<!DOCTYPE html>" in html
        assert "Compliance Certificate" in html

    def test_with_scan_result_object(self):
        sr = ScanResultSummary(total_findings=5, score=85.0, target="my-server")
        html = generate_certificate(sr)
        assert "my-server" in html
        assert "85.0%" in html

    def test_with_frameworks(self):
        sr = {"score": 80.0}
        fws = [{"name": "OWASP Agentic", "score": 90.0, "status": "compliant"}]
        html = generate_certificate(sr, fws)
        assert "OWASP Agentic" in html
        assert "COMPLIANT" in html

    def test_with_framework_objects(self):
        sr = ScanResultSummary(score=75.0)
        fws = [FrameworkCompliance(name="SOC2", score=80.0)]
        html = generate_certificate(sr, fws)
        assert "SOC2" in html

    def test_with_auditor(self):
        html = generate_certificate(
            {"score": 80.0},
            auditor={"name": "Jane Doe", "organization": "SecureCo"},
        )
        assert "Jane Doe" in html
        assert "SecureCo" in html

    def test_with_auditor_object(self):
        aud = AuditorInfo(name="Carlos", organization="Acme")
        html = generate_certificate({"score": 80.0}, auditor=aud)
        assert "Carlos" in html

    def test_with_options_dict(self):
        html = generate_certificate(
            {"score": 80.0},
            options={"validity_days": 30, "custom_note": "Test note"},
        )
        assert "Test note" in html
        assert "30 days" in html

    def test_with_options_object(self):
        opts = CertificateOptions(validity_days=60, custom_note="Important")
        html = generate_certificate({"score": 80.0}, options=opts)
        assert "Important" in html

    def test_no_seal(self):
        html = generate_certificate(
            {"score": 80.0},
            options={"include_seal": False},
        )
        # CSS class defs remain, but the actual seal div should not appear
        assert '<div class="seal">' not in html

    def test_no_signature(self):
        html = generate_certificate(
            {"score": 80.0},
            options={"include_signature": False},
        )
        # CSS class defs remain, but the actual signature block div should not appear
        assert '<div class="signature-block">' not in html

    def test_certificate_id_in_html(self):
        html = generate_certificate(
            {"score": 80.0},
            options={"certificate_id": "CERT-TEST-1234"},
        )
        assert "CERT-TEST-1234" in html

    def test_content_hash_in_html(self):
        html = generate_certificate({"score": 80.0})
        assert "SHA-256:" in html

    def test_severity_badges(self):
        sr = {"critical": 2, "high": 5, "medium": 10, "low": 3, "score": 60.0}
        html = generate_certificate(sr)
        assert "Critical: 2" in html
        assert "High: 5" in html

    def test_multiple_frameworks(self):
        fws = [
            {"name": "OWASP", "score": 90.0},
            {"name": "SOC2", "score": 75.0},
            {"name": "ISO 27001", "score": 60.0},
        ]
        html = generate_certificate({"score": 75.0}, fws)
        assert "OWASP" in html
        assert "SOC2" in html
        assert "ISO 27001" in html

    def test_grade_display(self):
        html = generate_certificate({"score": 95.0})
        assert "Grade: A" in html

    def test_controls_display(self):
        fws = [FrameworkCompliance(name="T", score=80.0, controls_passed=8, controls_total=10)]
        html = generate_certificate({"score": 80.0}, fws)
        assert "8/10" in html


# ---- generate_certificate_with_metadata ----

class TestGenerateCertificateWithMetadata:
    def test_returns_certificate_result(self):
        result = generate_certificate_with_metadata({"score": 80.0})
        assert isinstance(result, CertificateResult)
        assert result.html != ""
        assert result.certificate_id.startswith("CERT-")

    def test_issue_and_expiry_dates(self):
        result = generate_certificate_with_metadata({"score": 80.0})
        assert result.issue_date != ""
        assert result.expiry_date != ""

    def test_content_hash(self):
        result = generate_certificate_with_metadata({"score": 80.0})
        assert len(result.content_hash) == 64

    def test_overall_status_compliant(self):
        fws = [FrameworkCompliance(name="a", score=90.0, status="compliant")]
        result = generate_certificate_with_metadata({"score": 90.0}, fws)
        assert result.overall_status == "compliant"

    def test_overall_status_non_compliant(self):
        fws = [FrameworkCompliance(name="a", score=30.0, status="non_compliant")]
        result = generate_certificate_with_metadata({"score": 30.0}, fws)
        assert result.overall_status == "non_compliant"

    def test_to_dict(self):
        result = generate_certificate_with_metadata({"score": 80.0})
        d = result.to_dict()
        assert "certificate_id" in d
        assert "issue_date" in d
        assert "content_hash" in d

    def test_with_all_options(self):
        sr = ScanResultSummary(score=85.0, target="server")
        fws = [FrameworkCompliance(name="OWASP", score=90.0)]
        aud = AuditorInfo(name="Test")
        opts = CertificateOptions(validity_days=60)
        result = generate_certificate_with_metadata(sr, fws, aud, opts)
        assert result.overall_status == "compliant"
        assert "OWASP" in result.html

    def test_custom_cert_id(self):
        opts = CertificateOptions(certificate_id="MY-CERT-001")
        result = generate_certificate_with_metadata({"score": 80.0}, options=opts)
        assert result.certificate_id == "MY-CERT-001"


# ---- format_certificate_json ----

class TestFormatCertificateJSON:
    def test_valid_json(self):
        result = generate_certificate_with_metadata({"score": 80.0})
        json_str = format_certificate_json(result)
        data = json.loads(json_str)
        assert "certificate_id" in data
        assert "content_hash" in data

    def test_contains_all_fields(self):
        result = generate_certificate_with_metadata({"score": 80.0})
        json_str = format_certificate_json(result)
        data = json.loads(json_str)
        assert "issue_date" in data
        assert "expiry_date" in data
        assert "overall_status" in data
