"""Architecture Lane Catalog: canonicalize catalog_item records, then compose live recipes.

No new kernel, checkout binding, storefront, or Stripe fields on catalog_item.
"""

from pathlib import Path

import pytest
import yaml
from src.domain.app_blocks.canonical import CANONICAL_BLOCK_IDS, feature_pack_only_aliases
from src.domain.app_blocks.catalogs import FEATURE_PACK_ALIASES, default_known_packs
from src.domain.app_blocks.compiler import compile_install_plan
from src.domain.app_blocks.execution import assert_install_plan_installable
from src.domain.app_blocks.generated_product_conformance_12h import (
    KernelDeniedError,
    PlatformKernel,
)
from src.domain.app_blocks.planner import ProductIntent, select_capabilities
from src.domain.app_blocks.public import public_composition
from src.domain.app_blocks.registry import load_default_registry
from src.domain.app_blocks.vertical_recipes import (
    load_recipe_catalogs,
    load_recipes,
    match_recipe_for_intent,
    match_vertical_recipe,
)
from src.domain.starter_library.catalog import known_contract_ids
from src.domain.projects.discovery_schemas import (
    Actor,
    AuthenticationSpec,
    DiscoverySpec,
    Entity,
    ProductDefinition,
    TenancySpec,
    Workflow,
)
from tests.test_catalog_product_11g2 import catalog_composition

ROOT = Path(__file__).resolve().parents[1]
RECEIPT = ROOT / "src/domain/app_blocks/recipes/catalog_04.receipt.txt"
AUDIT = ROOT / "src/domain/app_blocks/recipes/catalog_architecture_audit.receipt.txt"
REGISTRY = ROOT / "src/domain/app_blocks/platform_coverage_registry.v1.yaml"
CATALOG_RECIPES = (
    "contractor_quote_crm",
    "course_catalog",
    "service_catalog",
    "internal_service_directory",
)
CATALOG_SPINE = ("course_catalog", "service_catalog", "internal_service_directory")
GATED_COMMERCE = (
    "payments.checkout",
    "membership.portal",
)
STARTER_SLOTS = {
    "list": "record.table",
    "detail": "record.detail",
    "create": "record.create",
    "edit": "record.edit",
    "empty": "state.empty",
}


def _ids(intent: ProductIntent) -> set[str]:
    return {block_id for _cap, block_id, _reason in select_capabilities(intent)}


def test_catalog_runtime_is_canonical_without_new_kernel():
    registry = load_default_registry()
    block = registry.require("catalog.product")
    canonical = registry.canonical.require("catalog.product")
    assert block.status == "implemented"
    assert "catalog.product" not in FEATURE_PACK_ALIASES
    assert "catalog.product" not in feature_pack_only_aliases()
    assert canonical.installation.feature_packs == ("product-catalog",)
    primitives = {dep.id for dep in canonical.requires if dep.kind == "primitive"}
    assert primitives == {"data.entity"}
    assert "payment-checkout" not in canonical.installation.feature_packs
    assert "product-catalog" in default_known_packs()
    composition = catalog_composition()
    entity = composition["entities"][0]
    assert entity["entity_key"] == "catalog_item"
    keys = {field["key"] for field in entity["schema"]["fields"]}
    assert "stripe_product_id" not in keys
    assert "stripe_price_id" not in keys


def test_stripe_and_shopify_copy_are_not_this_catalog():
    miss = _ids(
        ProductIntent(purpose="Add Stripe checkout products and a Shopify product catalog later")
    )
    assert "catalog.product" not in miss
    assert match_vertical_recipe("build a stripe checkout") is None


def test_catalog_04_is_live():
    flags = {
        catalog.batch: catalog.ready_for_fast_lane_composition
        for catalog in load_recipe_catalogs()
    }
    assert flags["catalog_04"] is True
    live_ids = {recipe.id for recipe in load_recipes(composition_ready_only=True)}
    assert set(CATALOG_RECIPES) <= live_ids


