"""7P Forms App Block / Feature Pack / Starter composition binding."""
from __future__ import annotations

from pathlib import Path

from src.domain.app_blocks.compiler import compile_install_plan
from src.domain.app_blocks.forms_binding import sources_from_plan_and_packs
from src.domain.codegen.forms_codegen import (
    classify_form_intent,
    project_client_form_config,
    select_forms_generation,
)
from src.domain.connectors.invoke_capabilities import (
    CAPABILITY_CONVERSION_PATTERN,
    FORMS_FAMILY_PLAN,
    FORMS_HYPOTHESIZED_VERBS_NOT_FROZEN,
    INVOKE_CAPABILITIES,
)
from src.domain.connectors.public_gateway import PHASE_7_STATUS, PUBLIC_INVOCABLE_CAPABILITIES
from src.domain.forms.actions import RecordingCapabilityInvoker
from src.domain.forms.composition import (
    FORM_BINDING_CONFLICT,
    FORMS_COMPOSITION_PHASE_7P,
    CompositionFormRuntimeStore,
    FormBindingSource,
    InstalledPackView,
    classify_composition,
    collect_form_binding_sources,
    planned_form_keys,
    publish_form_readiness,
    resolve_form_bindings,
)
from src.domain.forms.persistence import (
    FormPersistenceContext,
    InMemoryFormSubmissionStore,
    get_form_submission,
)
from src.domain.forms.public import SilentCapabilityInvoker, submit_form
from src.domain.starter_library.catalog import known_contract_ids


def _pack(**config) -> InstalledPackView:
    return InstalledPackView(
        slug="contact-capture",
        status="installed",
        config=config,
    )


def _submit(store, form_key, values, invoker=None, submissions=None):
    return submit_form(
        project_id="prj_a",
        form_key=form_key,
        values=values,
        client_identity="127.0.0.1",
        runtimes=store,
        invoker=invoker or SilentCapabilityInvoker(),
        submissions=submissions or InMemoryFormSubmissionStore(),
    )


def test_7p_closes_composition_without_form_submit_capability() -> None:
    assert PHASE_7_STATUS == "complete"
    assert CAPABILITY_CONVERSION_PATTERN["phase_7_status"] == "complete"
    assert CAPABILITY_CONVERSION_PATTERN["next_program_slice"] == (
        "9_app_block_modernization"
    )
    assert CAPABILITY_CONVERSION_PATTERN["opened_families"]["forms.*"] == (
        "composition_integration"
    )
    assert FORMS_FAMILY_PLAN["status"] == "complete"
    assert FORMS_FAMILY_PLAN["composition_integration_frozen"] is True
    assert FORMS_FAMILY_PLAN["slices"][14] == "7o_codegen_exposure"
    assert FORMS_FAMILY_PLAN["slices"][15] == "7p_app_block_feature_pack_starter"
    assert FORMS_FAMILY_PLAN["next_slice"] == "9_app_block_modernization"
    assert FORMS_COMPOSITION_PHASE_7P["schema_migration"] is False
    assert FORMS_COMPOSITION_PHASE_7P["first_wins"] is False
    assert FORMS_COMPOSITION_PHASE_7P["legacy_contact_route"] == "retained"
    assert "form.submit" not in INVOKE_CAPABILITIES
    assert "form.submit" in FORMS_HYPOTHESIZED_VERBS_NOT_FROZEN
    assert "form.submit" not in PUBLIC_INVOCABLE_CAPABILITIES


def test_inventory_classification() -> None:
    assert classify_composition("forms.contact") == "COMPOSE"
    assert classify_composition("forms.quote_request") == "COMPOSE"
    assert classify_composition("contact-capture") == "COMPOSE"
    assert classify_composition("form.public_submission") == "KEEP_LEGACY"
    assert classify_composition("form.public_create") == "KEEP_LEGACY"
    assert classify_composition("submitFormEntry") == "KEEP_LEGACY"
    assert classify_composition("/api/contact") == "KEEP_LEGACY"
    assert classify_composition("form.standard") == "KEEP_LEGACY"
    assert classify_composition("crm.lead") == "KEEP_LEGACY"
    assert "form.standard" in known_contract_ids()
    contact = Path("src/domain/codegen/runtime/p2hs-contact.mjs").read_text(
        encoding="utf-8"
    )
    assert "contact" in contact
    legacy = Path("src/domain/codegen/runtime/p2hs-forms.mjs").read_text(encoding="utf-8")
    assert "submitFormEntry" in legacy


