"""Architecture Lane Membership: admit membership_record roster state.

No auth, RBAC, Core workspace membership, saas-membership, or Stripe subscriptions.
"""

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.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, default_slots, select_capabilities
from src.domain.app_blocks.registry import load_default_registry
from src.domain.app_blocks.vertical_recipes import match_vertical_recipe
from src.domain.projects.discovery_schemas import (
    Actor,
    AuthenticationSpec,
    DiscoverySpec,
    Entity,
    ProductDefinition,
    TenancySpec,
    Workflow,
)
from src.domain.starter_library.catalog import known_contract_ids
from tests.test_phase11_pack_migration_materialization import _captured_composition

ROOT = Path(__file__).resolve().parents[1]
RECEIPT = ROOT / "src/domain/app_blocks/recipes/membership_freeze.receipt.txt"
AUDIT = ROOT / "src/domain/app_blocks/recipes/membership_architecture_audit.receipt.txt"
REGISTRY = ROOT / "src/domain/app_blocks/platform_coverage_registry.v1.yaml"
GATED = (
    "payments.checkout",
    "membership.portal",
)
STATUS = {"pending", "active", "inactive", "cancelled"}
STARTER_SLOTS = {
    "list": "record.table",
    "detail": "record.detail",
    "create": "record.create",
    "edit": "record.edit",
    "empty": "state.empty",
}
FIELDS = {
    "reference",
    "member_name",
    "membership_type",
    "start_date",
    "end_date",
    "status",
    "notes",
}


def membership_composition():
    return _captured_composition(
        "alembic/versions/149_membership_management_pack.py", "membership-management"
    )


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


def _spec(prompt: str) -> DiscoverySpec:
    return DiscoverySpec(
        product=ProductDefinition(
            purpose=prompt,
            first_build_goal="Staff review submitted records",
        ),
        original_intent=prompt,
        actors=[Actor(id="staff", name="Staff counselor")],
        workflows=[Workflow(id="review", name="Staff review", description="Review records")],
        entities=[Entity(id="record", name="Record", description="A record")],
        tenancy=TenancySpec(model="single_workspace"),
        authentication=AuthenticationSpec(required=False),
    )


def test_membership_runtime_is_canonical_without_new_kernel():
    registry = load_default_registry()
    block = registry.require("membership.member")
    canonical = registry.canonical.require("membership.member")
    assert block.status == "implemented"
    assert "membership.member" in CANONICAL_BLOCK_IDS
    assert "membership.member" not in FEATURE_PACK_ALIASES
    assert "membership.member" not in feature_pack_only_aliases()
    assert "membership.portal" not in CANONICAL_BLOCK_IDS
    assert canonical.installation.feature_packs == ("membership-management",)
    primitives = {dep.id for dep in canonical.requires if dep.kind == "primitive"}
    assert primitives == {"data.entity"}
    assert "saas-membership" not in canonical.installation.feature_packs
    assert "payment-checkout" not in canonical.installation.feature_packs
    assert "membership-management" in default_known_packs()
    composition = membership_composition()
    entity = composition["entities"][0]
    assert entity["entity_key"] == "membership_record"
    keys = {field["key"] for field in entity["schema"]["fields"]}
    assert keys == FIELDS
    types = {field["key"]: field["type"] for field in entity["schema"]["fields"]}
    assert types["status"] == "select"
    assert types["start_date"] == "date"
    assert types["end_date"] == "date"
    status = next(field for field in entity["schema"]["fields"] if field["key"] == "status")
    assert set(status["options"]) == STATUS
    assert "expired" not in status["options"]
    assert "paid" not in status["options"]
    assert "stripe_subscription_id" not in keys
    assert "user_id" not in keys
    assert "role" not in keys


def test_membership_install_plan_does_not_bind_saas_or_auth(monkeypatch):
    from src.domain.app_blocks import compiler

    registry = load_default_registry()
    monkeypatch.setattr(
        compiler,
        "select_capabilities",
        lambda _intent: [("membership_member", "membership.member", "Selected.")],
    )
    plan = compiler.compile_install_plan(None, registry=registry)
    assert_install_plan_installable(plan)
    assert "membership-management" in plan.required_feature_packs
    assert "saas-membership" not in plan.required_feature_packs
    assert "payment-checkout" not in plan.required_feature_packs
    assert "data.entity" in plan.required_primitives


