"""Architecture Lane Booking: canonicalize existing runtime, then compose live recipes.

No new kernel, store, ACL, or visitor booking BFF.
"""

from pathlib import Path

from src.domain.app_blocks.canonical import DUAL_NAMESPACE_IDS
from src.domain.app_blocks.catalogs import FEATURE_PACK_ALIASES
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.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.cloud.service import _REQUEST_TYPE_POLICY
from src.domain.feature_packs.contract import CATALOG_PRIMITIVES
from src.domain.projects.discovery_schemas import (
    Actor,
    AuthenticationSpec,
    DiscoverySpec,
    Entity,
    ProductDefinition,
    TenancySpec,
    Workflow,
)

ROOT = Path(__file__).resolve().parents[1]
CLOUD = ROOT.parent / "P2HSCloud"
BOOKING_RECIPES = (
    "clinic_booking",
    "consultant_booking",
    "tutor_scheduling",
    "salon_booking",
    "home_service_booking",
    "professional_booking",
)
BILLED_BOOKING_RECIPES = ("booking_crm_finance_join",)


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


def test_booking_runtime_is_canonical_without_new_kernel():
    registry = load_default_registry()
    block = registry.require("booking.appointment")
    canonical = registry.canonical.require("booking.appointment")
    assert block.status == "implemented"
    assert "booking.appointment" in DUAL_NAMESPACE_IDS
    assert FEATURE_PACK_ALIASES["booking.appointment"] == "appointment-booking"
    assert canonical.installation.feature_packs == ("appointment-booking",)
    primitives = {dep.id for dep in canonical.requires if dep.kind == "primitive"}
    assert primitives == {"booking.reserve", "booking.cancel", "booking.availability"}
    assert "data.entity" not in primitives
    assert {"booking.reserve", "booking.cancel", "booking.availability"} <= CATALOG_PRIMITIVES
    assert "booking.reserve" in _REQUEST_TYPE_POLICY
    assert "booking.cancel" in _REQUEST_TYPE_POLICY
    catalog = ROOT / "src/integrations/contracts/platform-port-catalog.json"
    ports = catalog.read_text(encoding="utf-8")
    assert "/api/bookings/reserve" in ports
    assert "/api/bookings/:reservation_id/cancel" in ports
    assert "/api/bookings/resources/:resource_key/availability" in ports


def test_cloud_booking_store_remains_the_persistence_boundary():
    if not CLOUD.is_dir():
        return
    gateway = (CLOUD / "apps/pc-gateway/src/db/bookings.ts").read_text(encoding="utf-8")
    sql = (CLOUD / "migrations/0020_booking_reminders.sql").read_text(encoding="utf-8")
    assert "cloud_booking_resources" in gateway
    assert "cloud_booking_reservations" in gateway
    assert "CREATE TABLE IF NOT EXISTS cloud_booking_resources" in sql
    assert "CREATE TABLE IF NOT EXISTS cloud_booking_reservations" in sql


def test_google_calendar_and_hotel_copy_are_not_booking():
    miss = _ids(ProductIntent(purpose="Add a Google Calendar and hotel booking later"))
    assert "booking.appointment" not in miss
    assert match_vertical_recipe("add calendly so customers can self-book") is None


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


def test_each_booking_recipe_compiles_the_admitted_booking_path():
    registry = load_default_registry()
    for recipe in load_recipes(composition_ready_only=True):
        if recipe.id not in BOOKING_RECIPES:
            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 "booking.appointment" in selected
        assert set(recipe.required_app_blocks) <= selected
        assert "catalog.product" 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="book", name="Book", description=recipe.intent_phrases[0])],
                entities=[Entity(id="visit", name="Visit", description="A visit")],
                tenancy=TenancySpec(model="single_workspace"),
                authentication=AuthenticationSpec(required=False),
            ),
            source_prompt=recipe.intent_phrases[0],
        )
        assert_install_plan_installable(plan)
        assert "appointment-booking" in plan.required_feature_packs
        assert "data.entity" not in {
            dep.id
            for dep in registry.canonical.require("booking.appointment").requires
            if dep.kind == "primitive"
        }