def test_contact_block_plus_pack_is_callable() -> None:
    sources = collect_form_binding_sources(
        planned_block_ids=("forms.contact",),
        packs=(_pack(notify_to_email="owner@example.com"),),
    )
    resolution = resolve_form_bindings(sources)
    store = CompositionFormRuntimeStore("prj_a", resolution)
    runtime = store.get("prj_a", "contact")
    assert runtime is not None
    assert runtime.persist_first is True
    assert runtime.definition.key == "contact"
    assert "hubspot" not in str(runtime.plan)
    client = project_client_form_config(runtime.definition)
    assert "persist_first" not in client
    assert "plan" not in client
    assert client["form_key"] == "contact"
    result = _submit(
        store,
        "contact",
        {"name": "Ada", "email": "ada@example.com", "message": "Hello"},
    )
    assert result.body["ok"] is True
    assert result.body["status"] == "accepted"
    assert result.body["submission_id"].startswith("fsb_")


def test_missing_or_uninstalled_pack_is_unavailable() -> None:
    empty = resolve_form_bindings(
        collect_form_binding_sources(planned_block_ids=("forms.contact",), packs=())
    )
    store = CompositionFormRuntimeStore("prj_a", empty)
    assert store.get("prj_a", "contact") is None
    gone = collect_form_binding_sources(
        planned_block_ids=("forms.contact",),
        packs=(InstalledPackView("contact-capture", "uninstalled", {}),),
    )
    assert resolve_form_bindings(gone).runtimes == {}


def test_pack_only_fallback_binds_contact() -> None:
    sources = collect_form_binding_sources(
        planned_block_ids=(),
        packs=(_pack(),),
    )
    assert sources[0].form_key == "contact"
    assert sources[0].owner_kind == "feature_pack"


def test_quote_plan_does_not_also_bind_contact_from_shared_pack() -> None:
    plan = compile_install_plan(
        {
            "product": {"purpose": "Roofing quotes", "first_build_goal": "quote form"},
            "actors": [],
            "workflows": [],
            "entities": [],
            "archetypes": [],
        }
    )
    sources = sources_from_plan_and_packs(plan, (_pack(),))
    keys = {source.form_key for source in sources}
    assert "quote_request" in keys
    assert "contact" not in keys


def test_form_key_collision_is_not_first_wins() -> None:
    sources = (
        FormBindingSource(
            form_key="contact",
            owner_id="forms.contact",
            owner_kind="app_block",
            pack_slug="contact-capture",
            pack_config={},
        ),
        FormBindingSource(
            form_key="contact",
            owner_id="other.contact",
            owner_kind="app_block",
            pack_slug="contact-capture",
            pack_config={},
        ),
    )
    resolution = resolve_form_bindings(sources)
    assert "contact" in resolution.conflicts
    store = CompositionFormRuntimeStore("prj_a", resolution)
    assert store.get("prj_a", "contact") is None
    assert publish_form_readiness(
        generated_form_keys=("contact",),
        resolution=resolution,
    ) == (FORM_BINDING_CONFLICT,)


def test_projects_are_isolated() -> None:
    resolution = resolve_form_bindings(
        collect_form_binding_sources(
            planned_block_ids=("forms.contact",),
            packs=(_pack(),),
        )
    )
    store = CompositionFormRuntimeStore("prj_a", resolution)
    assert store.get("prj_b", "contact") is None
    assert store.get("prj_a", "contact") is not None


def test_optional_crm_failure_still_accepts() -> None:
    resolution = resolve_form_bindings(
        collect_form_binding_sources(
            planned_block_ids=("forms.contact",),
            packs=(_pack(),),
        )
    )
    store = CompositionFormRuntimeStore("prj_a", resolution)
    invoker = RecordingCapabilityInvoker()
    result = _submit(
        store,
        "contact",
        {"name": "Ada", "email": "ada@example.com", "message": "Hi"},
        invoker=invoker,
    )
    assert result.body["ok"] is True
    dumped = str(invoker.calls)
    assert any(call["capability"] == "crm.contact.upsert" for call in invoker.calls)
    assert "hubspot.create_contact" not in dumped
    assert "salesforce.create_contact" not in dumped


def test_quote_runtime_includes_file_field() -> None:
    sources = collect_form_binding_sources(
        planned_block_ids=("forms.quote_request",),
        packs=(_pack(),),
    )
    runtime = resolve_form_bindings(sources).runtimes["quote_request"]
    keys = [field.key for field in runtime.definition.fields]
    assert "attachment" in keys
    assert runtime.definition.key == "quote_request"


