"""Lane C: archetype Starters, job Feature Packs, repo isolation, marketing golds."""

from __future__ import annotations

from pathlib import Path

import pytest
import yaml

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.job_packs import JOB_FEATURE_PACKS, known_job_pack_ids
from src.domain.codegen.repo_export import (
    RepoExportError,
    assert_export_clean,
    classify_path,
    is_forbidden_path,
    scan_bundle_text,
)
from src.domain.projects.bootstrap_contract import (
    BFF_PATH_PREFIX,
    BOOTSTRAP_MODE_STARTER_ASSEMBLY,
    CONNECTED_REPO_MODE,
    FRONTEND_BFF_REFERENCE,
    NEW_PROJECT_BOOTSTRAP_MODE,
)
from src.domain.projects.discovery_schemas import (
    Actor,
    AuthenticationSpec,
    DiscoverySpec,
    Entity,
    ProductDefinition,
    TenancySpec,
    Workflow,
)
from src.domain.starter_library.archetypes import (
    ARCHETYPE_STARTERS,
    known_archetype_ids,
    starter_id_for_recipe,
)
from src.domain.starter_library.assembler import assemble_starter_files, assert_thin_shell

ROOT = Path(__file__).resolve().parents[1]
YAML_PATH = ROOT / "docs/coverage/MARKETING_PRODUCT_COVERAGE.yaml"


def _coverage():
    return yaml.safe_load(YAML_PATH.read_text(encoding="utf-8"))


def _by_slug(payload: dict) -> dict[str, dict]:
    return {row["slug"]: row for row in payload["pages"]}


def _plan_for_prompt(prompt: str):
    spec = DiscoverySpec(
        product=ProductDefinition(purpose=prompt, first_build_goal=prompt),
        actors=[Actor(id="staff", name="Staff")],
        workflows=[Workflow(id="run", name="Run", description=prompt[:1000])],
        entities=[Entity(id="record", name="Record", description="Record")],
        tenancy=TenancySpec(model="single_workspace"),
        authentication=AuthenticationSpec(required=False),
    )
    plan = compile_install_plan(spec, source_prompt=prompt)
    assert_install_plan_installable(plan)
    return plan


def test_bootstrap_contract_is_starter_assembly():
    assert NEW_PROJECT_BOOTSTRAP_MODE == BOOTSTRAP_MODE_STARTER_ASSEMBLY
    assert CONNECTED_REPO_MODE == "adopt_and_patch"
    assert FRONTEND_BFF_REFERENCE == "relative_same_origin"
    assert BFF_PATH_PREFIX == "/api"


def test_archetype_family_size_and_diversity():
    ids = known_archetype_ids()
    assert 8 <= len(ids) <= 12
    required = {
        "crm_sales",
        "case_operations",
        "membership_association",
        "service_field",
        "booking",
        "catalog_orders",
        "document_heavy",
        "compliance_audit",
        "client_portal",
        "internal_operations",
    }
    assert required <= ids
    signatures = []
    for starter_id in (
        "crm_sales",
        "case_operations",
        "membership_association",
        "booking",
        "catalog_orders",
        "document_heavy",
    ):
        starter = ARCHETYPE_STARTERS[starter_id]
        signatures.append(
            (
                starter.primary_route,
                starter.workflow_emphasis,
                tuple(item.href for item in starter.default_navigation),
            )
        )
    assert len(set(signatures)) == 6


def test_starter_and_pack_thinness():
    for starter in ARCHETYPE_STARTERS.values():
        files = assemble_starter_files(starter.id)
        assert_thin_shell(files)
        assert "public/starter-shell.json" in files or "starter-shell.json" in files
        assert "/api" in next(iter(files.values())) or "/api" in files.get(
            "public/starter-shell.json", ""
        )
    for pack in JOB_FEATURE_PACKS.values():
        assert "engine" not in pack.id
        if "payments.checkout" not in pack.required_app_blocks:
            assert "payments.checkout" in pack.forbidden_implicit_capabilities


