from src.domain.codegen.generation_plan import compile_generation_plan
from src.domain.forms.canonical import FormField, project_form_definition
from src.domain.projects.form_requirement_sync import known_forms_payload, looks_form_related
from src.domain.projects.form_requirements import (
    FormRequirement,
    FormRequirementDelta,
    apply_form_requirement_delta,
    resolved_form_definitions_config,
)

from tests.test_generation_plan import app_plan, discovery


def test_apply_delta_adds_new_field_preserving_order():
    existing = (
        FormRequirement(
            form_key="quote_request",
            fields=(FormField(key="name", type="text", required=True),),
        ),
    )
    delta = FormRequirementDelta(
        form_key="quote_request", add_fields=(FormField(key="phone", type="phone"),)
    )
    merged = apply_form_requirement_delta(existing, delta)
    assert len(merged) == 1
    assert [f.key for f in merged[0].fields] == ["name", "phone"]


def test_apply_delta_update_replaces_in_place():
    existing = (
        FormRequirement(
            form_key="quote_request",
            fields=(
                FormField(key="name", type="text", required=True),
                FormField(key="phone", type="phone", required=False),
            ),
        ),
    )
    delta = FormRequirementDelta(
        form_key="quote_request",
        update_fields=(FormField(key="phone", type="phone", required=True),),
    )
    merged = apply_form_requirement_delta(existing, delta)
    fields = merged[0].fields
    assert [f.key for f in fields] == ["name", "phone"]
    assert fields[1].required is True


def test_apply_delta_remove_wins_over_same_delta_add():
    existing = (FormRequirement(form_key="quote_request", fields=()),)
    delta = FormRequirementDelta(
        form_key="quote_request",
        add_fields=(FormField(key="phone", type="phone"),),
        remove_fields=("phone",),
    )
    merged = apply_form_requirement_delta(existing, delta)
    assert merged[0].fields == ()


def test_apply_delta_does_not_touch_other_forms():
    existing = (
        FormRequirement(
            form_key="contact",
            fields=(FormField(key="message", type="textarea", required=True),),
        ),
        FormRequirement(
            form_key="quote_request",
            fields=(FormField(key="name", type="text", required=True),),
        ),
    )
    delta = FormRequirementDelta(
        form_key="quote_request", add_fields=(FormField(key="phone", type="phone"),)
    )
    merged = apply_form_requirement_delta(existing, delta)
    by_key = {r.form_key: r for r in merged}
    assert [f.key for f in by_key["contact"].fields] == ["message"]
    assert [f.key for f in by_key["quote_request"].fields] == ["name", "phone"]


def test_apply_delta_new_form_starts_empty():
    delta = FormRequirementDelta(
        form_key="quote_request", add_fields=(FormField(key="name", type="text"),)
    )
    merged = apply_form_requirement_delta((), delta)
    assert merged[0].form_key == "quote_request"
    assert merged[0].access == "public_anonymous"


def test_resolved_form_definitions_config_omits_empty_requirement():
    requirements = [
        FormRequirement(form_key="contact", fields=()),
        FormRequirement(
            form_key="quote_request",
            fields=(FormField(key="name", type="text", required=True),),
        ),
    ]
    config = resolved_form_definitions_config(requirements)
    assert "contact" not in config
    assert config["quote_request"]["fields"][0]["key"] == "name"


def test_looks_form_related_generic_hints():
    assert looks_form_related("Add a phone field to the quote form", frozenset())
    assert looks_form_related("Add a budget dropdown to the contact us form", frozenset())
    assert not looks_form_related("Make the hero banner blue", frozenset())
    assert not looks_form_related("", frozenset())


def test_looks_form_related_known_form_key():
    assert looks_form_related("drop the extra field on quote_request", frozenset({"quote_request"}))


def test_onboarding_requirement_matches_across_generation_plan_pack_config_and_runtime():
    """One resolved FormRequirement -> GenerationPlan, pack config projection, and
    runtime resolution must all agree on the same field set. This is the
    cross-layer guarantee the whole mechanism exists to provide (see the Forms
    composition-drift incident this was built to close)."""
    requirement = FormRequirement(
        form_key="quote_request",
        fields=(
            FormField(key="name", type="text", required=True),
            FormField(key="phone", type="phone"),
            FormField(
                key="budget",
                type="select",
                options=(
                    {"value": "0-1000", "label": "0 to 1000"},
                    {"value": "1001-5000", "label": "1001 to 5000"},
                ),
            ),
        ),
    )

    # Layer 1: GenerationPlan.
    result = discovery()
    result.spec.form_requirements = [requirement]
    plan = compile_generation_plan(result, app_plan())
    plan_definition = plan.composition.form_definitions["quote_request"]
    assert [f.key for f in plan_definition.fields] == ["name", "phone", "budget"]

    # Layer 2: pack config projection (what install_feature_pack persists).
    pack_config = resolved_form_definitions_config([requirement])
    assert [f["key"] for f in pack_config["quote_request"]["fields"]] == ["name", "phone", "budget"]

    # Layer 3: runtime resolution (what Forms submission validates against).
    runtime_definition = project_form_definition(
        form_key="quote_request", config={"form_definitions": pack_config}
    )
    assert [f.key for f in runtime_definition.fields] == ["name", "phone", "budget"]
    assert runtime_definition.fields[2].options[0].value == "0-1000"

    # All three agree byte-for-byte once serialized.
    assert plan_definition.model_dump(mode="json") == runtime_definition.model_dump(mode="json")


def test_first_delta_for_a_form_seeds_from_canonical_not_empty():
    """Regression: a brand-new project's first-ever form customization must
    extend the canonical fields the generated UI already relies on, never
    replace them. Reproduces a live incident where this started from an empty
    list and would have dropped name/email/project_type/budget/message."""
    delta = FormRequirementDelta(
        form_key="quote_request",
        add_fields=(FormField(key="alternate_phone", type="phone"),),
    )
    merged = apply_form_requirement_delta((), delta)
    keys = [f.key for f in merged[0].fields]
    assert keys == [
        "name", "email", "project_type", "budget", "message", "attachment", "alternate_phone"
    ]


def test_known_forms_payload_includes_canonical_runtime_keys_even_when_empty():
    """Regression: an extractor call for a project with no custom FormRequirement
    yet (every project's day-one state) must still see contact/quote_request as
    known forms, with their real canonical fields -- not an empty list. Reproduces
    a live incident where the extractor returned applies=false for an unambiguous
    'add a field to the quote form' prompt because `known_forms` was empty."""
    payload = known_forms_payload(())
    by_key = {row["form_key"]: row for row in payload}
    assert set(by_key) == {"contact", "quote_request"}
    assert [f["key"] for f in by_key["quote_request"]["fields"]] == [
        "name", "email", "project_type", "budget", "message", "attachment",
    ]
    assert [f["key"] for f in by_key["contact"]["fields"]] == ["name", "email", "phone", "message"]


def test_known_forms_payload_prefers_custom_requirement_over_canonical():
    custom = FormRequirement(
        form_key="contact", fields=(FormField(key="name", type="text", required=True),)
    )
    payload = known_forms_payload((custom,))
    by_key = {row["form_key"]: row for row in payload}
    assert [f["key"] for f in by_key["contact"]["fields"]] == ["name"]
