"""Phase 4 form controls — toggle, consent, address, phone, matrix, ranking,
barcode, camera, autonumber — and the server-checked "unique" and
"exists_in" validation rules (app/crud/form_stored_rules.py)."""

import uuid


async def _form(client, headers, fields: list[dict], **settings) -> dict:
    resp = await client.post(
        "/forms",
        headers=headers,
        json={"name": f"Pytest Controls {uuid.uuid4().hex[:6]}", "settings": settings, "field_definitions": fields},
    )
    assert resp.status_code == 201, resp.text
    published = await client.post(f"/forms/{resp.json()['form_id']}/publish", headers=headers)
    assert published.status_code == 200, published.text
    return published.json()


async def _submit(client, headers, form: dict, values: dict, draft: bool = False):
    return await client.post(
        "/form-submissions",
        headers=headers,
        json={"form_id": form["form_id"], "form_version": form["version"], "field_values": values, "draft": draft},
    )


def _messages(resp) -> str:
    detail = resp.json()["detail"]
    return " | ".join(d["msg"] for d in detail) if isinstance(detail, list) else str(detail)


CONTROLS = [
    {"name": "mobile", "type": "phone", "label": "Mobile", "default_country": "+91"},
    {"name": "home", "type": "address", "label": "Home address"},
    {"name": "agree", "type": "consent", "label": "Consent", "required": True, "consent_text": "I agree to the [terms](https://example.com/terms)."},
    {"name": "notify", "type": "toggle", "label": "Send me updates"},
    {"name": "service", "type": "matrix", "label": "Service", "matrix_rows": ["Punctuality", "Cleanliness"], "options": ["Poor", "Fair", "Good"]},
    {"name": "priorities", "type": "ranking", "label": "Priorities", "options": ["Fares", "Routes", "Timings"]},
    {"name": "ticket", "type": "barcode", "label": "Ticket code"},
    {"name": "photo", "type": "camera", "label": "Photo"},
]


async def test_new_controls_store_their_shapes(client, auth_headers):
    form = await _form(client, auth_headers, CONTROLS)
    good = {
        "mobile": "98470 12345",
        "home": {"line1": " 12 MG Road ", "city": "Kochi", "postal_code": "682016", "line2": ""},
        "agree": True,
        "notify": "true",
        "service": {"Punctuality": "Good", "Cleanliness": "Fair"},
        "priorities": ["Timings", "Fares", "Routes"],
        "ticket": "KL-2026-0042",
    }
    resp = await _submit(client, auth_headers, form, good)
    assert resp.status_code == 201, resp.text
    values = resp.json()["field_values"]
    assert values["mobile"] == "+91 9847012345"  # the default country, digits kept
    assert values["home"] == {"line1": "12 MG Road", "city": "Kochi", "postal_code": "682016"}
    assert values["notify"] is True
    assert values["priorities"] == ["Timings", "Fares", "Routes"]

    bad = {
        **good,
        "mobile": "+91 12",
        "home": {"line1": "x", "planet": "Mars"},
        "service": {"Punctuality": "Excellent"},
        "priorities": ["Fares", "Fares", "Routes"],
        "photo": str(uuid.uuid4()),
    }
    resp = await _submit(client, auth_headers, form, bad)
    assert resp.status_code == 422
    text = _messages(resp)
    for fragment in ("Mobile: Must be a phone number of 4 to 14 digits", "Home address: Must be an address", "Service: Has an answer that isn't one of the choices", "Priorities: Must order every option exactly once", "Photo: Isn't an uploaded file"):
        assert fragment in text, text


async def test_required_consent_address_and_matrix(client, auth_headers):
    fields = [
        {**CONTROLS[0], "required": True},
        {**CONTROLS[1], "required": True},
        CONTROLS[2],
        {**CONTROLS[4], "required": True},
    ]
    form = await _form(client, auth_headers, fields)
    resp = await _submit(client, auth_headers, form, {"mobile": "+91 ", "home": {"line1": "12 MG Road"}, "agree": False, "service": {"Punctuality": "Good"}})
    assert resp.status_code == 422, resp.text
    # A bare dial code is blank; an address needs its street and city; a
    # consent must be ticked; every statement of a matrix needs an answer.
    assert set(resp.json()["detail"]["missing_required_fields"]) == {"mobile", "home", "agree", "service"}
    ok = await _submit(client, auth_headers, form, {"mobile": "+44 20 7946 0958", "home": {"line1": "1 Main St", "city": "Leeds"}, "agree": True, "service": {"Punctuality": "Good", "Cleanliness": "Poor"}})
    assert ok.status_code == 201, ok.text
    assert ok.json()["field_values"]["mobile"] == "+44 2079460958"


