"""Verify backend readiness for every marketing capability bundle.

This is intentionally stricter than the marketing buildability harvest.  A page
can have a valid composition vector while still carrying an integration or
specialist-runtime gap.  This report makes those gaps visible to Core/Cloud/Dash
work without treating credentials as implementation failures.
"""

from __future__ import annotations

import json
from pathlib import Path

from scripts.build_marketing_capability_matrix import build_matrix
from scripts.harvest_marketing_product_coverage import harvest
from src.domain.app_blocks.registry import load_default_registry
from src.domain.feature_packs.normalize import load_pack_contract
from src.domain.starter_library.archetypes import known_archetype_ids

ROOT = Path(__file__).resolve().parents[1]
OUT = ROOT / "docs/coverage/BACKEND_BUNDLE_READINESS.json"

# These mean the implementation is present and only a user/provider connection is
# needed. Everything else remains an explicit platform or domain gap until proven.
CONFIGURATION_ONLY = frozenset(
    {
        "stripe_subscription",
        "stripe_charge",
        "member_price",
        "claims_payment",
        "paid_booking",
        "rent_ach_collection",
        "bank_payout",
        "supplier_edi",
        "mls_sync",
        "ad_platform_capture",
        "social_platform_credentials",
        "certificate_authority",
        "automatic_certificate_authority",
        "esign_provider_required_for_signature_authority",
        "publishing_api",
        "google_docs_clone",
        "live_gps",
        "remote_provisioning",
    }
)

# Keep each open gap actionable. Some are architecture-gated families or
# claim boundaries, not missing generic App Blocks.
ARCHITECTURE_DECISION_GAPS = frozenset(
    {
        "member_portal",
        "entitlements",
        "entitlement_enforcement",
        "membership.member_as_employee",
        "membership.member_from_board_member_wording",
        "attendance_access_gate",
        "lms_runtime",
        "auto_sent_ai_replies",
        "payments.checkout",
    }
)
PROVIDER_INTEGRATION_GAPS = frozenset(
    {
        "regulated_airworthiness_authority",
    }
)
SPECIALIST_BACKEND_GAPS = frozenset(
    {
        "clinical_ehr",
        "clinical_record",
        "ehr",
        "hipaa_claim",
        "insurance_claims",
        "insurance_adjudication",
        "regulated_lims_claim",
        "regulated_lending_decisioning",
        "loan_servicing_posting",
        "aviation_release_to_service",
        "manufacturing_mrp",
        "finite_capacity_scheduling",
        "tms_optimization",
        "warehouse_bin_wms",
        "wave_picking_optimization",
        "takeoff_engine",
        "three_way_match_engine",
        "bi_warehouse",
        "general_ledger",
        "payroll_posting",
    }
)
REUSABLE_PRIMITIVE_GAPS = frozenset(
    {
        "atp_reservation",
    }
)
CLAIM_BOUNDARY_GAPS = frozenset(
    {"account_reset_side_effects", "private_case_backend", "raw_password_collection"}
)

EXTERNAL_PROVIDER_GAPS = frozenset(
    {
        "esign_provider_required_for_signature_authority",
        "stripe_charge",
        "stripe_subscription",
        "paid_booking",
        "social_platform_credentials",
    }
)
EXTERNAL_SYSTEM_GAPS = frozenset(
    {
        "ad_platform_capture",
        "google_docs_clone",
        "live_gps",
        "mls_sync",
        "publishing_api",
        "remote_provisioning",
    }
)
UNIMPLEMENTED_EXTERNAL_SEMANTICS = frozenset(
    {
        "bank_payout",
        "claims_payment",
        "member_price",
        "rent_ach_collection",
    }
)

WORK_QUEUE_PRIORITY = {
    "reusable_primitive": 10,
    "provider_integration": 20,
    "architecture_decision": 30,
    "specialist_backend": 40,
    "claim_boundary": 50,
}

WORK_QUEUE_ACTION = {
    "reusable_primitive": "implement_shared_core_cloud_contract",
    "provider_integration": "implement_approved_provider_adapter",
    "architecture_decision": "admit_or_reframe_architecture_before_implementation",
    "specialist_backend": "design_and_admit_specialist_backend_contract",
    "claim_boundary": "remove_or_rewrite_unsupported_claim",
}


def gap_category(token: str) -> str:
    if token in ARCHITECTURE_DECISION_GAPS:
        return "architecture_decision"
    if token in PROVIDER_INTEGRATION_GAPS:
        return "provider_integration"
    if token in SPECIALIST_BACKEND_GAPS:
        return "specialist_backend"
    if token in REUSABLE_PRIMITIVE_GAPS:
        return "reusable_primitive"
    if token in CLAIM_BOUNDARY_GAPS:
        return "claim_boundary"
    return "review_required"


def external_requirement(
    *, configuration_providers: list[str], configuration_gaps: list[str], platform_gaps: list[str]
) -> str:
    """Classify external dependency without turning it into a native primitive."""
    if configuration_providers and not configuration_gaps and not platform_gaps:
        return "credential_configuration"
    tokens = set(configuration_gaps) | set(platform_gaps)
    if tokens & UNIMPLEMENTED_EXTERNAL_SEMANTICS:
        return "unimplemented_external_semantics"
    if tokens & EXTERNAL_SYSTEM_GAPS:
        return "external_system_dependency"
    if tokens & EXTERNAL_PROVIDER_GAPS:
        return "external_provider_required"
    if configuration_providers:
        return "credential_configuration"
    return "none"


