"""Lane B harvest: P2HS-Home demand mapped to frozen catalogs only."""

from pathlib import Path

import pytest
import yaml

from src.domain.app_blocks.canonical import CANONICAL_BLOCK_IDS
from src.domain.app_blocks.catalogs import DEFAULT_KNOWN_PACKS

ROOT = Path(__file__).resolve().parents[1]
YAML_PATH = ROOT / "docs/coverage/MARKETING_PRODUCT_COVERAGE.yaml"
REGISTRY = ROOT / "src/domain/app_blocks/platform_coverage_registry.v1.yaml"
HARVEST = ROOT / "scripts/harvest_marketing_product_coverage.py"
HOME_PAGES = ROOT.parent / "P2HS-Home" / "scripts" / "market-pages.js"

INVENTED = {
    # contract.contract / contract-management: no longer invented -- the
    # Architecture Lane Contract freeze was deliberately lifted for the
    # Native Capability Baseline (see docs/audits/ARCHITECTURE_LANE_CONTRACT.md).
    "cloud/association_backend.py",
    "cloud/coworking_backend.py",
    "cloud/audit_backend.py",
}


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 test_harvest_admitted_blocks_match_canonical():
    from scripts.harvest_marketing_product_coverage import ADMITTED_BLOCKS, BLOCK_TO_PACK

    assert ADMITTED_BLOCKS == CANONICAL_BLOCK_IDS
    assert BLOCK_TO_PACK["payments.checkout"] == "payment-checkout"
    assert "saas-membership" not in BLOCK_TO_PACK.values()


def test_coverage_yaml_program_invariants():
    payload = _coverage()
    assert payload["program"] == "marketing_to_product_coverage"
    assert payload["lane"] == "B"
    assert payload["page_count"] == 205
    assert payload["page_count"] == len(payload["pages"])
    assert payload["sequential_architecture_family"] == "none"
    assert payload["fast_lane_recipe_work_membership"] == "live"
    assert payload.get("fast_lane_batch_11_started") is False
    assert payload["class_counts"] == {"C0": 82, "C2": 123}
    families = [row["family"] for row in payload["architecture_unlock_rank"]]
    assert families == []
    assert "property" not in families
    assert "reporting" not in families
    assert "contract" not in families
    assert "workforce" not in families
    dumped = yaml.safe_dump(payload)
    for invented in INVENTED:
        assert invented not in dumped


def test_page_identifiers_are_admitted():
    payload = _coverage()
    slugs = [row["slug"] for row in payload["pages"]]
    assert len(slugs) == len(set(slugs))
    for row in payload["pages"]:
        extra = set(row["app_blocks"]) - CANONICAL_BLOCK_IDS
        assert not extra, (row["slug"], extra)
        if "payments.checkout" not in row["app_blocks"]:
            assert "payments.checkout" in row["forbidden"]
            if row["slug"] in {"membership-management-system", "coworking-space-management-software", "association-management-software"}:
                assert "saas-membership" not in row["forbidden"]
            else:
                assert "saas-membership" in row["forbidden"]
        assert "membership.portal" in row["forbidden"]
        assert "private_vertical_backend" in row["forbidden"]
        for pack in row["feature_packs"]:
            assert pack in DEFAULT_KNOWN_PACKS
            if row["slug"] not in {"membership-management-system", "coworking-space-management-software", "association-management-software"}:
                assert pack != "saas-membership"
        if row["starter"] == "missing":
            assert not row.get("job_feature_packs")
            assert row["class"] in {"C4", "C5", "C6"}
        elif row["starter"] != "mismatch":
            from src.domain.starter_library.archetypes import known_archetype_ids

            assert row["starter"] in known_archetype_ids(), row["slug"]
        assert row["class"] in {"C0", "C1", "C2", "C3", "C4", "C5", "C6"}
        assert "coverage" in row


def test_board_pages_do_not_install_membership():
    rows = _by_slug(_coverage())
    for slug in ("board-management-portal", "committee-management-portal"):
        row = rows[slug]
        assert row["class"] == "C2"
        assert "membership.member" not in row["app_blocks"]
        assert "membership-management" not in row["feature_packs"]
        assert "do_not_install_membership.member" in row["action"]
        assert {"documents", "task.management", "workflow.approval"} <= set(row["app_blocks"])
        assert "membership.member_from_board_member_wording" not in row["mismatches"]


