"""Closure witnesses, not a second implementation of the 12H certifier.

The checked-in inventory is the reviewed admission boundary. Changing it needs
explicit admission; normal presentation work must not grow semantic catalogs.
"""

import json
import re
from collections import Counter
from pathlib import Path

from src.domain.app_blocks.canonical import CANONICAL_BLOCK_IDS
from src.domain.app_blocks.catalogs import DEFAULT_KNOWN_PACKS
from src.domain.app_blocks.registry import load_default_registry
from src.domain.codegen.legacy_contact_generation import (
    ARTIFACT_CLASSIFICATION,
    LEGACY_CONTACT_GENERATION,
)
from src.domain.feature_packs.catalog_v2 import CATALOG_OVERLAYS
from src.domain.starter_library.catalog import list_contracts

ROOT = Path(__file__).resolve().parents[1]
FIXTURES = ROOT / "docs/audits/fixtures"
LEDGER = json.loads((FIXTURES / "phase_12_final_debt_ledger.json").read_text(encoding="utf-8"))


def test_reviewed_ontology_matches_live_catalogs():
    # GR-05 (Phase 13, explicitly authorized -- see docs/audits/GR05_EXIT_REVIEW.md)
    # admitted support.ticket over the pre-existing support-inbox Feature Pack
    # (19 -> 20). GR-06 (see docs/audits/GR06_EXIT_REVIEW.md) admitted
    # workflow.approval over the pre-existing approval-workflow Feature Pack
    # (20 -> 21). feature_packs/starter_contracts counts are unchanged in
    # both cases since the underlying packs and every reused starter
    # contract already existed. This is the "explicit admission" this
    # test's own docstring requires before the reviewed inventory may change.
    inventory = LEDGER["inventory"]
    # Architecture Lane Reporting admits reporting.dashboard (25 -> 26) and its
    # backing pack reporting-dashboards (30 -> 31); starter_contracts is unchanged
    # since the block reuses only already-admitted record.table/detail/create/edit
    # and state.empty contracts.
    # Capability Model Phase 2 admits crm.organization (26 -> 27) and its
    # backing pack organization-records (31 -> 32); same starter-contract reuse.
    # Capability Model Phase 3 admits crm.quote, collaboration.comment,
    # governance.group, customer_success.account (27 -> 31) and their backing
    # packs quote-lifecycle, comment-threads, governance-groups,
    # customer-success-accounts (32 -> 36); same starter-contract reuse.
    # Capability Model Phase 4 admits contract.contract (31 -> 32), freeze
    # deliberately lifted -- see docs/audits/ARCHITECTURE_LANE_CONTRACT.md.
    # Capability Model Phase 5 admits workforce.worker (32 -> 33), freeze
    # deliberately lifted -- see docs/audits/ARCHITECTURE_LANE_WORKFORCE.md.
    # Wave 5 admitted payments.checkout over the existing payment-checkout pack
    # (33 -> 34); feature_packs count is unchanged.
    assert len(inventory["app_blocks"]) == 36
    assert len(inventory["feature_packs"]) == 39
    # 47 at 12I close; FL-D2 admitted record.calendar (47 -> 48).
    # List chrome cluster: layout.page_header, filter.bar, nav.pagination,
    # status.badge (48 -> 52). record.list composes that cluster (52 -> 53).
    # record.card is the narrow-viewport list host (53 -> 54).
    # search.box is filter chrome, not a Search product (54 -> 55).
    # Architecture Lane Visualization+Calculation admits chart.bar/line/pie/area,
    # metric.card, calculator.formula (55 -> 61).
    assert len(inventory["starter_contracts"]) == 61
    assert set(inventory["app_blocks"]) == CANONICAL_BLOCK_IDS
    assert load_default_registry().canonical.ids == CANONICAL_BLOCK_IDS
    assert set(inventory["feature_packs"]) == DEFAULT_KNOWN_PACKS == set(CATALOG_OVERLAYS)
    assert inventory["starter_contracts"] == {c.id: c.layer for c in list_contracts()}
    candidates = {
        "pipeline.board",
        "task.board",
        "calendar.schedule",
        "notification.inbox",
        "dashboard.analytics",
    }
    assert candidates.isdisjoint(CANONICAL_BLOCK_IDS)
    assert candidates.isdisjoint(inventory["starter_contracts"])


