"""Creating a guest from the admin actually works (task 3.7).

This route had no test, and it was broken for every single call: `guest.invitations = []`
looks like a harmless initialisation, but assigning to a relationship makes SQLAlchemy load
the current collection first so it can compute what changed — a lazy SELECT, which is
illegal under asyncio. Every "Add guest" ended in MissingGreenlet and a 500.

Nothing in the existing suite could have caught it. The unit tests never touch a session,
and the route-guard tests read the OpenAPI document rather than calling anything. So this
one drives the real endpoint against real Postgres, which is the only place the bug lives.
"""

import uuid
from collections.abc import AsyncIterator
from typing import Any

import pytest
import pytest_asyncio
from httpx import ASGITransport, AsyncClient
from sqlalchemy import text
from sqlalchemy.ext.asyncio import AsyncSession

from app.main import app

from .conftest import requires_db

pytestmark = [requires_db, pytest.mark.usefixtures("app_engine_per_loop")]


@pytest_asyncio.fixture
async def cleanup(session: AsyncSession) -> AsyncIterator[list[str]]:
    """Names to delete afterwards, so a run against the dev database leaves nothing behind."""
    names: list[str] = []
    yield names
    for name in names:
        await session.execute(
            text(
                "DELETE FROM invitation WHERE guest_id IN "
                "(SELECT id FROM guest WHERE full_name = :n)"
            ),
            {"n": name},
        )
        await session.execute(text("DELETE FROM guest WHERE full_name = :n"), {"n": name})
    await session.commit()


async def _post_guest(event_id: Any, payload: dict[str, Any]) -> Any:
    """A guest is always created under an event now — the event is the route (design D11)."""
    async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client:
        return await client.post(f"/api/admin/events/{event_id}/guests", json=payload)


def _body(name: str, **overrides: Any) -> dict[str, Any]:
    payload: dict[str, Any] = {
        "full_name": name,
        "phone": "01712345000",
        "email": f"{uuid.uuid4().hex[:10]}@example.com",
        "side": "bride",
        "preferred_locale": "en",
        "preferred_channel": "auto",
        "group_tag": ["family"],
        "max_guests": 3,
    }
    payload.update(overrides)
    return payload


@pytest.mark.usefixtures("super_admin")
async def test_a_guest_is_created_with_an_invitation(
    cleanup: list[str], fixture_event: dict[str, Any]
) -> None:
    name = f"Create Test {uuid.uuid4().hex[:8]}"
    cleanup.append(name)

    response = await _post_guest(fixture_event["event_id"], _body(name))

    assert response.status_code == 201, response.text
    body = response.json()
    assert body["full_name"] == name
    assert body["event_id"] == str(fixture_event["event_id"])
    # The invitation is the point. A guest row with no invitation is invisible to every
    # send, every reminder and every headcount.
    assert len(body["invitations"]) == 1
    assert body["invitations"][0]["max_guests"] == 3


@pytest.mark.usefixtures("super_admin")
async def test_the_same_contact_is_refused_on_the_same_event(
    cleanup: list[str], fixture_event: dict[str, Any]
) -> None:
    """Uniqueness is per event, so a second row with this phone on THIS list is a duplicate."""
    name = f"Dup Test {uuid.uuid4().hex[:8]}"
    cleanup.append(name)
    phone = "01712345123"

    first = await _post_guest(fixture_event["event_id"], _body(name, phone=phone))
    assert first.status_code == 201, first.text

    second = await _post_guest(fixture_event["event_id"], _body(f"{name} Again", phone=phone))
    cleanup.append(f"{name} Again")
    assert second.status_code == 409
    assert name in second.json()["detail"]


@pytest.mark.usefixtures("super_admin")
async def test_a_guest_with_neither_phone_nor_email_is_refused(
    fixture_event: dict[str, Any],
) -> None:
    """There would be no way to reach them and no way to recognise them as a duplicate."""
    response = await _post_guest(
        fixture_event["event_id"],
        _body("Unreachable", phone=None, email=None),
    )
    assert response.status_code == 422


@pytest.mark.usefixtures("super_admin")
async def test_creating_against_an_unknown_event_is_not_found() -> None:
    response = await _post_guest(uuid.uuid4(), _body("Nowhere"))
    assert response.status_code == 404