def test_b2b_order_portal_composes_checkout_with_order_boundary():
    row = _by_slug(_coverage())["b2b-order-portal"]
    assert row["class"] == "C2"
    assert "payments.checkout" in row["app_blocks"]
    assert "payments.checkout" not in row["mismatches"]
    assert {"catalog.product", "orders.order"} <= set(row["app_blocks"])
    assert "payment-checkout" in row["feature_packs"]


def test_client_portal_pages_use_owner_scoped_client_portal_pack():
    rows = _by_slug(_coverage())
    for slug in ("client-approval-portal", "client-onboarding-portal", "client-project-portal"):
        row = rows[slug]
        assert "client-portal" in row["feature_packs"]
        assert "member_portal" not in row["mismatches"]


def test_membership_management_is_c2_with_payment_mismatches():
    row = _by_slug(_coverage())["membership-management-system"]
    assert row["class"] == "C2"
    assert "membership.member" in row["app_blocks"]
    assert "marketing_coverage" in row["live_recipe_batches"]
    assert "membership_recipe_held" not in row["gaps"]
    assert "stripe_subscription" in row["mismatches"]
    assert "saas-membership" not in row["forbidden"]


def test_subscription_management_is_c2_with_explicit_billing_configuration():
    row = _by_slug(_coverage())["subscription-management-platform"]
    assert row["class"] == "C2"
    assert "billing.subscription_operations" in row["app_blocks"]
    assert "subscription_billing" not in row["mismatches"]
    assert "tax_engine" not in row["mismatches"]


def test_course_delivery_explicitly_composes_checkout():
    row = _by_slug(_coverage())["online-course-lms-platform"]
    assert "payments.checkout" in row["app_blocks"]
    assert "lms_checkout" not in row["mismatches"]


def test_school_operations_explicitly_composes_checkout():
    row = _by_slug(_coverage())["school-management-system"]
    assert "payments.checkout" in row["app_blocks"]
    assert "lms_checkout" not in row["mismatches"]


def test_warehouse_composes_bins_and_location_ordered_pick_lists():
    row = _by_slug(_coverage())["warehouse-management-system"]
    assert "inventory.warehouse" in row["app_blocks"]
    assert "logistics.shipment" in row["app_blocks"]
    assert "calculation.calculator" in row["app_blocks"]
    assert "warehouse_bin_wms" not in row["mismatches"]
    assert "wave_picking_optimization" not in row["mismatches"]


def test_generic_case_pages_do_not_inherit_clinical_case_gaps():
    rows = _by_slug(_coverage())
    assert "clinical_record" not in rows["bug-tracking-issue-triage"]["mismatches"]
    assert "private_case_backend" not in rows["bug-tracking-issue-triage"]["mismatches"]
    assert "clinical_record" not in rows["personal-injury-case-management"]["mismatches"]
    assert "calculation.calculator" in rows["personal-injury-case-management"]["app_blocks"]


def test_approval_and_workforce_pages_do_not_claim_payroll_posting():
    rows = _by_slug(_coverage())
    for slug in ("purchase-order-approval-software", "time-off-request-approval", "employee-scheduling-software"):
        assert "payroll_posting" not in rows[slug]["mismatches"]


def test_booking_operations_do_not_inherit_ehr_or_insurance_claim_gaps():
    rows = _by_slug(_coverage())
    for slug in ("salon-management-software", "hotel-management-system", "pet-care-management-system"):
        assert "ehr" not in rows[slug]["mismatches"]
        assert "insurance_claims" not in rows[slug]["mismatches"]


def test_certification_program_uses_education_and_renewal_composition():
    row = _by_slug(_coverage())["certification-program-management"]
    assert row["job_feature_packs"] == ["certification_operations"]
    assert {"education.progress", "education.assessment", "education.certificate"} <= set(row["app_blocks"])
    assert not row["mismatches"]


def test_procurement_match_gap_is_scoped_to_the_explicit_match_claim():
    rows = _by_slug(_coverage())
    assert "three_way_match_engine" not in rows["procurement-management-software"]["mismatches"]
    assert "calculation.calculator" in rows["procurement-management-software"]["app_blocks"]
    for slug in ("procurement-intake-portal", "vendor-risk-management-software", "supplier-quality-management-software"):
        assert "three_way_match_engine" not in rows[slug]["mismatches"]
        assert "supplier_edi" not in rows[slug]["mismatches"]