def test_each_catalog_spine_recipe_compiles_catalog_item_not_checkout():
    registry = load_default_registry()
    for recipe in load_recipes(composition_ready_only=True):
        if recipe.id not in CATALOG_SPINE:
            continue
        intent = ProductIntent(
            purpose=recipe.intent_phrases[0],
            first_build_goal=recipe.intent_phrases[0],
            source_prompt=recipe.intent_phrases[0],
            staff_review=True,
        )
        matched = match_recipe_for_intent(intent)
        assert matched is not None, recipe.id
        assert matched.id == recipe.id
        selected = _ids(intent)
        assert "catalog.product" in selected
        assert set(recipe.required_app_blocks) <= selected
        assert "booking.appointment" not in selected
        plan = compile_install_plan(
            DiscoverySpec(
                product=ProductDefinition(
                    purpose=recipe.intent_phrases[0],
                    first_build_goal=f"Ship {recipe.name}",
                ),
                archetypes=["operations"],
                actors=[Actor(id="staff", name="Staff")],
                workflows=[Workflow(id="list", name="List", description=recipe.intent_phrases[0])],
                entities=[Entity(id="item", name="Item", description="A catalog item")],
                tenancy=TenancySpec(model="single_workspace"),
                authentication=AuthenticationSpec(required=False),
            ),
            source_prompt=recipe.intent_phrases[0],
        )
        assert_install_plan_installable(plan)
        assert "product-catalog" in plan.required_feature_packs
        assert "payment-checkout" not in plan.required_feature_packs
        assert {dep.id for dep in registry.canonical.require("catalog.product").requires if dep.kind == "primitive"} == {
            "data.entity"
        }


def test_contractor_quote_crm_uses_quote_form_not_catalog_or_checkout():
    intent = ProductIntent(
        purpose="contractor quote crm",
        first_build_goal="contractor quote crm",
        source_prompt="contractor quote crm",
        staff_review=True,
    )
    matched = match_recipe_for_intent(intent)
    assert matched is not None
    assert matched.id == "contractor_quote_crm"
    ids = _ids(intent)
    assert {"forms.quote_request", "crm.customer", "crm.pipeline"} <= ids
    assert "catalog.product" not in ids
    plan = compile_install_plan(
        DiscoverySpec(
            product=ProductDefinition(
                purpose="contractor quote crm",
                first_build_goal="Ship Contractor Quote and Lead Tracker",
            ),
            archetypes=["operations"],
            actors=[Actor(id="staff", name="Staff")],
            workflows=[Workflow(id="quote", name="Quote", description="contractor quote crm")],
            entities=[Entity(id="quote", name="Quote", description="A quote request")],
            tenancy=TenancySpec(model="single_workspace"),
            authentication=AuthenticationSpec(required=False),
        ),
        source_prompt="contractor quote crm",
    )
    assert_install_plan_installable(plan)
    assert "payment-checkout" not in plan.required_feature_packs


def test_course_catalog_public_copy_uses_recipe_labels():
    spec = DiscoverySpec(
        product=ProductDefinition(
            purpose="track records for a consultancy",
            first_build_goal="Staff review submitted records",
        ),
        original_intent="course catalog",
        actors=[Actor(id="staff", name="Registrar")],
        workflows=[Workflow(id="list", name="List", description="List courses")],
        entities=[Entity(id="course", name="Course", description="A course")],
        tenancy=TenancySpec(model="single_workspace"),
        authentication=AuthenticationSpec(required=False),
    )
    composition = public_composition(spec.model_dump(), source_prompt="course catalog")
    assert "Course catalog" in composition["building"]
    assert "Learner records" in composition["building"]
    assert "Course enquiry form" in composition["building"]


def test_catalog_negation_skips_recipe_catalog_block():
    ids = _ids(
        ProductIntent(
            purpose="Course catalog",
            source_prompt="Course catalog. Do not add catalog.",
            staff_review=True,
        )
    )
    assert "catalog.product" not in ids
    assert "crm.customer" in ids


def test_13a_product_catalog_gold_does_not_match_catalog_recipes():
    assert match_vertical_recipe(
        "build a product catalog for our staff. they add, inspect and edit catalog items with name and sku"
    ) is None
    ids = _ids(
        ProductIntent(
            purpose="Staff manage a product catalog",
            staff_review=True,
        )
    )
    assert "catalog.product" in ids
    assert "crm.customer" not in ids


