"""A first Google sign-in must leave a trace, and a record (task 3.3, spec admin-auth).

Under `add-admin-access-control` an unknown verified Google account is no longer turned away:
it becomes a `PENDING` row in the roster's approval queue, and the audit gains an entry
saying so (design D6). The visitor is told they are awaiting approval — the old generic 401
protected an allowlist that self-registration has already made discoverable, and keeping it
would strand every legitimate new host at a dead end that reads as a bug (design D7).

The commit is what these tests really exist for, and it predates the change. `audit.record`
adds to the caller's transaction rather than committing, so an action cannot be logged as
having happened when it was rolled back — correct everywhere else, and exactly wrong here,
since the refusal response rolls the session back and would take both the account and the
evidence with it.
"""

import uuid
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 app.services import auth as auth_service

from .conftest import requires_db

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


@pytest.fixture
def unknown_email() -> str:
    """Namespaced so a run against the dev database cannot collide with a real address."""
    return f"nobody-{uuid.uuid4().hex[:10]}@example.test"


@pytest.fixture
def _google_accepts(monkeypatch: pytest.MonkeyPatch, unknown_email: str) -> None:
    """Stand in for Google.

    The allowlist check is what is under test, so verification has to succeed — otherwise
    the request fails earlier and never reaches the branch this test is about.
    """

    async def fake_verify(_id_token: str) -> dict[str, Any]:
        return {"email": unknown_email, "email_verified": True, "name": "Nobody"}

    monkeypatch.setattr(auth_service, "verify_google_id_token", fake_verify)


async def _entries_for(session: AsyncSession, email: str, action: str) -> int:
    count = await session.scalar(
        text("SELECT count(*) FROM audit_log WHERE action = :action AND actor_email = :email"),
        {"email": email, "action": action},
    )
    return int(count or 0)


async def _accounts_for(session: AsyncSession, email: str) -> int:
    count = await session.scalar(
        text("SELECT count(*) FROM admin_user WHERE email = :email"), {"email": email}
    )
    return int(count or 0)


async def _forget(session: AsyncSession, email: str) -> None:
    await session.execute(
        text("DELETE FROM audit_log WHERE actor_email = :email"), {"email": email}
    )
    await session.execute(text("DELETE FROM admin_user WHERE email = :email"), {"email": email})
    await session.commit()


@pytest.mark.usefixtures("_google_accepts")
async def test_a_first_signin_creates_a_pending_account_and_records_it(
    session: AsyncSession, unknown_email: str
) -> None:
    transport = ASGITransport(app=app)
    async with AsyncClient(transport=transport, base_url="http://test") as client:
        response = await client.post("/api/auth/google", json={"id_token": "verified-by-stub"})

    # No session, but not a bare refusal either: they are in the queue and told so.
    assert response.status_code == 403
    assert "awaiting approval" in response.json()["detail"].lower()
    assert "set-cookie" not in {k.lower() for k in response.headers}

    # Read on a separate connection: a row that only exists inside the request's own
    # transaction is exactly the bug, so the check has to survive that transaction ending.
    await session.commit()
    assert await _accounts_for(session, unknown_email) == 1
    assert await _entries_for(session, unknown_email, "admin.self_registered") == 1

    await _forget(session, unknown_email)


@pytest.mark.usefixtures("_google_accepts")
async def test_signing_in_again_while_pending_creates_no_second_account(
    session: AsyncSession, unknown_email: str
) -> None:
    transport = ASGITransport(app=app)
    async with AsyncClient(transport=transport, base_url="http://test") as client:
        first = await client.post("/api/auth/google", json={"id_token": "verified-by-stub"})
        second = await client.post("/api/auth/google", json={"id_token": "verified-by-stub"})

    assert first.status_code == second.status_code == 403
    assert first.json() == second.json()

    await session.commit()
    assert await _accounts_for(session, unknown_email) == 1

    await _forget(session, unknown_email)


@pytest.mark.usefixtures("_google_accepts")
async def test_the_response_names_nobody_but_the_caller(
    session: AsyncSession, unknown_email: str
) -> None:
    """Self-registration made the allowlist discoverable, so the pending state is stated
    plainly — but the response still says nothing about anyone *else*, and it does not echo
    the address back into a page that might be logged or cached."""
    transport = ASGITransport(app=app)
    async with AsyncClient(transport=transport, base_url="http://test") as client:
        response = await client.post("/api/auth/google", json={"id_token": "verified-by-stub"})

    assert unknown_email not in response.text
    assert "super_admin" not in response.text

    await session.commit()
    await _forget(session, unknown_email)