def test_financial_operations_do_not_inherit_general_ledger_gap():
    rows = _by_slug(_coverage())
    for slug in ("invoice-management-system", "accounting-practice-software", "professional-services-automation-software"):
        assert "general_ledger" not in rows[slug]["mismatches"]


def test_saas_analytics_uses_bounded_record_aggregates_without_warehouse_gap():
    rows = _by_slug(_coverage())
    assert "bi_warehouse" not in rows["saas-analytics-dashboard"]["mismatches"]
    for slug in ("customer-feedback-roadmap", "customer-health-score-dashboard", "project-profitability-dashboard", "supplier-performance-scorecard"):
        assert "bi_warehouse" not in rows[slug]["mismatches"]


def test_takeoff_gap_is_scoped_to_construction_estimating():
    rows = _by_slug(_coverage())
    assert "takeoff_engine" not in rows["construction-estimating-software"]["mismatches"]
    assert "takeoff_engine" not in rows["construction-management-software"]["mismatches"]


def test_manufacturing_claim_uses_work_orders_and_reporting_without_mrp_gap():
    row = _by_slug(_coverage())["manufacturing-erp-dashboard"]
    assert "reporting.dashboard" in row["app_blocks"]
    assert "manufacturing_mrp" not in row["mismatches"]
    assert "finite_capacity_scheduling" not in row["mismatches"]


def test_loan_servicing_stays_within_implemented_operations_boundary():
    row = _by_slug(_coverage())["loan-servicing-software"]
    assert "finance.loan_operations" in row["app_blocks"]
    assert "loan_servicing_posting" not in row["mismatches"]
    assert "regulated_lending_decisioning" not in row["mismatches"]


def test_regulated_lims_gap_is_scoped_to_the_lims_claim():
    rows = _by_slug(_coverage())
    assert "regulated_lims_claim" not in rows["laboratory-information-management-system-lims"]["mismatches"]
    assert "lab.sample" in rows["laboratory-information-management-system-lims"]["app_blocks"]
    for slug in ("laboratory-sample-tracking-software", "food-traceability-software", "production-batch-traceability"):
        assert "regulated_lims_claim" not in rows[slug]["mismatches"]


def test_saas_analytics_composes_chart_and_calculation_primitives():
    row = _by_slug(_coverage())["saas-analytics-dashboard"]
    assert {"reporting.dashboard", "visualization.chart", "calculation.calculator"} <= set(row["app_blocks"])
    assert "bi_warehouse" not in row["mismatches"]


def test_ai_support_uses_suggested_reply_pack_without_auto_send_claim():
    rows = _by_slug(_coverage())
    ai = rows["ai-customer-support-dashboard"]
    assert ai["job_feature_packs"] == ["ai_support_operations"]
    assert "auto_sent_ai_replies" not in ai["mismatches"]
    assert "ai_support_operations" in ai["job_feature_packs"]
    assert "account_reset_side_effects" not in rows["internal-help-desk"]["mismatches"]


def test_workforce_pages_do_not_inherit_application_membership_gap():
    rows = _by_slug(_coverage())
    for slug in ("employee-scheduling-software", "hr-management-system", "workforce-credential-tracking"):
        assert "membership.member_as_employee" not in rows[slug]["mismatches"]


def test_education_service_pages_do_not_inherit_lms_runtime_gap():
    rows = _by_slug(_coverage())
    for slug in ("education-consultancy-management", "internship-placement-management", "grant-management-software"):
        assert "lms_runtime" not in rows[slug]["mismatches"]


def test_contract_page_is_admitted_capability():
    row = _by_slug(_coverage())["contract-lifecycle-management-software"]
    assert row["class"] == "C2"
    assert row["architecture_family"] is None
    assert "contract.contract" in row["app_blocks"]
    assert "contract_admitted_compose_without_esign_requirement" in row["action"]


def test_workforce_page_is_admitted_capability():
    row = _by_slug(_coverage())["employee-scheduling-software"]
    assert row["class"] == "C2"
    assert row["architecture_family"] is None
    assert "workforce.worker" in row["app_blocks"]
    assert "workforce_admitted_compose_without_payroll_or_membership_reuse" in row["action"]
    assert "membership.member" not in row["app_blocks"]


