"""Tests for CDP PDF certificate generator (acgs_lite.cdp.certificate)."""

from __future__ import annotations

import hashlib
import json
import sys
from typing import Any

import pytest

# ---------------------------------------------------------------------------
# Minimal fixture record
# ---------------------------------------------------------------------------


def _minimal_record() -> dict[str, Any]:
    return {
        "cdp_id": "cdp-test-0001-abcd1234",
        "tenant_id": "test-tenant",
        "subject_id": "agent:doc-reviewer",
        "action": "review_contract",
        "verdict": "allow",
        "risk_score": 0.2,
        "confidence_score": 0.92,
        "reasoning": "The requested action complies with all active policy rules.",
        "constitutional_hash": "608508a9bd224290",
        "input_hash": "sha256:deadbeef" + "0" * 56,
        "prev_cdp_hash": "genesis",
        "compliance_frameworks": ["eu_ai_act", "hipaa"],
        "matched_rules": ["rule:policy-001", "rule:policy-002"],
        "violated_rules": [],
        "maci_chain": [
            {
                "agent_id": "proposer-agent-uuid",
                "role": "proposer",
                "action": "propose",
                "outcome": "allow",
                "timestamp": "2026-04-10T12:00:00Z",
                "reasoning": "Initial proposal passed all checks.",
            },
            {
                "agent_id": "validator-agent-uuid",
                "role": "validator",
                "action": "validate",
                "outcome": "allow",
                "timestamp": "2026-04-10T12:00:01Z",
                "reasoning": "",
            },
        ],
        "compliance_evidence": [
            {
                "framework_id": "eu_ai_act",
                "article_ref": "Art.13",
                "evidence": "Transparency obligations met.",
                "compliant": True,
            }
        ],
        "runtime_obligations": [
            {
                "obligation_type": "explainability",
                "severity": "advisory",
                "framework_id": "eu_ai_act",
                "article_ref": "Art.13",
                "satisfied": True,
                "description": "Explanation provided in reasoning field.",
            }
        ],
        "intervention": None,
        "created_at": "2026-04-10T12:00:00Z",
    }


def _full_record() -> dict[str, Any]:
    """Record with all optional sections populated."""
    rec = _minimal_record()
    rec["violated_rules"] = ["rule:policy-forbidden"]
    rec["verdict"] = "deny"
    rec["risk_score"] = 0.85
    rec["intervention"] = {
        "triggered": True,
        "action": "block",
        "reason": "PHI detected without consent.",
    }
    rec["runtime_obligations"].append(
        {
            "obligation_type": "phi_guard",
            "severity": "blocking",
            "framework_id": "hipaa",
            "article_ref": "§164.502",
            "satisfied": False,
            "description": "PHI present in output.",
        }
    )
    rec["compliance_evidence"].append(
        {
            "framework_id": "hipaa",
            "article_ref": "§164.502",
            "evidence": "PHI detected.",
            "compliant": False,
        }
    )
    return rec


# ---------------------------------------------------------------------------
# Import guard
# ---------------------------------------------------------------------------

try:
    import fpdf  # noqa: F401

    HAS_FPDF = True
except ImportError:
    HAS_FPDF = False

skip_no_fpdf = pytest.mark.skipif(not HAS_FPDF, reason="fpdf2 not installed")


# ---------------------------------------------------------------------------
# Tests: ImportError path
# ---------------------------------------------------------------------------


class TestImportError:
    def test_raises_import_error_when_fpdf_missing(self) -> None:
        """If fpdf2 is not installed, generate_certificate must raise ImportError."""
        # Temporarily hide fpdf from sys.modules
        original = sys.modules.get("fpdf")
        sys.modules["fpdf"] = None  # type: ignore[assignment]
        try:
            # Force re-import so the try/except block inside generate_certificate runs
            import importlib

            import acgs_lite.cdp.certificate as cert_mod

            importlib.reload(cert_mod)
            with pytest.raises(ImportError, match="fpdf2 is required"):
                cert_mod.generate_certificate(_minimal_record())
        finally:
            if original is None:
                sys.modules.pop("fpdf", None)
            else:
                sys.modules["fpdf"] = original


# ---------------------------------------------------------------------------
# Tests: PDF output
# ---------------------------------------------------------------------------