def test_public_hosts_keep_declared_form_authority():
    # Guard the resolved H-C defect at its shared host owner. A source witness
    # supplements, and does not replace, the existing runtime/auth proofs.
    hosts = {
        "boilerplates/nuxt_app/components/starter/blocks/ContactPublicForm.vue": ["contact"],
        "boilerplates/nuxt_app/components/starter/blocks/QuotePublicForm.vue": ["quote_request"],
        "boilerplates/react_app/src/components/starter/blocks.jsx": ["contact", "quote_request"],
        "boilerplates/html/js/starter-blocks.js": ["contact", "quote_request"],
    }
    for path, form_keys in hosts.items():
        source = (ROOT / path).read_text(encoding="utf-8")
        assert "submitContact" not in source, path
        assert "/api/contact" not in source, path
        for key in form_keys:
            assert re.search(
                rf"submitGeneratedForm\(\s*\{{\s*formKey:\s*[\"\x27]{key}[\"\x27]",
                source,
            ), (path, key)


def test_compatibility_is_not_new_generated_product_authority():
    assert LEGACY_CONTACT_GENERATION["new_project_generation"] == "canonical_only"
    assert LEGACY_CONTACT_GENERATION["legacy_runtime"] == "compatibility_only"
    assert ARTIFACT_CLASSIFICATION["p2hs-app-client.submitContact"] == "supported_existing_only"
    assert ARTIFACT_CLASSIFICATION["p2hs-app-client.submitGeneratedForm"] == "canonical_new_project"
    assert ARTIFACT_CLASSIFICATION["contact-capture-notify"] == "canonical_new_project"
    classifications = {
        row["surface"]: row["classification"] for row in LEDGER["compatibility_surfaces"]
    }
    assert classifications["submitContact"] == "compatibility_only"
    assert classifications["submitGeneratedForm"] == "canonical"
    assert classifications["contact-capture-notify"] == "canonical"


def test_debt_reconciliation_has_no_lost_ux_or_double_counted_resolved_p1():
    items = LEDGER["items"]
    ids = [item["id"] for item in items]
    assert len(ids) == len(set(ids))
    expected_ux = {f"UX-{n:02d}" for n in range(1, 19)} - {"UX-14"}
    assert {
        i["id"] for i in items if i["origin_phase"] == "12G" and i["current_severity"] == "P2"
    } == expected_ux
    assert {"UX-14", "AF-3", "H-C"}.isdisjoint(ids)
    required = {
        "id",
        "origin_phase",
        "description",
        "current_severity",
        "current_evidence",
        "architecture_impact",
        "runtime_impact",
        "product_impact",
        "owner_layer",
        "recommended_future_phase",
        "status",
        "bucket",
    }
    for item in items:
        assert required <= item.keys()
        assert all(item[key] for key in required)
        assert item["bucket"] in {"D2", "D3", "D4", "D5", "D6"}
        assert item["architecture_blocking"] is False
    counts = Counter(i["bucket"] for i in items)
    assert LEDGER["summary"]["bucket_counts"] == {f"D{n}": counts[f"D{n}"] for n in range(1, 7)}
    for severity in ("P2", "P3"):
        assert LEDGER["summary"][f"remaining_{severity.lower()}"] == sum(
            i["current_severity"] == severity for i in items
        )
    assert sum(i["id"] == "PL-01" for i in items) == 1
    assert (
        sum(c["classification"] == "compatibility_only" for c in LEDGER["compatibility_surfaces"])
        == LEDGER["summary"]["compatibility_only_surfaces"]
    )
    assert LEDGER["summary"]["capability_candidates_not_admitted"] == counts["D4"]


def test_checked_in_matrix_supports_closure_without_recertification():
    matrix = json.loads((FIXTURES / "12h_certification_matrix.json").read_text(encoding="utf-8"))
    cells = matrix["cells"]
    assert len(cells) == 18
    assert {(c["archetype"], c["starter"]) for c in cells} == {
        (a, s) for a in "ABCDEF" for s in ("nuxt_app", "react_app", "html")
    }
    for key in (
        "planned_must_surfaces",
        "generated_surfaces",
        "mounted_must_surfaces",
        "reachable_must_surfaces",
    ):
        assert sum(c[key] for c in cells) == 273
    for cell in cells:
        assert (
            cell["planned_must_surfaces"]
            == cell["generated_surfaces"]
            == cell["mounted_must_surfaces"]
            == cell["reachable_must_surfaces"]
        )
        assert cell["result"] == "pass"
        assert cell["missing_must_surfaces"] == []
        assert cell["unexpected_semantic_surfaces"] == []
        assert cell["private_substitute_count"] == 0
    # Historical measurement, not a ceiling on future authorized improvements.
    # Was 15/18 (archetype A only) at Phase 12I closure; the post-B2 residual
    # remediation pass (Phase 13 -- 13A-034 fix, GR-04 phrase widening, the
    # "potential clients" narrowing) improved general matcher precision/recall
    # and, as a side effect, closed the remaining 3 archetype-A cells. See
    # docs/audits/POST_B2_RESIDUAL_RECONCILIATION_RECEIPT.md.
    assert sum(c["planner_intent_match"] == "pass" for c in cells) == 18
    assert {c["archetype"] for c in cells if c["planner_intent_match"] != "pass"} == set()