def test_booking_crm_finance_join_recipe_compiles_the_admitted_join():
    """Elevates the booking+CRM+billing join (marketing gap
    booking_plus_crm_billing_recipe) from Lane C's marketing_coverage batch to
    the Architecture Lane tier, reusing only admitted App Blocks."""
    registry = load_default_registry()
    recipes = {recipe.id: recipe for recipe in load_recipes(composition_ready_only=True)}
    recipe = recipes["booking_crm_finance_join"]
    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
    assert matched.id == recipe.id
    selected = _ids(intent)
    assert {"booking.appointment", "crm.customer", "task.management", "billing.invoice"} <= selected
    assert set(recipe.required_app_blocks) <= selected
    assert "catalog.product" 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="book", name="Book", description=recipe.intent_phrases[0])],
            entities=[Entity(id="visit", name="Visit", description="A visit")],
            tenancy=TenancySpec(model="single_workspace"),
            authentication=AuthenticationSpec(required=False),
        ),
        source_prompt=recipe.intent_phrases[0],
    )
    assert_install_plan_installable(plan)
    assert "appointment-booking" in plan.required_feature_packs
    assert "billing-management" in plan.required_feature_packs
    assert "customer-records" in plan.required_feature_packs
    assert "task-management" in plan.required_feature_packs
    assert "data.entity" not in {
        dep.id
        for dep in registry.canonical.require("booking.appointment").requires
        if dep.kind == "primitive"
    }


def test_booking_crm_finance_join_public_copy_uses_recipe_labels():
    spec = DiscoverySpec(
        product=ProductDefinition(
            purpose="track records for a consultancy",
            first_build_goal="Staff review submitted records",
        ),
        original_intent="booking with billing and crm",
        actors=[Actor(id="staff", name="Staff")],
        workflows=[Workflow(id="book", name="Book", description="Reserve visits")],
        entities=[Entity(id="visit", name="Visit", description="A visit")],
        tenancy=TenancySpec(model="single_workspace"),
        authentication=AuthenticationSpec(required=False),
    )
    composition = public_composition(
        spec.model_dump(), source_prompt="booking with billing and crm"
    )
    assert "Appointment booking" in composition["building"]
    assert "Customer records" in composition["building"]
    assert "Invoices" in composition["building"]


def test_booking_crm_finance_join_negation_skips_billing_block():
    ids = _ids(
        ProductIntent(
            purpose="Booking with billing and crm",
            source_prompt="Booking with billing and crm. Do not add billing.",
            staff_review=True,
        )
    )
    assert "billing.invoice" not in ids
    assert "booking.appointment" in ids
    assert "crm.customer" in ids


def test_clinic_booking_public_copy_uses_recipe_labels():
    spec = DiscoverySpec(
        product=ProductDefinition(
            purpose="track records for a consultancy",
            first_build_goal="Staff review submitted records",
        ),
        original_intent="medical clinic booking",
        actors=[Actor(id="staff", name="Reception")],
        workflows=[Workflow(id="book", name="Book", description="Reserve visits")],
        entities=[Entity(id="visit", name="Visit", description="A clinic visit")],
        tenancy=TenancySpec(model="single_workspace"),
        authentication=AuthenticationSpec(required=False),
    )
    composition = public_composition(
        spec.model_dump(), source_prompt="medical clinic booking"
    )
    assert "Clinic appointments" in composition["building"]
    assert "Patient records" in composition["building"]
    assert "Visit request form" in composition["building"]


def test_booking_negation_skips_recipe_booking_block():
    ids = _ids(
        ProductIntent(
            purpose="Medical clinic booking",
            source_prompt="Medical clinic booking. Do not add appointments.",
            staff_review=True,
        )
    )
    assert "booking.appointment" not in ids
    assert "crm.customer" in ids


def test_13a_appointment_gold_does_not_match_booking_recipes():
    assert match_vertical_recipe(
        "a staff-only appointment booking website for a clinic. receptionists reserve a room"
    ) is None
    ids = _ids(
        ProductIntent(
            purpose="Staff can book an appointment",
            staff_review=True,
        )
    )
    assert "booking.appointment" in ids
    assert "crm.customer" not in ids