@skip_no_fpdf
class TestGenerateCertificate:
    def test_returns_bytes(self) -> None:
        from acgs_lite.cdp.certificate import generate_certificate

        result = generate_certificate(_minimal_record())
        assert isinstance(result, bytes)

    def test_starts_with_pdf_magic_bytes(self) -> None:
        from acgs_lite.cdp.certificate import generate_certificate

        result = generate_certificate(_minimal_record())
        assert result[:4] == b"%PDF", "Output must be a valid PDF"

    def test_non_empty_output(self) -> None:
        from acgs_lite.cdp.certificate import generate_certificate

        result = generate_certificate(_minimal_record())
        assert len(result) > 1_000, "PDF should be at least 1KB"

    def test_custom_title(self) -> None:
        from acgs_lite.cdp.certificate import generate_certificate

        result = generate_certificate(_minimal_record(), title="Legal AI Audit Report")
        # Custom title should not raise; PDF is still valid
        assert result[:4] == b"%PDF"

    def test_all_verdicts(self) -> None:
        from acgs_lite.cdp.certificate import generate_certificate

        for verdict in ("allow", "deny", "conditional", "abstain", "error"):
            rec = _minimal_record()
            rec["verdict"] = verdict
            result = generate_certificate(rec)
            assert result[:4] == b"%PDF", f"Failed for verdict={verdict}"

    def test_full_record_with_intervention(self) -> None:
        from acgs_lite.cdp.certificate import generate_certificate

        result = generate_certificate(_full_record())
        assert result[:4] == b"%PDF"
        assert len(result) > 2_000

    def test_empty_maci_chain(self) -> None:
        from acgs_lite.cdp.certificate import generate_certificate

        rec = _minimal_record()
        rec["maci_chain"] = []
        rec["compliance_evidence"] = []
        result = generate_certificate(rec)
        assert result[:4] == b"%PDF"

    def test_long_reasoning_truncated(self) -> None:
        from acgs_lite.cdp.certificate import generate_certificate

        rec = _minimal_record()
        rec["reasoning"] = "X" * 2000  # well above 600-char truncation limit
        result = generate_certificate(rec)
        assert result[:4] == b"%PDF"

    def test_missing_optional_fields_graceful(self) -> None:
        """Minimal record with most optional fields absent."""
        from acgs_lite.cdp.certificate import generate_certificate

        result = generate_certificate(
            {
                "cdp_id": "cdp-bare",
                "verdict": "allow",
            }
        )
        assert result[:4] == b"%PDF"

    def test_multiple_pages_generated(self) -> None:
        """A full record should produce at least 2 pages."""

        from acgs_lite.cdp.certificate import generate_certificate

        result = generate_certificate(_full_record())
        # Re-parse the PDF bytes to count pages
        # Simplest proxy: count /Page dictionary markers
        _ = result.count(b"/Type /Page\n") + result.count(b"/Type /Page ")
        # fpdf2 may use different whitespace — just check we got a multi-KB PDF
        assert len(result) > 4_000


# ---------------------------------------------------------------------------
# Tests: Integrity hash
# ---------------------------------------------------------------------------


class TestIntegrityHash:
    def test_deterministic(self) -> None:
        from acgs_lite.cdp.certificate import _integrity_hash

        rec = _minimal_record()
        h1 = _integrity_hash(rec)
        h2 = _integrity_hash(rec)
        assert h1 == h2

    def test_changes_on_field_modification(self) -> None:
        from acgs_lite.cdp.certificate import _integrity_hash

        rec = _minimal_record()
        original = _integrity_hash(rec)
        rec["verdict"] = "deny"
        modified = _integrity_hash(rec)
        assert original != modified

    def test_sha256_hex_length(self) -> None:
        from acgs_lite.cdp.certificate import _integrity_hash

        h = _integrity_hash(_minimal_record())
        assert len(h) == 64
        # Must be valid hex
        int(h, 16)

    def test_matches_manual_computation(self) -> None:
        from acgs_lite.cdp.certificate import _integrity_hash

        rec = {"cdp_id": "test", "verdict": "allow"}
        expected = hashlib.sha256(
            json.dumps(rec, sort_keys=True, ensure_ascii=True).encode()
        ).hexdigest()
        assert _integrity_hash(rec) == expected


# ---------------------------------------------------------------------------
# Tests: Timestamp formatter
# ---------------------------------------------------------------------------


class TestFormatTs:
    def test_iso_z_format(self) -> None:
        from acgs_lite.cdp.certificate import _format_ts

        result = _format_ts("2026-04-10T12:00:00Z")
        assert "2026" in result
        assert "UTC" in result

    def test_iso_offset_format(self) -> None:
        from acgs_lite.cdp.certificate import _format_ts

        result = _format_ts("2026-04-10T12:00:00+00:00")
        assert "2026" in result

    def test_invalid_input_passthrough(self) -> None:
        from acgs_lite.cdp.certificate import _format_ts

        result = _format_ts("not-a-date")
        assert result == "not-a-date"

    def test_empty_string(self) -> None:
        from acgs_lite.cdp.certificate import _format_ts

        # Should not raise
        result = _format_ts("")
        assert isinstance(result, str)


# ---------------------------------------------------------------------------
# Tests: Public API via __init__
# ---------------------------------------------------------------------------


@skip_no_fpdf
class TestPublicExport:
    def test_importable_from_cdp_package(self) -> None:
        from acgs_lite.cdp import generate_certificate  # noqa: F401

    def test_callable_via_package(self) -> None:
        from acgs_lite.cdp import generate_certificate

        result = generate_certificate(_minimal_record())
        assert result[:4] == b"%PDF"