def test_catalog_does_not_imply_commerce_or_booking():
    assert "membership.portal" not in CANONICAL_BLOCK_IDS
    assert "payments.checkout" in CANONICAL_BLOCK_IDS
    ids = _ids(
        ProductIntent(
            purpose="Staff manage a product catalog. No checkout, orders, or billing.",
            source_prompt="Staff manage a product catalog. No checkout, orders, or billing.",
            staff_review=True,
        )
    )
    assert "catalog.product" in ids
    assert "booking.appointment" not in ids
    assert "orders.order" not in ids
    assert "billing.invoice" not in ids
    assert "inventory.stock" not in ids
    assert "membership.member" not in ids
    assert not (ids & set(GATED_COMMERCE))


def test_catalog_and_booking_compose_without_merging_stores():
    prompt = (
        "Appointment booking for the receptionist and a staff product catalog. "
        "Reserve appointments and maintain business item records; no checkout or payment integration."
    )
    assert match_vertical_recipe(prompt) is None
    ids = _ids(ProductIntent(purpose=prompt, source_prompt=prompt, staff_review=True))
    assert {"booking.appointment", "catalog.product"} <= ids
    assert "crm.customer" not in ids
    assert "orders.order" not in ids
    assert "inventory.stock" not in ids
    assert not (ids & set(GATED_COMMERCE))


def test_catalog_reuses_record_starter_contracts():
    registry = load_default_registry()
    block = registry.require("catalog.product")
    contracts = {
        slot.id: slot.component_contract
        for slot in block.slots
        if slot.component_contract
    }
    assert STARTER_SLOTS.items() <= contracts.items()
    assert "catalog.grid" not in known_contract_ids()
    assert "catalog.detail" not in known_contract_ids()


def test_catalog_runtime_crud_is_staff_managed_records():
    kernel = PlatformKernel(namespace="catalog-freeze")
    kernel.as_anonymous()
    with pytest.raises(KernelDeniedError):
        kernel.create_record("catalog_item", {"name": "Hourly"})
    kernel.as_staff()
    created = kernel.create_record(
        "catalog_item",
        {
            "name": "Hourly",
            "sku": "H1",
            "price_amount": 120,
            "currency": "USD",
            "status": "active",
        },
    )
    assert created["status"] == "active"
    listed = kernel.list_records("catalog_item")
    assert listed[0]["id"] == created["id"]
    patched = kernel.patch_record("catalog_item", created["id"], {"status": "inactive", "name": "Daily"})
    read = kernel.get_record("catalog_item", created["id"])
    assert read["name"] == "Daily"
    assert read["status"] == "inactive"
    assert patched["sku"] == "H1"
    assert "stripe_product_id" not in read


def test_catalog_freeze_receipts_and_coverage_registry():
    receipt = RECEIPT.read_text(encoding="utf-8")
    audit = AUDIT.read_text(encoding="utf-8")
    for required in (
        "catalog_admitted=yes",
        "canonical_app_block=catalog.product",
        "canonical_feature_pack=product-catalog",
        "catalog_without_stripe=yes",
        "catalog_without_orders=yes",
        "family_frozen=yes",
        "ready_for_fast_lane_composition=true",
        "next_architecture_family=orders",
        "phase_10_kernel_changed=no",
    ):
        assert required in receipt, required
        assert required.replace("=true", "=yes") in audit or required in audit
    registry = yaml.safe_load(REGISTRY.read_text(encoding="utf-8"))
    assert registry["family_just_frozen"] == "visualization_calculation"
    assert registry["next_architecture_family"] == "not_yet_selected"
    assert registry["selection_action"] == "content_collection_closed"
    by_id = {row["id"]: row for row in registry["capabilities"]}
    assert by_id["catalog.product"]["status"] == "frozen"
    assert by_id["catalog.product"]["fast_lane_ready"] is True
    assert by_id["orders"]["status"] == "frozen"
    assert by_id["billing"]["status"] == "frozen"
    assert by_id["billing.invoice"]["status"] == "frozen"
    assert by_id["payment-checkout"]["status"] == "provider_specific"
    assert by_id["content_collection"]["cloud_role"] == "no_new_store"


def test_catalog_production_smoke_script():
    from scripts.architecture_lane_catalog_smoke import run_smoke

    payload = run_smoke()
    assert payload["pass"] is True, payload["failures"]
    assert payload["booking_selectable"] is True
    assert payload["catalog_selectable"] is True
    assert payload["gated_recipe_ids"] == []