def test_positive_intent_selects_membership():
    for prompt in (
        "Staff need to manage our association members.",
        "Track member type, start date, end date and active status.",
        "Build a membership management screen.",
        "We need a roster of club members and their membership status.",
        "Track professional association memberships for our members.",
        "Build a membership management system for our association.",
        "Staff need to manage club members and their membership status.",
        "Track member name, membership type, start date and end date.",
        "We need a roster of active and inactive members.",
        "Create a membership database for our professional organization.",
        "Staff should manage member records and membership dates.",
        "Track memberships for our alumni association.",
        "We need membership management for our community organization.",
    ):
        ids = _ids(ProductIntent(purpose=prompt, source_prompt=prompt, staff_review=True))
        assert "membership.member" in ids, prompt
        assert not (ids & set(GATED)), prompt


def test_negative_intent_skips_membership():
    for prompt in (
        "Client records. Do not add membership management.",
        "Staff CRM. We don't have members.",
        "Case tracking. No membership system.",
        "Membership is handled externally.",
        "Internal tool. No member tracking yet.",
        "Client portal. Do not build a member portal.",
        "Do not add membership management.",
        "No membership system.",
        "We don't track members.",
        "No member tracking yet.",
        "Do not build a member roster.",
        "We do not need membership functionality.",
        "No membership management for now.",
    ):
        ids = _ids(ProductIntent(purpose=prompt, source_prompt=prompt, staff_review=True))
        assert "membership.member" not in ids, prompt


def test_false_positives_do_not_select_membership():
    for purpose in (
        "Add staff members",
        "Add staff members to the workspace.",
        "Admins can invite team members.",
        "Users need to sign in.",
        "Create user accounts for our staff.",
        "Workspace members should collaborate.",
        "Add project team members.",
        "Board members need access to files.",
        "Track board members, meetings, agendas and minutes.",
        "Conversation members can reply.",
        "Admin can add and remove staff members.",
        "Users can sign up and log in",
        "Offer monthly subscriptions",
        "Our SaaS has monthly subscriptions.",
        "Users subscribe to our software.",
        "Users pay $29/month for access.",
        "we have subscribers",
        "Add role-based permissions for staff members.",
        "Customers create accounts and log in.",
        "workspace member invitations",
        "team member directory for employees",
        "organization member roles",
        "user account creation",
        "subscriber list for the newsletter",
        "customer records for the firm",
        "member function of the board",
        "board member biography page",
        "family member emergency contacts",
        "Stripe membership billing",
        "SaaS membership entitlements",
        "Staff manage a product catalog",
        "Staff manage customer orders",
        "Staff need an invoice management screen",
    ):
        ids = _ids(ProductIntent(purpose=purpose, source_prompt=purpose, staff_review=True))
        assert "membership.member" not in ids, purpose


def test_crm_and_membership_compose_without_collapsing():
    prompt = "Staff customer records and membership management. No checkout."
    ids = _ids(ProductIntent(purpose=prompt, source_prompt=prompt, staff_review=True))
    assert {"crm.customer", "membership.member"} <= ids
    assert not (ids & set(GATED))


def test_billing_and_membership_compose_without_automation():
    prompt = "Staff invoice management and membership management. No checkout."
    ids = _ids(ProductIntent(purpose=prompt, source_prompt=prompt, staff_review=True))
    assert {"billing.invoice", "membership.member"} <= ids
    assert not (ids & set(GATED))


def test_booking_and_membership_compose_without_access_gate_language():
    prompt = (
        "Appointment booking for the receptionist and membership management for the club. "
        "No checkout."
    )
    ids = _ids(ProductIntent(purpose=prompt, source_prompt=prompt, staff_review=True))
    assert {"booking.appointment", "membership.member"} <= ids
    assert not (ids & set(GATED))


def test_documents_and_membership_compose_independently():
    prompt = "Staff document management and membership management. No checkout."
    ids = _ids(ProductIntent(purpose=prompt, source_prompt=prompt, staff_review=True))
    assert {"documents", "membership.member"} <= ids
    assert not (ids & set(GATED))


def test_files_and_membership_compose_independently():
    prompt = (
        "Staff membership management with file attachments on those records. "
        "No checkout."
    )
    ids = _ids(ProductIntent(purpose=prompt, source_prompt=prompt, staff_review=True))
    assert {"files.attachments", "membership.member"} <= ids
    assert not (ids & set(GATED))


def test_catalog_orders_inventory_compose_independently():
    prompt = (
        "Staff product catalog, customer orders, inventory management, "
        "and membership management. No checkout."
    )
    ids = _ids(ProductIntent(purpose=prompt, source_prompt=prompt, staff_review=True))
    assert {"catalog.product", "orders.order", "inventory.stock", "membership.member"} <= ids
    assert "payments.checkout" not in ids


