"""Forms inside a form ("embedded_form" fields): submitting the parent saves
each embedded form's answers as that form's own submission, in the same
transaction, and the parent keeps its id; editing the parent updates the
same embedded submission (app/routers/form_submissions.py::_save_embedded).
Also: an autocomplete taking several comma-separated choices."""

import uuid

from app.core.auth import create_access_token
from app.models.identity import Role, User, UserRole
from tests.conftest import AsyncSessionLocal


async def _published(client, headers, name: str, fields: list[dict]) -> dict:
    resp = await client.post("/forms", headers=headers, json={"name": f"{name} {uuid.uuid4().hex[:6]}", "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 _parent_and_child(client, headers) -> tuple[dict, dict]:
    child = await _published(
        client,
        headers,
        "Applicant",
        [
            {"name": "applicant_name", "type": "text", "label": "Applicant Name", "required": True},
            {"name": "phone", "type": "text", "label": "Phone"},
        ],
    )
    parent = await _published(
        client,
        headers,
        "Application",
        [
            {"name": "subject", "type": "text", "label": "Subject"},
            {"name": "applicant", "type": "embedded_form", "label": "Applicant", "embedded_form_id": child["form_id"], "required": True},
        ],
    )
    return parent, child


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


async def test_embedded_answers_are_saved_as_the_embedded_forms_submission(client, auth_headers):
    parent, child = await _parent_and_child(client, auth_headers)
    resp = await _submit(client, auth_headers, parent, {"subject": "Bus timings", "applicant": {"applicant_name": "Asha", "phone": "98470"}})
    assert resp.status_code == 201, resp.text
    saved = resp.json()
    child_id = saved["field_values"]["applicant"]
    uuid.UUID(child_id)

    stored = (await client.get(f"/form-submissions/{child_id}", headers=auth_headers)).json()
    assert stored["form_id"] == child["form_id"]
    assert stored["field_values"] == {"applicant_name": "Asha", "phone": "98470"}

    # Opened to edit: the embedded answers in place of the id.
    expanded = (await client.get(f"/form-submissions/{saved['id']}?expand=embedded", headers=auth_headers)).json()
    assert expanded["field_values"]["applicant"] == {"applicant_name": "Asha", "phone": "98470"}

    # Editing updates the same embedded submission, not a new one.
    edited = await client.patch(
        f"/form-submissions/{saved['id']}", headers=auth_headers, json={"field_values": {"subject": "Fares", "applicant": {"applicant_name": "Asha M", "phone": "98470"}}}
    )
    assert edited.status_code == 200, edited.text
    assert edited.json()["field_values"]["applicant"] == child_id
    assert (await client.get(f"/form-submissions/{child_id}", headers=auth_headers)).json()["field_values"]["applicant_name"] == "Asha M"

    # Sending the stored id back unchanged keeps it.
    kept = await client.patch(f"/form-submissions/{saved['id']}", headers=auth_headers, json={"field_values": {"subject": "Fares", "applicant": child_id}})
    assert kept.status_code == 200, kept.text
    assert kept.json()["field_values"]["applicant"] == child_id


async def test_embedded_forms_rules_apply_and_nothing_is_saved_on_error(client, auth_headers):
    parent, child = await _parent_and_child(client, auth_headers)
    missing = await _submit(client, auth_headers, parent, {"subject": "x", "applicant": {"phone": "1"}})
    assert missing.status_code == 422
    assert "Applicant" in str(missing.json()["detail"])

    # The embedded form itself is required on the parent.
    blank = await _submit(client, auth_headers, parent, {"subject": "x", "applicant": {"applicant_name": "", "phone": ""}})
    assert blank.status_code == 422

    # An id the caller makes up isn't accepted in place of answers.
    forged = await _submit(client, auth_headers, parent, {"subject": "x", "applicant": str(uuid.uuid4())})
    assert forged.status_code == 422

    rows = (await client.post("/datasets/query", headers=auth_headers, json={"source_type": "form", "source_id": child["form_id"]})).json()
    assert rows["rows"] == []


async def test_a_form_cant_embed_itself(client, auth_headers):
    form = await _published(client, auth_headers, "Loop", [{"name": "x", "type": "text", "label": "X"}])
    created = await client.post(f"/forms/{form['form_id']}/new-version", headers=auth_headers)
    assert created.status_code in (200, 201), created.text
    patched = await client.patch(
        f"/forms/{form['form_id']}",
        headers=auth_headers,
        json={"field_definitions": [{"name": "x", "type": "text", "label": "X"}, {"name": "me", "type": "embedded_form", "label": "Me", "embedded_form_id": form["form_id"]}]},
    )
    assert patched.status_code == 200, patched.text
    live = (await client.post(f"/forms/{form['form_id']}/publish", headers=auth_headers)).json()
    resp = await _submit(client, auth_headers, live, {"x": "1", "me": {"x": "2"}})
    assert resp.status_code == 422
    assert "embed themselves" in str(resp.json()["detail"])


def test_embedded_form_definition_rules():
    import pytest
    from pydantic import ValidationError

    from app.schemas.workflow import FormFieldDef

    with pytest.raises(ValidationError):
        FormFieldDef(name="a", type="embedded_form", label="A")
    with pytest.raises(ValidationError):
        FormFieldDef(name="a", type="text", label="A", embedded_form_id=uuid.uuid4())
    with pytest.raises(ValidationError):
        FormFieldDef(name="a", type="dropdown", label="A", options=["x"], allow_multiple=True)


async def test_portal_grant_on_a_form_covers_the_forms_it_embeds(client, auth_headers, test_tenant):
    parent, child = await _parent_and_child(client, auth_headers)
    other = await _published(client, auth_headers, "Unrelated", [{"name": "x", "type": "text", "label": "X"}])
    async with AsyncSessionLocal() as db:
        async with db.begin():
            role = Role(tenant_id=test_tenant["tenant_id"], name=f"Portal {uuid.uuid4().hex[:6]}", scope="portal", action_matrix={"forms": [parent["form_id"]]})
            db.add(role)
            await db.flush()
            user = User(tenant_id=test_tenant["tenant_id"], name="Citizen", email=f"citizen-{role.id}@example.com", status="active")
            db.add(user)
            await db.flush()
            db.add(UserRole(user_id=user.id, role_id=role.id))
            user_id = user.id
    headers = {"Authorization": f"Bearer {create_access_token(tenant_id=test_tenant['tenant_id'], user_id=user_id, vendor_id=uuid.uuid4())}"}
    assert (await client.get(f"/forms/{child['form_id']}/live", headers=headers)).status_code == 200
    assert (await client.get(f"/forms/{other['form_id']}/live", headers=headers)).status_code == 403


async def test_autocomplete_with_several_choices(client, auth_headers):
    form = await _published(
        client,
        auth_headers,
        "Districts",
        [{"name": "districts", "type": "autocomplete", "label": "Districts", "options": ["Kochi", "Thrissur", "Kollam"], "allow_multiple": True}],
    )
    ok = await _submit(client, auth_headers, form, {"districts": "Kochi,Thrissur , Kochi"})
    assert ok.status_code == 201, ok.text
    assert ok.json()["field_values"]["districts"] == "Kochi, Thrissur"

    bad = await _submit(client, auth_headers, form, {"districts": "Kochi, Mumbai"})
    assert bad.status_code == 422


def test_label_source_definition_rules():
    import pytest
    from pydantic import ValidationError

    from app.schemas.workflow import FormFieldDef

    FormFieldDef(name="a", type="label", label="A", label_source="text", label_text="Fee: {fee}")
    FormFieldDef(
        name="a", type="label", label="A", label_source="record", label_source_type="master", label_source_id=uuid.uuid4(),
        label_display_field="address", label_match_field="id", label_match_value="{office}",
    )
    with pytest.raises(ValidationError):
        FormFieldDef(name="a", type="label", label="A", label_source="record", label_source_type="master")
    with pytest.raises(ValidationError):
        FormFieldDef(name="a", type="text", label="A", label_source="text")


def test_label_height_is_for_labels():
    import pytest
    from pydantic import ValidationError

    from app.schemas.navigation import PageBlockDef
    from app.schemas.workflow import FormFieldDef

    assert FormFieldDef(name="a", type="label", label="A", label_height=80).label_height == 80
    with pytest.raises(ValidationError):
        FormFieldDef(name="a", type="label", label="A", label_height=5)
    with pytest.raises(ValidationError):
        FormFieldDef(name="a", type="text", label="A", label_height=80)
    assert PageBlockDef(id="l", type="label", auto_height=True).auto_height is True


def test_hidden_fields_take_a_source_but_no_height():
    import pytest
    from pydantic import ValidationError

    from app.schemas.workflow import FormFieldDef

    FormFieldDef(
        name="h", type="hidden", label="H", label_source="record", label_source_type="form", label_source_id=uuid.uuid4(),
        label_display_field="code", label_match_field="id", label_match_value="{{recordId}}",
    )
    with pytest.raises(ValidationError):
        FormFieldDef(name="h", type="hidden", label="H", label_height=80)