def test_owner_email_stays_server_only() -> None:
    resolution = resolve_form_bindings(
        collect_form_binding_sources(
            planned_block_ids=("forms.contact",),
            packs=(_pack(notify_to_email="ops@example.com"),),
        )
    )
    runtime = resolution.runtimes["contact"]
    client = project_client_form_config(runtime.definition)
    assert "ops@example.com" not in str(client)
    assert "notify_to_email" not in str(client)


def test_new_contact_generation_uses_canonical_route() -> None:
    assert classify_form_intent("forms.contact for a new install") == (
        "public_submission_form"
    )
    decision = select_forms_generation("forms.contact")
    assert decision.runtime == "canonical_forms"
    legacy = select_forms_generation("keep /api/contact for old apps")
    assert legacy.runtime == "legacy_contact_adapter"


def test_starter_plan_form_keys_are_ready_when_pack_installed() -> None:
    plan = compile_install_plan(
        {
            "product": {
                "purpose": "Marketing site",
                "first_build_goal": "contact form",
            },
            "actors": [],
            "workflows": [],
            "entities": [],
            "archetypes": ["website"],
        }
    )
    keys = planned_form_keys([block.id for block in plan.blocks])
    resolution = resolve_form_bindings(sources_from_plan_and_packs(plan, (_pack(),)))
    assert publish_form_readiness(generated_form_keys=keys, resolution=resolution) == ()
    submissions = InMemoryFormSubmissionStore()
    result = submit_form(
        project_id="prj_a",
        form_key="contact",
        values={"name": "Ada", "email": "ada@example.com", "message": "Hello"},
        client_identity="127.0.0.1",
        runtimes=CompositionFormRuntimeStore("prj_a", resolution),
        invoker=SilentCapabilityInvoker(),
        submissions=submissions,
    )
    assert result.body["ok"] is True
    stored = get_form_submission(
        submissions,
        FormPersistenceContext(project_id="prj_a"),
        result.body["submission_id"],
    )
    assert stored is not None
    assert stored.accepted.submission.values["email"] == "ada@example.com"


def test_update_changes_runtime_snapshot_without_rewriting_submissions() -> None:
    submissions = InMemoryFormSubmissionStore()
    first = resolve_form_bindings(
        collect_form_binding_sources(
            planned_block_ids=("forms.contact",),
            packs=(_pack(notify_to_email="old@example.com"),),
        )
    )
    first_result = _submit(
        CompositionFormRuntimeStore("prj_a", first),
        "contact",
        {"name": "Ada", "email": "ada@example.com", "message": "Hello"},
        submissions=submissions,
    )
    submission_id = first_result.body["submission_id"]
    second = resolve_form_bindings(
        collect_form_binding_sources(
            planned_block_ids=("forms.contact",),
            packs=(_pack(notify_to_email="new@example.com"),),
        )
    )
    runtime = second.runtimes["contact"]
    owner = next(action for action in runtime.plan.actions if action.key == "owner_notify")
    assert owner.constants["to"] == "new@example.com"
    stored = get_form_submission(
        submissions,
        FormPersistenceContext(project_id="prj_a"),
        submission_id,
    )
    assert stored is not None
    assert stored.accepted.submission.values["message"] == "Hello"


def test_uninstall_removes_binding_and_keeps_fsb_rows() -> None:
    submissions = InMemoryFormSubmissionStore()
    installed = resolve_form_bindings(
        collect_form_binding_sources(
            planned_block_ids=("forms.contact",),
            packs=(_pack(),),
        )
    )
    accepted = _submit(
        CompositionFormRuntimeStore("prj_a", installed),
        "contact",
        {"name": "Ada", "email": "ada@example.com", "message": "Hello"},
        submissions=submissions,
    )
    gone = resolve_form_bindings(
        collect_form_binding_sources(planned_block_ids=("forms.contact",), packs=())
    )
    missing = _submit(
        CompositionFormRuntimeStore("prj_a", gone),
        "contact",
        {"name": "Ada", "email": "ada@example.com", "message": "Hello"},
        submissions=submissions,
    )
    assert missing.body["error"] == "form_unavailable"
    assert get_form_submission(
        submissions,
        FormPersistenceContext(project_id="prj_a"),
        accepted.body["submission_id"],
    ) is not None


def test_client_projection_is_provider_neutral_across_crm_backends() -> None:
    resolution = resolve_form_bindings(
        collect_form_binding_sources(
            planned_block_ids=("forms.contact",),
            packs=(_pack(),),
        )
    )
    client = project_client_form_config(resolution.runtimes["contact"].definition)
    assert client == project_client_form_config(resolution.runtimes["contact"].definition)
    dumped = str(client)
    assert "hubspot" not in dumped
    assert "salesforce" not in dumped
    assert "crm.contact.upsert" not in dumped
