"""Opening Meetings: POST /v1/session swaps the product token for ours; GET /v1/me
shows the gate every other endpoint goes through."""

import pytest

from tests.conftest import make_app, running
from tests.fakes import MISSING_ROLE
from tests.tokens import MISSING, meeting_token

NOT_SIGNED_IN = {"error": {"code": "not_signed_in", "message": "Please sign in again."}}
NO_ACCESS = {"error": {"code": "no_access", "message": "You don't have access to Meetings"}}
UNAVAILABLE = {
    "error": {"code": "service_unavailable", "message": "Meetings are temporarily unavailable"}
}


def _bearer(token: str) -> dict[str, str]:
    return {"Authorization": f"Bearer {token}"}


async def _session(client, **claims) -> str:
    response = await client.post("/v1/session", headers=_bearer(meeting_token(**claims)))
    assert response.status_code == 200, response.text
    return response.json()["access_token"]


# --- POST /v1/session -------------------------------------------------------


async def test_session_is_issued_for_a_signed_in_user(client):
    response = await client.post("/v1/session", headers=_bearer(meeting_token()))

    assert response.status_code == 200
    body = response.json()
    assert body["token_type"] == "Bearer"
    assert body["expires_in"] == 900
    assert body["access_token"]


async def test_opening_meetings_always_asks_the_product_fresh(client, fake_helixtahr):
    await _session(client)
    await _session(client)

    assert fake_helixtahr.allowed_calls == 2


@pytest.mark.parametrize(
    "headers",
    [
        {},
        {"Authorization": "Bearer "},
        {"Authorization": "Basic dXNlcjpwYXNz"},
        {"Authorization": "not-a-token"},
    ],
)
async def test_session_needs_a_product_token(client, headers):
    response = await client.post("/v1/session", headers=headers)

    assert response.status_code == 401
    assert response.json() == NOT_SIGNED_IN


@pytest.mark.parametrize(
    "token",
    [
        "garbage",
        meeting_token(secret="forged-" + "0123456789abcdef" * 3),
        meeting_token(exp=1),
        meeting_token(token_type=MISSING),
    ],
    ids=["garbage", "forged", "expired", "helix-ai-token"],
)
async def test_untrusted_product_token_gets_no_session(client, fake_helixtahr, token):
    response = await client.post("/v1/session", headers=_bearer(token))

    assert response.status_code == 401
    assert response.json() == NOT_SIGNED_IN
    # Refused before ever asking the product.
    assert fake_helixtahr.requests == []


async def test_token_revoked_at_the_product_gets_no_session(client, fake_helixtahr):
    fake_helixtahr.status_code = 401

    response = await client.post("/v1/session", headers=_bearer(meeting_token()))

    assert response.status_code == 401
    assert response.json() == NOT_SIGNED_IN


async def test_company_without_meetings_gets_no_session(client, fake_helixtahr):
    fake_helixtahr.meetings_allowed = False

    response = await client.post("/v1/session", headers=_bearer(meeting_token()))

    assert response.status_code == 403
    assert response.json() == NO_ACCESS


async def test_product_down_means_unavailable_not_let_in(client, fake_helixtahr):
    fake_helixtahr.unreachable = True

    response = await client.post("/v1/session", headers=_bearer(meeting_token()))

    assert response.status_code == 503
    assert response.json() == UNAVAILABLE


# --- the gate, through GET /v1/me --------------------------------------------


async def test_me_returns_the_signed_in_user(client):
    response = await client.get("/v1/me", headers=_bearer(await _session(client)))

    assert response.status_code == 200
    assert response.json() == {
        "user_id": "674",
        "name": "Test User",
        "org_id": "26",
        "is_org_admin": False,
    }


async def test_me_marks_org_admins(client, fake_helixtahr):
    fake_helixtahr.role_name = "superadmin"
    token = await _session(client, role_name="superadmin")

    response = await client.get("/v1/me", headers=_bearer(token))

    assert response.json()["is_org_admin"] is True


@pytest.mark.parametrize(
    ("token_role", "current_role", "is_org_admin"),
    [("admin", "manager", False), ("manager", "admin", True)],
    ids=["no-longer-admin", "newly-admin"],
)
async def test_session_uses_the_current_role_not_the_tokens(
    client, fake_helixtahr, token_role, current_role, is_org_admin
):
    # The token's role is fixed for 30 days; HelixtaHR's answer is today's.
    fake_helixtahr.role_name = current_role
    token = await _session(client, role_name=token_role)

    response = await client.get("/v1/me", headers=_bearer(token))

    assert response.json()["is_org_admin"] is is_org_admin


async def test_without_a_current_role_the_tokens_role_is_used(client, fake_helixtahr):
    fake_helixtahr.role_name = MISSING_ROLE
    token = await _session(client, role_name="admin")

    response = await client.get("/v1/me", headers=_bearer(token))

    assert response.json()["is_org_admin"] is True


async def test_product_token_is_not_accepted_in_place_of_a_session(client):
    response = await client.get("/v1/me", headers=_bearer(meeting_token()))

    assert response.status_code == 401
    assert response.json() == NOT_SIGNED_IN


@pytest.mark.parametrize("headers", [{}, {"Authorization": "Bearer "}, {"Authorization": "x"}])
async def test_me_without_a_session_is_not_signed_in(client, headers):
    response = await client.get("/v1/me", headers=headers)

    assert response.status_code == 401
    assert response.json() == NOT_SIGNED_IN


async def test_session_in_url_is_ignored(client):
    token = await _session(client)

    response = await client.get(f"/v1/me?access_token={token}")

    assert response.status_code == 401


async def test_session_from_before_a_restart_must_be_renewed(fake_helixtahr):
    async with _running(fake_helixtahr) as first:
        token = await _session(first)
    async with _running(fake_helixtahr) as restarted:
        response = await restarted.get("/v1/me", headers=_bearer(token))

    assert response.status_code == 401


async def test_company_switched_off_during_a_session_loses_access(client, fake_helixtahr):
    token = await _session(client)
    fake_helixtahr.meetings_allowed = False

    # While the 60 s answer is remembered, the session still works...
    assert (await client.get("/v1/me", headers=_bearer(token))).status_code == 200
    # ...once a fresh check hears "no" (here: someone opening Meetings), every call is refused.
    await client.post("/v1/session", headers=_bearer(meeting_token()))
    response = await client.get("/v1/me", headers=_bearer(token))

    assert response.status_code == 403
    assert response.json() == NO_ACCESS


def _running(fake_helixtahr):
    return running(make_app(helixtahr=fake_helixtahr))


# --- every endpoint is behind the gate ----------------------------------------


def test_every_endpoint_goes_through_the_gate():
    # The schemes show up as `security` on each operation they guard.
    open_paths = {("GET", "/health")}
    takes_product_token = {("POST", "/v1/session")}
    operations = {
        (method.upper(), path): operation.get("security", [])
        for path, item in make_app().openapi()["paths"].items()
        for method, operation in item.items()
    }

    assert operations[("POST", "/v1/session")] == [{"ProductToken": []}]
    ungated = [
        f"{method} {path}"
        for (method, path), security in operations.items()
        if (method, path) not in open_paths | takes_product_token
        and security != [{"SessionToken": []}]
    ]
    assert ("GET", "/v1/me") in operations
    assert ungated == []