def test_registry_points_at_harvest_without_rewriting_queue():
    registry = yaml.safe_load(REGISTRY.read_text(encoding="utf-8"))
    assert registry["next_architecture_family"] == "not_yet_selected"
    assert registry["family_just_frozen"] == "visualization_calculation"
    coverage = registry["marketing_to_product_coverage"]
    assert coverage["artifact"] == "docs/coverage/MARKETING_PRODUCT_COVERAGE.yaml"
    assert coverage["unlock_rank_is_evidence_not_queue"] is True
    assert coverage["fast_lane_recipe_work_membership"] == "live"
    assert (ROOT / coverage["program"]).is_file()
    assert HARVEST.is_file()


@pytest.mark.skipif(not HOME_PAGES.is_file(), reason="P2HS-Home corpus not checked out")
def test_harvest_check_matches_home_corpus():
    from scripts.harvest_marketing_product_coverage import harvest, main, recipe_catalog

    payload = harvest()
    assert payload == _coverage()
    import sys

    argv = sys.argv
    sys.argv = ["harvest_marketing_product_coverage.py", "--check"]
    try:
        assert main() == 0
    finally:
        sys.argv = argv
    assert recipe_catalog(payload["pages"])["batch"] == "marketing_coverage"


def _plan_for_prompt(prompt: str):
    from src.domain.app_blocks.compiler import compile_install_plan
    from src.domain.app_blocks.execution import assert_install_plan_installable
    from src.domain.projects.discovery_schemas import (
        Actor,
        AuthenticationSpec,
        DiscoverySpec,
        Entity,
        ProductDefinition,
        TenancySpec,
        Workflow,
    )

    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_home_prompts_compose_admitted_recipes_not_vertical_backends():
    rows = _by_slug(_coverage())
    cases = {
        "audit-management-software": "mkt_case_ops",
        "b2b-order-portal": "mkt_catalog_orders_inventory",
        "board-management-portal": "mkt_board_governance",
        "membership-management-system": "mkt_membership_entitlements",
        "association-management-software": "mkt_membership_access",
        "client-approval-portal": "mkt_client_portal",
        "contract-lifecycle-management-software": "mkt_contract_lifecycle",
        "field-service-management-software": "mkt_field_service",
        "invoice-management-system": "mkt_billing_records",
    }
    for slug, recipe_id in cases.items():
        prompt = rows[slug]["prompt"]
        plan = _plan_for_prompt(prompt)
        ids = {block.id for block in plan.blocks}
        assert plan.matched_recipe_id == recipe_id, (slug, plan.matched_recipe_id)
        assert set(rows[slug]["app_blocks"]) <= ids
        if "payments.checkout" in rows[slug]["app_blocks"]:
            assert "payments.checkout" in ids
        else:
            assert "payments.checkout" not in ids
        assert "membership.portal" not in ids
        for invented in INVENTED:
            assert invented not in ids
        if slug in {"board-management-portal"}:
            assert "membership.member" not in ids
        if slug == "b2b-order-portal":
            assert {"catalog.product", "orders.order"} <= ids
        if slug == "membership-management-system":
            assert "membership.member" in ids
        if "payments.checkout" in rows[slug]["app_blocks"]:
            assert "forbidden:payments.checkout" not in plan.unsupported_assumptions
        else:
            assert "forbidden:payments.checkout" in plan.unsupported_assumptions


def test_subscription_and_specialist_pages_do_not_match_a_recipe():
    rows = _by_slug(_coverage())
    plan = _plan_for_prompt(rows["subscription-management-platform"]["prompt"])
    assert plan.matched_recipe_id != "mkt_membership_roster"
    assert "membership.member" not in {block.id for block in plan.blocks}


def test_codegen_prompt_requires_recipe_before_invention():
    from src.domain.codegen.prompt_wrapper import _COMPOSITION_AUTHORITY

    assert "matched_recipe_id" in _COMPOSITION_AUTHORITY
    assert "starter_id" in _COMPOSITION_AUTHORITY
    assert "feature_pack_ids" in _COMPOSITION_AUTHORITY
    assert "payments.checkout" in _COMPOSITION_AUTHORITY
    assert "association_backend" in _COMPOSITION_AUTHORITY
    assert "membership.member" in _COMPOSITION_AUTHORITY
