"""Tests für das öffentliche QR-Stempeln (Personalnummer + PIN, statischer QR).

Benötigt Redis (PIN-Lockout + Kurz-Session) – läuft auf dem Server.
"""
import pytest
import pytest_asyncio
from httpx import AsyncClient


async def _register(client: AsyncClient, company: str, email: str, pin: str | None,
                    personnel: str = "0001", enable: bool = True):
    """Registriert Firma+Admin, setzt Personalnr/PIN, aktiviert QR-Stempeln,
    rotiert das Token. Gibt dict mit headers/token/personnel/pin zurück."""
    reg = await client.post("/api/v1/auth/register", json={
        "company_name": company,
        "first_name": "QR",
        "last_name": "Admin",
        "email": email,
        "password": "Secret123",
    })
    assert reg.status_code == 201, reg.text
    headers = {"Authorization": f"Bearer {reg.json()['access_token']}"}

    me = await client.get("/api/v1/auth/me", headers=headers)
    user_id = me.json()["id"]

    pr = await client.patch(f"/api/v1/users/{user_id}",
                            json={"personnel_number": personnel}, headers=headers)
    assert pr.status_code == 200, pr.text

    if pin is not None:
        pr = await client.post(f"/api/v1/users/{user_id}/kiosk-pin",
                               json={"pin": pin}, headers=headers)
        assert pr.status_code == 200, pr.text

    upd = await client.patch("/api/v1/companies/me",
                             json={"public_stamp_enabled": enable}, headers=headers)
    assert upd.status_code == 200, upd.text

    rot = await client.post("/api/v1/companies/me/public-stamp-token/rotate", headers=headers)
    assert rot.status_code == 200, rot.text
    return {"headers": headers, "user_id": user_id,
            "token": rot.json()["token"], "personnel": personnel, "pin": pin}


@pytest_asyncio.fixture(scope="session", loop_scope="session")
async def ps(client: AsyncClient):
    return await _register(client, "QR Stamp GmbH", "qr@stamp.de", "1234")


# ── Token-Auflösung ──────────────────────────────────────────────────────────

@pytest.mark.asyncio
async def test_company_info_resolves(client: AsyncClient, ps):
    resp = await client.get(f"/api/v1/time/public/company?t={ps['token']}")
    assert resp.status_code == 200
    body = resp.json()
    assert body["company_name"] == "QR Stamp GmbH"
    assert body["enabled"] is True


@pytest.mark.asyncio
async def test_company_info_unknown_token_404(client: AsyncClient):
    resp = await client.get("/api/v1/time/public/company?t=totally-invalid-token-xyz")
    assert resp.status_code == 404


@pytest.mark.asyncio
async def test_rotate_returns_url_once(client: AsyncClient, ps):
    resp = await client.post("/api/v1/companies/me/public-stamp-token/rotate", headers=ps["headers"])
    assert resp.status_code == 200
    body = resp.json()
    assert "/stamp?t=" in body["public_url"]
    assert len(body["token"]) >= 32
    # neues Token → ps['token'] (altes) ungültig
    old = await client.get(f"/api/v1/time/public/company?t={ps['token']}")
    assert old.status_code == 404
    # ps-Fixture aktualisieren, damit Folge-Tests das gültige Token nutzen
    ps["token"] = body["token"]


# ── Auth ─────────────────────────────────────────────────────────────────────

@pytest.mark.asyncio
async def test_auth_success(client: AsyncClient, ps):
    resp = await client.post("/api/v1/time/public/auth", json={
        "token": ps["token"], "personnel_number": ps["personnel"], "pin": ps["pin"],
    })
    assert resp.status_code == 200, resp.text
    body = resp.json()
    assert body["session_token"]
    assert body["expires_in_seconds"] > 0
    assert body["open"] is False


@pytest.mark.asyncio
async def test_auth_wrong_pin_401(client: AsyncClient, ps):
    resp = await client.post("/api/v1/time/public/auth", json={
        "token": ps["token"], "personnel_number": ps["personnel"], "pin": "9999",
    })
    assert resp.status_code == 401


@pytest.mark.asyncio
async def test_auth_disabled_company_403(client: AsyncClient):
    setup = await _register(client, "QR Disabled GmbH", "qr@disabled.de", "1234",
                            personnel="0002", enable=False)
    resp = await client.post("/api/v1/time/public/auth", json={
        "token": setup["token"], "personnel_number": "0002", "pin": "1234",
    })
    assert resp.status_code == 403


@pytest.mark.asyncio
async def test_auth_lockout_after_failures(client: AsyncClient):
    """5 Fehlversuche (auch bei unbekannter Personalnr.) → 429 Lockout."""
    setup = await _register(client, "QR Lockout GmbH", "qr@lockout.de", "1234",
                            personnel="0003")
    codes = []
    for _ in range(6):
        r = await client.post("/api/v1/time/public/auth", json={
            "token": setup["token"], "personnel_number": "999999", "pin": "0000",
        })
        codes.append(r.status_code)
    assert 429 in codes  # Sperre greift


# ── Stempel-Aktionen ─────────────────────────────────────────────────────────

@pytest.mark.asyncio
async def test_stamp_in_and_out(client: AsyncClient, ps):
    auth = await client.post("/api/v1/time/public/auth", json={
        "token": ps["token"], "personnel_number": ps["personnel"], "pin": ps["pin"],
    })
    assert auth.status_code == 200, auth.text
    session = auth.json()["session_token"]
    already_open = auth.json()["open"]

    if not already_open:
        sin = await client.post("/api/v1/time/public/action",
                                json={"session_token": session, "action": "in"})
        assert sin.status_code == 200, sin.text
        assert sin.json()["open"] is True

    sout = await client.post("/api/v1/time/public/action",
                             json={"session_token": session, "action": "out"})
    assert sout.status_code == 200, sout.text
    assert sout.json()["open"] is False


@pytest.mark.asyncio
async def test_action_invalid_session_401(client: AsyncClient):
    resp = await client.post("/api/v1/time/public/action", json={
        "session_token": "00000000-0000-0000-0000-000000000000", "action": "in",
    })
    assert resp.status_code == 401


# ── Token-Delete ─────────────────────────────────────────────────────────────

@pytest.mark.asyncio
async def test_delete_token_invalidates(client: AsyncClient):
    setup = await _register(client, "QR Delete GmbH", "qr@delete.de", "1234",
                            personnel="0004")
    dl = await client.delete("/api/v1/companies/me/public-stamp-token", headers=setup["headers"])
    assert dl.status_code == 204
    resp = await client.get(f"/api/v1/time/public/company?t={setup['token']}")
    assert resp.status_code == 404