def readiness_metadata(
    *, status: str, configuration_providers: list[str], configuration_gaps: list[str], platform_gaps: list[str]
) -> dict[str, str]:
    requirement = external_requirement(
        configuration_providers=configuration_providers,
        configuration_gaps=configuration_gaps,
        platform_gaps=platform_gaps,
    )
    external = requirement != "none"
    return {
        "platform_readiness": "external_capability_required" if external else "native",
        "external_requirement": requirement,
        "degraded_path": "available" if status == "configuration_required" else "unavailable",
        "marketing_claim_mode": "integration" if external else "native",
    }


def build_readiness() -> dict:
    coverage = harvest()
    matrix = build_matrix()
    registry = load_default_registry()
    manifests = {manifest.id: manifest for manifest in registry.all()}
    archetypes = known_archetype_ids()
    pages = {row["slug"]: row for row in coverage["pages"]}
    bundles: list[dict] = []

    for bundle in matrix["bundles"]:
        rows = [pages[slug] for slug in bundle["pages"]]
        missing_blocks = sorted(
            block_id
            for block_id in bundle["app_blocks"]
            if block_id not in manifests or not manifests[block_id].implemented
        )
        pack_errors: list[str] = []
        for pack_id in bundle["feature_packs"]:
            try:
                contract = load_pack_contract(pack_id, {})
            except Exception as exc:  # pragma: no cover - report should name bad catalog data
                pack_errors.append(f"{pack_id}:{exc}")
                continue
            if not contract.provide_ids() and not contract.entities and not contract.events:
                pack_errors.append(f"{pack_id}:empty_contract")

        starters = sorted({row["starter"] for row in rows if row["starter"]})
        missing_starters = sorted(set(starters) - archetypes)
        mismatches = sorted({item for row in rows for item in row["mismatches"]})
        gaps = sorted({item for row in rows for item in row["gaps"]})
        configuration_gaps = sorted(set(mismatches) & CONFIGURATION_ONLY)
        platform_gaps = sorted(set(mismatches) - CONFIGURATION_ONLY | set(gaps))

        if missing_blocks or pack_errors or missing_starters:
            status = "backend_contract_error"
        elif platform_gaps:
            status = "open_platform_gap"
        elif configuration_gaps:
            status = "configuration_required"
        else:
            status = "ready"

        metadata = readiness_metadata(
            status=status,
            configuration_providers=bundle["configuration_providers"],
            configuration_gaps=configuration_gaps,
            platform_gaps=platform_gaps,
        )
        bundles.append(
            {
                "bundle_id": bundle["bundle_id"],
                "pages": bundle["pages"],
                "page_count": bundle["page_count"],
                "app_blocks": bundle["app_blocks"],
                "feature_packs": bundle["feature_packs"],
                "primitive_ids": bundle["primitive_ids"],
                "configuration_providers": bundle["configuration_providers"],
                "starters": starters,
                "missing_blocks": missing_blocks,
                "pack_errors": pack_errors,
                "missing_starters": missing_starters,
                "configuration_gaps": configuration_gaps,
                "platform_gaps": platform_gaps,
                "gap_categories": {token: gap_category(token) for token in platform_gaps},
                **metadata,
                "status": status,
            }
        )

    gap_bundles: dict[str, set[str]] = {}
    gap_pages: dict[str, set[str]] = {}
    for bundle in bundles:
        for token in bundle["platform_gaps"]:
            gap_bundles.setdefault(token, set()).add(bundle["bundle_id"])
            gap_pages.setdefault(token, set()).update(bundle["pages"])
    gap_register = [
        {
            "token": token,
            "category": gap_category(token),
            "bundle_count": len(gap_bundles[token]),
            "page_count": len(gap_pages[token]),
            "bundles": sorted(gap_bundles[token]),
            "pages": sorted(gap_pages[token]),
        }
        for token in sorted(gap_bundles)
    ]
    work_queue = [
        {
            **item,
            "priority": WORK_QUEUE_PRIORITY[item["category"]],
            "recommended_action": WORK_QUEUE_ACTION[item["category"]],
        }
        for item in sorted(
            gap_register,
            key=lambda item: (
                WORK_QUEUE_PRIORITY[item["category"]],
                -item["page_count"],
                item["token"],
            ),
        )
    ]

    page_register = [
        {
            "slug": page["slug"],
            "bundle_id": bundle["bundle_id"],
            "app_blocks": bundle["app_blocks"],
            "feature_packs": bundle["feature_packs"],
            "primitive_ids": bundle["primitive_ids"],
            "configuration_providers": bundle["configuration_providers"],
            "starter": page.get("starter"),
            "status": bundle["status"],
            "configuration_gaps": bundle["configuration_gaps"],
            "platform_gaps": bundle["platform_gaps"],
            "gap_categories": bundle["gap_categories"],
            "platform_readiness": bundle["platform_readiness"],
            "external_requirement": bundle["external_requirement"],
            "degraded_path": bundle["degraded_path"],
            "marketing_claim_mode": bundle["marketing_claim_mode"],
        }
        for bundle in bundles
        for page in (pages[slug] for slug in bundle["pages"])
    ]

    return {
        "schema_version": 1,
        "source": "docs/coverage/MARKETING_CAPABILITY_MATRIX.json",
        "page_count": coverage["page_count"],
        "bundle_count": len(bundles),
        "status_counts": {
            status: sum(item["status"] == status for item in bundles)
            for status in sorted({item["status"] for item in bundles})
        },
        "gap_register": gap_register,
        "work_queue": work_queue,
        "page_register": page_register,
        "bundles": bundles,
    }


def main() -> int:
    payload = build_readiness()
    OUT.parent.mkdir(parents=True, exist_ok=True)
    OUT.write_text(json.dumps(payload, indent=2) + "\n", encoding="utf-8")
    print(
        f"backend readiness pages={payload['page_count']} bundles={payload['bundle_count']} "
        f"statuses={payload['status_counts']}"
    )
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