def test_board_and_committee_packs_forbid_membership():
    for pack_id in ("board_governance", "committee_management"):
        pack = JOB_FEATURE_PACKS[pack_id]
        assert "membership.member" in pack.forbidden_implicit_capabilities
        assert "membership.member" not in pack.required_app_blocks
        assert "document_heavy" in pack.compatible_starters


def test_b2b_order_portal_composes_checkout_in_pack():
    pack = JOB_FEATURE_PACKS["b2b_order_portal"]
    assert "payments.checkout" in pack.required_app_blocks
    assert "payments.checkout" not in pack.forbidden_implicit_capabilities
    assert {"catalog.product", "orders.order"} <= set(pack.required_app_blocks)
    assert "catalog_orders" in pack.compatible_starters


def test_repo_export_rejects_control_plane_and_cloud_hosts():
    import tempfile

    with tempfile.TemporaryDirectory() as raw:
        tmp_path = Path(raw)
        assert classify_path("pages/index.vue") == "A"
        assert is_forbidden_path(".p2hs/ai-context/system-prompt.md")
        assert is_forbidden_path("p2hs/app-blocks-plan.json")
        (tmp_path / "pages").mkdir()
        (tmp_path / "pages" / "index.vue").write_text("<template>ok</template>", encoding="utf-8")
        (tmp_path / ".p2hs").mkdir()
        (tmp_path / ".p2hs" / "ai-context").mkdir()
        (tmp_path / ".p2hs" / "ai-context" / "system-prompt.md").write_text(
            "You are a codegen agent.\n", encoding="utf-8"
        )
        (tmp_path / "secret.js").write_text(
            "fetch('https://cloudgate.p2hs.com/v1')\n", encoding="utf-8"
        )
        with pytest.raises(RepoExportError):
            assert_export_clean(tmp_path, ["secret.js"])
        with pytest.raises(RepoExportError):
            assert_export_clean(tmp_path, [".p2hs/ai-context/system-prompt.md"])
        assert scan_bundle_text("NUXT_PUBLIC_P2HS_GATEWAY_URL=https://x")


def test_association_negative_intent_drops_membership():
    rows = _by_slug(_coverage())
    prompt = (
        rows["association-management-software"]["prompt"]
        + " Do not include membership tracking."
    )
    plan = _plan_for_prompt(prompt)
    assert plan.matched_recipe_id == "mkt_membership_access"
    ids = {block.id for block in plan.blocks}
    assert "membership.member" not in ids
    assert "saas-membership" not in ids


def test_unknown_demand_has_null_recipe():
    plan = _plan_for_prompt("Build a unique kite festival guestbook with photos of kites.")
    assert plan.matched_recipe_id is None
    assert plan.starter_id is None


GOLD_CASES = {
    "association-management-software": {
        "recipe": "mkt_membership_access",
        "starter": "membership_association",
        "packs": {"association_management"},
        "has": {"membership.member"},
        "missing": {"saas-membership", "membership.portal", "payments.checkout"},
    },
    "board-management-portal": {
        "recipe": "mkt_board_governance",
        "starter": "document_heavy",
        "packs": {"board_governance", "committee_management"},
        "has": {"documents", "workflow.approval"},
        "missing": {"membership.member"},
    },
    "committee-management-portal": {
        "recipe": "mkt_board_governance",
        "starter": "document_heavy",
        "packs": {"committee_management"},
        "has": {"documents"},
        "missing": {"membership.member"},
    },
    "field-service-management-software": {
        "recipe": "mkt_field_service",
        "starter": "service_field",
        "packs": {"field_service_operations"},
        "has": {"crm.customer", "case.management"},
        "missing": {"payments.checkout"},
    },
    "b2b-order-portal": {
        "recipe": "mkt_catalog_orders_inventory",
        "starter": "catalog_orders",
        "packs": {"b2b_order_portal"},
        "has": {"catalog.product", "orders.order"},
        "missing": {"payments.checkout"},
    },
    "document-control-software": {
        "recipe": "mkt_documents_ops",
        "starter": "document_heavy",
        "packs": {"document_control"},
        "has": {"documents"},
        "missing": {"membership.member"},
    },
    "client-onboarding-portal": {
        "recipe": "mkt_client_portal",
        "starter": "client_portal",
        "packs": {"client_onboarding"},
        "has": {"documents", "task.management"},
        "missing": {"portal.portal", "membership.portal"},
    },
    "customer-success-management-platform": {
        "recipe": "mkt_crm_sales",
        "starter": "crm_sales",
        "packs": {"customer_success_operations"},
        "has": {"crm.customer"},
        "missing": {"payments.checkout"},
    },
    "salon-management-software": {
        "recipe": "mkt_booking_ops",
        "starter": "booking",
        "packs": {"booking_operations"},
        "has": {"booking.appointment"},
        "missing": {"payments.checkout"},
    },
    "project-management-saas": {
        "recipe": "mkt_task_ops",
        "starter": "internal_operations",
        "packs": {"task_operations"},
        "has": {"task.management"},
        "missing": {"membership.member"},
    },
}