def test_cross_archetype_membership_is_the_same_block():
    prompts = (
        "Professional association membership management for member type and dates.",
        "Club membership management for club members and membership status.",
        "Alumni membership records with membership type and join date status.",
        "Nonprofit membership management with start date and status notes.",
        "Community organization maintains a staff-managed membership roster.",
        "Service business membership roster with membership type and dates.",
        "A service company records customers who hold a membership plan.",
    )
    for prompt in prompts:
        ids = _ids(ProductIntent(purpose=prompt, source_prompt=prompt, staff_review=True))
        assert "membership.member" in ids, prompt


def test_13a_do_not_create_orders_still_misses_membership():
    prompt = (
        "Our sales office maintains catalog items in a shared list. "
        "Employees should sign in, add items and correct descriptions. Do not create orders."
    )
    assert match_vertical_recipe(prompt) is None
    ids = _ids(ProductIntent(purpose=prompt, source_prompt=prompt, staff_review=True))
    assert "catalog.product" in ids
    assert "membership.member" not in ids


def test_membership_reuses_record_starter_contracts():
    registry = load_default_registry()
    block = registry.require("membership.member")
    contracts = {
        slot.id: slot.component_contract
        for slot in block.slots
        if slot.component_contract
    }
    assert STARTER_SLOTS.items() <= contracts.items()
    assert "membership.list" not in known_contract_ids()


def test_membership_runtime_crud_and_status_transition():
    kernel = PlatformKernel(namespace="membership-freeze")
    kernel.as_anonymous()
    with pytest.raises(KernelDeniedError):
        kernel.create_record("membership_record", {"reference": "MEM-1"})
    kernel.as_staff()
    created = kernel.create_record(
        "membership_record",
        {
            "reference": "MEM-1048",
            "member_name": "Ada Lovelace",
            "membership_type": "Professional",
            "start_date": "2026-01-01",
            "end_date": "2026-12-31",
            "status": "pending",
            "notes": "Board nominated",
        },
    )
    listed = kernel.list_records("membership_record")
    assert listed[0]["id"] == created["id"]
    kernel.patch_record(
        "membership_record",
        created["id"],
        {"status": "active", "membership_type": "Gold", "end_date": "2027-12-31", "notes": "Dues recorded"},
    )
    read = kernel.get_record("membership_record", created["id"])
    assert read["status"] == "active"
    assert read["membership_type"] == "Gold"
    assert read["end_date"] == "2027-12-31"
    assert read["member_name"] == "Ada Lovelace"
    assert "stripe_subscription_id" not in read
    assert "user_id" not in read
    kernel.patch_record("membership_record", created["id"], {"status": "inactive"})
    kernel.patch_record("membership_record", created["id"], {"status": "active"})
    kernel.patch_record("membership_record", created["id"], {"status": "cancelled"})
    assert kernel.get_record("membership_record", created["id"])["status"] == "cancelled"
    open_ended = kernel.create_record(
        "membership_record",
        {
            "reference": "MEM-LIFETIME",
            "member_name": "Grace Hopper",
            "membership_type": "Lifetime",
            "start_date": "2026-09-01",
            "status": "active",
            "notes": "Open-ended membership",
        },
    )
    assert kernel.get_record("membership_record", open_ended["id"]).get("end_date") in (None, "")
    other = PlatformKernel(namespace="membership-other")
    other.as_staff()
    assert other.list_records("membership_record") == []
    with pytest.raises(KeyError):
        other.get_record("membership_record", created["id"])


def test_orders_do_not_mutate_membership():
    kernel = PlatformKernel(namespace="order-member")
    kernel.as_staff()
    member = kernel.create_record(
        "membership_record",
        {"reference": "MEM-1", "member_name": "Ada", "status": "pending"},
    )
    order = kernel.create_record(
        "order_record",
        {"reference": "WO-1", "customer_name": "Ada", "status": "open"},
    )
    kernel.patch_record("order_record", order["id"], {"status": "fulfilled"})
    assert kernel.get_record("membership_record", member["id"])["status"] == "pending"
    kernel.patch_record("membership_record", member["id"], {"status": "active"})
    assert kernel.get_record("order_record", order["id"])["status"] == "fulfilled"