async def test_autonumber_is_given_on_submit_and_kept(client, auth_headers):
    form = await _form(
        client,
        auth_headers,
        [
            {"name": "subject", "type": "text", "label": "Subject"},
            {"name": "ref", "type": "autonumber", "label": "Reference", "autonumber_format": "APP/{YYYY}/{SEQ:4}"},
        ],
        allow_drafts=True,
    )
    first = await _submit(client, auth_headers, form, {"subject": "Fares", "ref": "APP/1999/9999"})
    assert first.status_code == 201, first.text
    ref = first.json()["field_values"]["ref"]
    assert ref.startswith("APP/20") and ref.endswith("/0001")  # the caller's value is ignored

    edited = await client.patch(f"/form-submissions/{first.json()['id']}", headers=auth_headers, json={"field_values": {"subject": "Routes", "ref": "X"}})
    assert edited.status_code == 200, edited.text
    assert edited.json()["field_values"]["ref"] == ref

    draft = await _submit(client, auth_headers, form, {"subject": "Later"}, draft=True)
    assert draft.status_code == 201, draft.text
    assert draft.json()["field_values"].get("ref") is None
    done = await client.patch(f"/form-submissions/{draft.json()['id']}", headers=auth_headers, json={"field_values": {"subject": "Later"}, "submit": True})
    assert done.status_code == 200, done.text
    assert done.json()["field_values"]["ref"].endswith("/0002")


async def test_unique_rule(client, auth_headers):
    form = await _form(
        client,
        auth_headers,
        [{"name": "email", "type": "email", "label": "Email", "validation_rules": [{"type": "unique"}]}],
        allow_drafts=True,
    )
    first = await _submit(client, auth_headers, form, {"email": "asha@example.com"})
    assert first.status_code == 201, first.text
    # Case and surrounding spaces don't make a value different.
    clash = await _submit(client, auth_headers, form, {"email": " Asha@Example.com "})
    assert clash.status_code == 422
    assert "Email must be unique" in _messages(clash)
    # A draft isn't checked (nor counted); editing a submission keeps its own value.
    assert (await _submit(client, auth_headers, form, {"email": "asha@example.com"}, draft=True)).status_code == 201
    same = await client.patch(f"/form-submissions/{first.json()['id']}", headers=auth_headers, json={"field_values": {"email": "asha@example.com"}})
    assert same.status_code == 200, same.text


async def test_exists_in_rule(client, auth_headers):
    master = await client.post(
        "/masters",
        headers=auth_headers,
        json={"name": f"Pytest Depots {uuid.uuid4().hex[:6]}", "field_schema": [{"name": "code", "type": "varchar"}]},
    )
    assert master.status_code == 201, master.text
    mid = master.json()["id"]
    record = await client.post(f"/masters/{mid}/records", headers=auth_headers, json={"field_values": {"code": "EKM"}})
    assert record.status_code == 201, record.text

    form = await _form(
        client,
        auth_headers,
        [
            {
                "name": "depot",
                "type": "text",
                "label": "Depot code",
                "validation_rules": [{"type": "exists_in", "source": f"master:{mid}", "source_field": "code", "message": "No depot has the code you entered."}],
            },
            {"name": "depot_id", "type": "text", "label": "Depot id", "validation_rules": [{"type": "exists_in", "source": f"master:{mid}", "source_field": "id"}]},
        ],
    )
    ok = await _submit(client, auth_headers, form, {"depot": "EKM", "depot_id": record.json()["id"]})
    assert ok.status_code == 201, ok.text
    bad = await _submit(client, auth_headers, form, {"depot": "TVM", "depot_id": str(uuid.uuid4())})
    assert bad.status_code == 422
    text = _messages(bad)
    assert "Depot code: No depot has the code you entered." in text
    assert "Depot id: Depot id isn't a known value." in text


async def test_settings_are_checked(client, auth_headers):
    async def create(fields):
        return await client.post("/forms", headers=auth_headers, json={"name": f"Pytest Bad {uuid.uuid4().hex[:6]}", "field_definitions": fields})

    assert (await create([{"name": "m", "type": "matrix", "label": "M", "options": ["a"]}])).status_code == 422
    assert (await create([{"name": "r", "type": "ranking", "label": "R", "options": ["only"]}])).status_code == 422
    assert (await create([{"name": "n", "type": "autonumber", "label": "N", "autonumber_format": "APP-1"}])).status_code == 422
    assert (await create([{"name": "p", "type": "phone", "label": "P", "default_country": "91"}])).status_code == 422
    assert (await create([{"name": "t", "type": "text", "label": "T", "validation_rules": [{"type": "exists_in"}]}])).status_code == 422
    # A datasource uses its own Unique setting instead.
    resp = await client.post(
        "/masters",
        headers=auth_headers,
        json={"name": f"Pytest Rule {uuid.uuid4().hex[:6]}", "field_schema": [{"name": "c", "type": "varchar", "validation_rules": [{"type": "unique"}]}]},
    )
    assert resp.status_code == 422
