"""An invitation issued before this change still works (task 10.5).

The change re-scoped guests, moved the card into the page and re-composed the invitation.
None of that is allowed to alter what a link resolves to or what an RSVP stores — a token
already sitting in someone's inbox has to keep behaving.

So this drives the guest-facing routes against a fixture invitation and asserts the parts
that must not have moved: the same guest, the same event, the same stored answer, the same
cancellation behaviour, the same locale resolution. The additions (greeting, card) are
asserted only to *exist* and to carry no guest data — their content is tested elsewhere.
"""

from typing import Any

import pytest
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")]


async def test_an_existing_token_resolves_to_the_same_guest_and_event(
    fixture_event: dict[str, Any],
) -> None:
    token = f"tok-{fixture_event['tag']}-0"
    async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client:
        response = await client.get(f"/api/invitations/by-token/{token}")

    assert response.status_code == 200, response.text
    body = response.json()
    assert body["guest"]["full_name"] == "Guest 0"
    assert body["event"]["id"] == str(fixture_event["event_id"])
    assert body["max_guests"] == 2
    assert body["status"] == "accepted"

    # Token-bearing, so it must never be cached by anything in between.
    assert response.headers["cache-control"] == "private, no-store"
    assert response.headers["referrer-policy"] == "no-referrer"


async def test_the_payload_gained_a_greeting_and_a_card_slot_and_lost_the_switcher(
    fixture_event: dict[str, Any],
) -> None:
    """The shape changed in exactly three ways, and this pins all three."""
    token = f"tok-{fixture_event['tag']}-0"
    async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client:
        body = (await client.get(f"/api/invitations/by-token/{token}")).json()

    # Added: a greeting that is never empty, in both locales.
    assert body["greeting"]["message_en"]
    assert body["greeting"]["message_bn"]
    assert body["greeting"]["invitation_type"] == "single"

    # Added: the card slot. None here — the fixture event has no published design, which is
    # the state that must render a complete page rather than an empty frame.
    assert body["card"] is None

    # Removed: no cross-event link survives (task 3.7).
    assert "other_invitations" not in body


async def test_the_open_route_still_carries_no_guest_data(
    fixture_event: dict[str, Any], session: AsyncSession
) -> None:
    """The rule the whole open route exists under, re-checked now that it carries a greeting
    and a card: neither may smuggle a guest's details onto a public, cacheable page."""
    slug = await session.scalar(
        text("SELECT slug FROM event WHERE id = :id"), {"id": fixture_event["event_id"]}
    )
    async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client:
        response = await client.get(f"/api/events/{slug}")

    assert response.status_code == 200, response.text
    body = response.json()
    assert "guest" not in body
    # The greeting on the open route is the single sentence, with nobody named.
    assert body["greeting"]["invitation_type"] == "single"

    serialised = response.text
    for leaked in ("Guest 0", "@example.test", fixture_event["tag"] + "@"):
        assert leaked not in serialised

    # Public and identical for everyone, so it stays cacheable.
    assert "public" in response.headers["cache-control"]


async def test_an_rsvp_stores_the_same_result_as_before(
    fixture_event: dict[str, Any], session: AsyncSession
) -> None:
    """The write path is untouched by this change, and this is what proves it stayed that
    way — the composition moved, the stored answer did not."""
    token = f"tok-{fixture_event['tag']}-1"
    async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client:
        response = await client.post(
            f"/api/rsvp/{token}/accept",
            json={
                "name": "Guest 1",
                "phone": "01712345678",
                "email": "guest1@example.com",
                "party_size": 2,
                "message_to_couple": "Congratulations",
            },
        )

    assert response.status_code == 200, response.text
    body = response.json()
    assert body["status"] == "accepted"
    assert body["party_size"] == 2
    assert body["cancel_url"].endswith(f"/i/{token}/cancel")
    assert body["calendar_url"].endswith(f"/api/ics/{token}")

    await session.rollback()
    stored = (
        await session.execute(
            text(
                "SELECT r.party_size, r.message_to_couple FROM rsvp r "
                "JOIN invitation i ON i.id = r.invitation_id WHERE i.token = :t"
            ),
            {"t": token},
        )
    ).one()
    assert stored.party_size == 2
    assert stored.message_to_couple == "Congratulations"


async def test_cancellation_is_still_post_only(fixture_event: dict[str, Any]) -> None:
    """A link-preview crawler issues a GET. It must not be able to cancel an RSVP, and the
    re-composition did not touch that — but it is cheap to keep proving."""
    token = f"tok-{fixture_event['tag']}-2"
    async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client:
        crawler = await client.get(f"/api/rsvp/{token}/cancel")
        assert crawler.status_code == 405

        deliberate = await client.post(f"/api/rsvp/{token}/cancel", json={"confirm": True})
        assert deliberate.status_code == 200
        assert deliberate.json()["status"] == "cancelled"


async def test_locale_resolution_is_unchanged(fixture_event: dict[str, Any]) -> None:
    """The guest's stored preference still drives the payload, and the greeting rides along
    with both locales so the page toggle costs no round trip."""
    token = f"tok-{fixture_event['tag']}-3"
    async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client:
        body = (await client.get(f"/api/invitations/by-token/{token}")).json()

    assert body["guest"]["preferred_locale"] == "en"
    assert body["couple"]["default_locale"] in {"en", "bn"}
    assert body["greeting"]["message_en"] != body["greeting"]["message_bn"]