def test_inventory_does_not_mutate_membership():
    kernel = PlatformKernel(namespace="stock-member")
    kernel.as_staff()
    member = kernel.create_record(
        "membership_record",
        {"reference": "MEM-1", "member_name": "Ada", "status": "active"},
    )
    stock = kernel.create_record(
        "inventory_record",
        {"sku": "PART-1", "quantity": 10, "reorder_level": 2},
    )
    kernel.patch_record("inventory_record", stock["id"], {"quantity": 4})
    assert kernel.get_record("membership_record", member["id"])["status"] == "active"
    kernel.patch_record("membership_record", member["id"], {"status": "inactive"})
    assert kernel.get_record("inventory_record", stock["id"])["quantity"] == 4


def test_generated_app_auth_coexists_without_coupling():
    prompt = "Staff membership management. Users need to sign in. No checkout."
    ids = _ids(
        ProductIntent(
            purpose=prompt,
            source_prompt=prompt,
            staff_review=True,
            authentication_required=True,
        )
    )
    assert {"membership.member", "auth.sign_in", "auth.profile"} <= ids
    kernel = PlatformKernel(namespace="member-auth-coexist")
    kernel.as_staff()
    member = kernel.create_record(
        "membership_record",
        {"reference": "MEM-1042", "member_name": "Sarah Mitchell", "status": "active"},
    )
    kernel.patch_record("membership_record", member["id"], {"status": "cancelled"})
    keys = {entity_key for entity_key, _record_id in kernel.records}
    assert keys == {"membership_record"}


def test_membership_public_surface_is_not_inferred():
    from src.domain.app_blocks import compiler

    registry = load_default_registry()
    prompt = "Staff membership management."
    plan = compiler.compile_install_plan(_spec(prompt), source_prompt=prompt, registry=registry)
    assert "membership-management" in plan.required_feature_packs
    assert "payment-checkout" not in plan.required_feature_packs
    public_markers = ("/join", "/register-member", "/membership-signup", "/member-login")
    membership_blocks = [block for block in plan.blocks if block.id == "membership.member"]
    assert membership_blocks
    for block in membership_blocks:
        joined = " ".join(str(value) for value in block.slots.values())
        assert not any(marker in joined for marker in public_markers)
    slots = default_slots(registry.require("membership.member"), ProductIntent(purpose=prompt))
    assert slots.get("heading") == "Membership"
    assert not any(marker in " ".join(str(value) for value in slots.values()) for marker in public_markers)
    host_roots = (
        ROOT / "boilerplates" / "html" / "js" / "membership-blocks.js",
        ROOT / "boilerplates" / "nuxt_app" / "components" / "starter" / "blocks" / "MembershipTable.vue",
        ROOT / "boilerplates" / "react_app" / "src" / "components" / "starter" / "membership-blocks.jsx",
    )
    forbidden = (
        "/join",
        "/register-member",
        "/membership-signup",
        "/member-login",
        "Buy Membership",
        "Member Price",
        "createCheckout",
        "/api/checkout",
    )
    for path in host_roots:
        source = path.read_text(encoding="utf-8")
        assert not any(token in source for token in forbidden), path


def test_core_saas_and_conversation_membership_stay_isolated():
    from src.domain.app_blocks import compiler
    from src.domain.feature_packs.catalog_v2 import overlay_for

    composition = membership_composition()
    assert composition["entities"][0]["entity_key"] == "membership_record"
    seed = (ROOT / "alembic/versions/149_membership_management_pack.py").read_text(encoding="utf-8")
    assert "on_conflict_do_nothing" in seed
    assert "workspace_memberships" not in seed
    assert "conversation_membership" not in seed
    assert "app_subscription" not in seed
    assert "stripe_customer_id" not in seed
    assert "stripe_subscription_id" not in seed
    assert overlay_for("membership-management")["entities"][0].entity_key == "membership_record"
    assert overlay_for("saas-membership")["entities"][0].entity_key == "app_subscription"
    messaging_keys = {entity.entity_key for entity in overlay_for("messaging")["entities"]}
    assert "conversation_membership" in messaging_keys
    assert "membership_record" not in messaging_keys
    assert FEATURE_PACK_ALIASES["payments.subscription"] == "saas-membership"
    assert FEATURE_PACK_ALIASES["communication.messaging"] == "messaging"
    assert "membership.member" not in FEATURE_PACK_ALIASES
    prompt = "Staff membership management."
    plan = compiler.compile_install_plan(_spec(prompt), source_prompt=prompt)
    assert "membership-management" in plan.required_feature_packs
    assert "saas-membership" not in plan.required_feature_packs
    assert "messaging" not in plan.required_feature_packs