@pytest.mark.parametrize("slug", sorted(GOLD_CASES))
def test_marketing_gold_recipe_starter_pack(slug):
    rows = _by_slug(_coverage())
    spec = GOLD_CASES[slug]
    prompt = rows[slug]["prompt"]
    plan = _plan_for_prompt(prompt)
    ids = {block.id for block in plan.blocks}
    assert plan.matched_recipe_id == spec["recipe"], (slug, plan.matched_recipe_id)
    assert plan.starter_id == spec["starter"], (slug, plan.starter_id)
    assert spec["packs"] <= set(plan.feature_pack_ids), (slug, plan.feature_pack_ids)
    assert spec["has"] <= ids, (slug, ids)
    if "payments.checkout" in ids:
        assert "payments.checkout" not in plan.forbidden_capabilities
    else:
        assert spec["missing"].isdisjoint(ids)
        assert "payments.checkout" not in ids
        assert "forbidden:payments.checkout" in plan.unsupported_assumptions or (
            "payments.checkout" in plan.forbidden_capabilities
        )
    assert starter_id_for_recipe(plan.matched_recipe_id) == spec["starter"]


def test_document_heavy_hosts_multiple_recipes():
    hosts = {
        starter_id_for_recipe("mkt_documents_ops"),
        starter_id_for_recipe("mkt_board_governance"),
        starter_id_for_recipe("mkt_contract_lifecycle"),
    }
    assert hosts == {"document_heavy"}


def test_coverage_metrics_match_lane_c_summary():
    payload = _coverage()
    pages = payload["pages"]
    assigned = [
        row
        for row in pages
        if row.get("starter") not in (None, "", "missing", "mismatch")
    ]
    assert payload["page_count"] == 205
    assert len(known_archetype_ids()) == 11
    assert len(JOB_FEATURE_PACKS) == 44
    assert len(assigned) == 205
    assert sum(1 for row in pages if row.get("starter") == "missing") == 0
    recipes = {
        f"mkt_{cluster}"
        for cluster in (
            "approval_ops",
            "case_ops",
            "task_ops",
            "documents_ops",
            "support_ops",
            "crm_sales",
            "crm_plus_booking",
            "membership_roster",
            "board_governance",
            "billing_records",
            "catalog_orders_inventory",
            "booking_ops",
            "clinic_admin_not_ehr",
            "field_service",
            "client_portal",
            "contract_lifecycle",
            "workforce",
            "property_assets",
            "procurement",
            "education_cert",
            "construction_precon",
        )
    }
    assert len(recipes) == 21
    assert all(starter_id_for_recipe(recipe_id) for recipe_id in recipes)
    assert all(row.get("job_feature_packs") for row in assigned if row["class"] in {"C0", "C2"})


def test_job_packs_use_frozen_ids_only():
    assert "association_management" in known_job_pack_ids()
    for pack in JOB_FEATURE_PACKS.values():
        from src.domain.app_blocks.canonical import CANONICAL_BLOCK_IDS

        assert set(pack.required_app_blocks) <= CANONICAL_BLOCK_IDS
        assert "contract.contract" not in pack.required_app_blocks
        assert "portal.portal" not in pack.required_app_blocks
        assert "onboarding.onboarding" not in pack.required_app_blocks