def test_membership_install_seed_is_idempotent():
    first = membership_composition()
    second = membership_composition()
    assert first["entities"][0]["entity_key"] == second["entities"][0]["entity_key"] == "membership_record"
    keys = {field["key"] for field in first["entities"][0]["schema"]["fields"]}
    assert keys == FIELDS
    seed = (ROOT / "alembic/versions/149_membership_management_pack.py").read_text(encoding="utf-8")
    assert seed.count("on_conflict_do_nothing") == 1


def test_billing_does_not_mutate_membership():
    kernel = PlatformKernel(namespace="bill-member")
    kernel.as_staff()
    member = kernel.create_record(
        "membership_record",
        {"reference": "MEM-1", "member_name": "Ada", "status": "pending"},
    )
    invoice = kernel.create_record(
        "invoice_record",
        {"reference": "INV-1", "customer_name": "Ada", "status": "issued"},
    )
    kernel.patch_record("invoice_record", invoice["id"], {"status": "paid"})
    assert kernel.get_record("membership_record", member["id"])["status"] == "pending"


def test_membership_does_not_create_invoice_or_auth_user():
    kernel = PlatformKernel(namespace="member-auth")
    kernel.as_staff()
    member = kernel.create_record(
        "membership_record",
        {"reference": "MEM-1", "member_name": "Ada", "status": "active"},
    )
    kernel.patch_record("membership_record", member["id"], {"status": "cancelled"})
    assert kernel.list_records("invoice_record") == []
    assert kernel.list_records("customer") == []
    keys = {entity_key for entity_key, _record_id in kernel.records}
    assert keys == {"membership_record"}


def test_crm_and_membership_records_coexist():
    kernel = PlatformKernel(namespace="crm-member")
    kernel.as_staff()
    customer = kernel.create_record("customer", {"name": "Ada Lovelace"})
    member = kernel.create_record(
        "membership_record",
        {"reference": "MEM-1", "member_name": "Ada Lovelace", "status": "active"},
    )
    assert customer["id"] != member["id"]
    assert kernel.get_record("customer", customer["id"])["name"] == "Ada Lovelace"
    assert kernel.get_record("membership_record", member["id"])["status"] == "active"


def test_materialized_membership_hosts_use_records_not_auth_or_stripe():
    import json
    from src.domain.app_blocks.hosts import merge_host_map

    for framework in ("html", "nuxt_app", "react_app"):
        root = ROOT / "boilerplates" / framework
        hosts = merge_host_map(json.loads((root / "p2hs/app-block-hosts.json").read_text()))
        rows = {h["slot"]: h for h in hosts["hosts"] if h["block"] == "membership.member"}
        assert {"list", "detail", "create", "edit", "empty"} <= set(rows)
        for slot in ("list", "detail", "create", "edit"):
            path = rows[slot]["implementation"].partition("#")[0]
            source = (root / path).read_text(encoding="utf-8")
            assert "membership_record" in source
            assert "createCheckout" not in source
            assert "/api/checkout" not in source
            assert "stripe_subscription_id" not in source
            assert "workspace_membership" not in source


def test_membership_freeze_receipts_and_coverage_next_family():
    receipt = RECEIPT.read_text(encoding="utf-8")
    audit = AUDIT.read_text(encoding="utf-8")
    for required in (
        "membership_admitted=yes",
        "canonical_app_block=membership.member",
        "canonical_feature_pack=membership-management",
        "generated_app_auth_coupling=no",
        "saas_membership_coupling=no",
        "family_frozen=yes",
        "next_architecture_family=reporting",
        "crm_composition_proven=pass",
        "inventory_composition_proven=pass",
        "documents_composition_proven=pass",
        "conversation_membership_isolation=pass",
        "public_signup_inferred=no",
    ):
        assert required in receipt or required in audit, required
    registry = yaml.safe_load(REGISTRY.read_text(encoding="utf-8"))
    by_id = {row["id"]: row for row in registry["capabilities"]}
    assert by_id["membership.member"]["status"] == "frozen"
    assert by_id["membership"]["status"] == "frozen"
    assert registry["next_architecture_family"] == "not_yet_selected"
    assert registry["family_just_frozen"] == "visualization_calculation"


def test_membership_production_smoke_script():
    from scripts.architecture_lane_membership_smoke import run_smoke

    payload = run_smoke()
    assert payload["pass"] is True, payload["failures"]
    assert payload["on_disk_membership_recipes"] == []
