"""API-token lifecycle handler tests without external services."""

import json
from typing import Any, cast

import pytest
from fastapi import FastAPI, Request

from app.api.errors import ApiError
from app.api.registry import HandlerRegistry
from app.db.pool import AsyncpgDatabase
from app.handlers.access import register_access_handlers


class TokenPool:
    def __init__(self) -> None:
        self.token: dict[str, Any] | None = None

    async def fetch(self, _query: str, _userid: str) -> list[dict[str, Any]]:
        return [] if self.token is None else [{"token_id": "test", **self.token}]

    async def fetchrow(self, query: str, *arguments: object) -> dict[str, Any] | None:
        if "INSERT INTO" in query:
            self.token = {
                "comment": arguments[3],
                "privilege_separation": arguments[5],
                "expire": arguments[4],
            }
            return self.token
        if "UPDATE api_tokens" in query:
            if self.token is None:
                return None
            self.token["comment"] = arguments[2]
            self.token["privilege_separation"] = arguments[4]
            return self.token
        return self.token

    async def fetchval(self, _query: str, _userid: str) -> bool:
        return True

    async def execute(self, _query: str, _userid: str, _tokenid: str) -> str:
        if self.token is None:
            return "DELETE 0"
        self.token = None
        return "DELETE 1"


class RealmPool:
    def __init__(self) -> None:
        self.realms: dict[str, dict[str, Any]] = {
            "pam": {
                "kind": "pam",
                "config": {"comment": "Linux PAM standard authentication"},
            },
            "pve": {
                "kind": "pve",
                "config": {"comment": "Proxmox VE authentication server"},
            },
        }
        self.principals: dict[str, str] = {"root@pam": "pam"}

    async def fetch(self, query: str, *arguments: object) -> list[dict[str, Any]]:
        del arguments
        if "FROM realms ORDER BY name" in query:
            return [
                {"name": name, "kind": data["kind"], "config": dict(data["config"])}
                for name, data in sorted(self.realms.items())
            ]
        raise AssertionError(query)

    async def fetchrow(self, query: str, *arguments: object) -> dict[str, Any] | None:
        if "FROM realms WHERE name" in query:
            realm = str(arguments[0])
            data = self.realms.get(realm)
            if data is None:
                return None
            return {"name": realm, "kind": data["kind"], "config": dict(data["config"])}
        raise AssertionError(query)

    async def fetchval(self, query: str, *arguments: object) -> bool:
        realm = str(arguments[0])
        if "EXISTS(SELECT 1 FROM realms" in query:
            return realm in self.realms
        if "EXISTS(SELECT 1 FROM principals" in query:
            return any(value == realm for value in self.principals.values())
        raise AssertionError(query)

    async def execute(self, query: str, *arguments: object) -> str:
        if "INSERT INTO realms" in query:
            self.realms[str(arguments[0])] = {
                "kind": str(arguments[1]),
                "config": json.loads(str(arguments[2])),
            }
            return "INSERT 0 1"
        if "UPDATE realms SET config=$2" in query:
            realm = str(arguments[0])
            self.realms[realm]["config"] = json.loads(str(arguments[1]))
            return "UPDATE 1"
        if "SET config = config - 'default'" in query:
            skip = str(arguments[0]) if arguments else None
            for name, data in self.realms.items():
                if skip is not None and name == skip:
                    continue
                data["config"].pop("default", None)
            return "UPDATE 0"
        if "DELETE FROM realms" in query:
            realm = str(arguments[0])
            if realm not in self.realms:
                return "DELETE 0"
            del self.realms[realm]
            return "DELETE 1"
        raise AssertionError(query)


class FakeDatabase:
    def __init__(self, pool: TokenPool | RealmPool) -> None:
        self.pool = pool


def request(pool: TokenPool | RealmPool, principal: str = "root@pam") -> Request:
    app = FastAPI()
    app.state.database = cast(AsyncpgDatabase, FakeDatabase(pool))
    result = Request(
        {
            "type": "http",
            "app": app,
            "method": "POST",
            "path": "/",
            "headers": [],
            "query_string": b"",
            "server": ("test", 80),
            "client": ("test", 123),
            "scheme": "http",
        }
    )
    result.state.principal = principal
    return result


def values(**items: object) -> dict[str, Any]:
    return {"values": items, "provided": frozenset(items)}


async def test_token_lifecycle_returns_secret_once_and_persists_metadata() -> None:
    registry = HandlerRegistry()
    register_access_handlers(registry)
    pool = TokenPool()
    http_request = request(pool)
    create = registry.get("/access/users/{userid}/token/{tokenid}", "POST")
    get = registry.get("/access/users/{userid}/token/{tokenid}", "GET")
    update = registry.get("/access/users/{userid}/token/{tokenid}", "PUT")
    delete = registry.get("/access/users/{userid}/token/{tokenid}", "DELETE")
    list_tokens = registry.get("/access/users/{userid}/token", "GET")
    assert create and get and update and delete and list_tokens

    created = await create(
        http_request,
        values(userid="root@pam", tokenid="test", comment="first", privsep=True),
    )
    assert created["full-tokenid"] == "root@pam!test"
    assert created["value"]
    assert "value" not in await get(http_request, values(userid="root@pam", tokenid="test"))
    assert await list_tokens(http_request, values(userid="root@pam"))

    updated = await update(
        http_request,
        values(userid="root@pam", tokenid="test", comment="second", privsep=False),
    )
    assert updated["comment"] == "second"
    await delete(http_request, values(userid="root@pam", tokenid="test"))
    with pytest.raises(ApiError) as missing:
        await get(http_request, values(userid="root@pam", tokenid="test"))
    assert missing.value.status_code == 404


async def test_token_lifecycle_rejects_non_owner() -> None:
    registry = HandlerRegistry()
    register_access_handlers(registry)
    handler = registry.get("/access/users/{userid}/token", "GET")
    assert handler
    with pytest.raises(ApiError) as denied:
        await handler(request(TokenPool(), "auditor@pve"), values(userid="other@pve"))
    assert denied.value.status_code == 403


async def test_domain_lifecycle_persists_realm_config() -> None:
    registry = HandlerRegistry()
    register_access_handlers(registry)
    pool = RealmPool()
    http_request = request(pool)
    create = registry.get("/access/domains", "POST")
    listing = registry.get("/access/domains", "GET")
    get = registry.get("/access/domains/{realm}", "GET")
    update = registry.get("/access/domains/{realm}", "PUT")
    delete = registry.get("/access/domains/{realm}", "DELETE")
    assert create and listing and get and update and delete

    await create(
        http_request,
        values(
            realm="corp",
            type="ldap",
            comment="Corporate LDAP",
            server1="ldap.example.com",
            password="secret",
            default=1,
        ),
    )
    listed = await listing(http_request, values())
    assert any(item["realm"] == "corp" and item["type"] == "ldap" for item in listed)
    created = await get(http_request, values(realm="corp"))
    assert created["comment"] == "Corporate LDAP"
    assert created["server1"] == "ldap.example.com"
    assert created["default"] == 1
    assert "password" not in created

    await update(
        http_request,
        values(realm="corp", comment="Updated LDAP", delete="default"),
    )
    updated = await get(http_request, values(realm="corp"))
    assert updated["comment"] == "Updated LDAP"
    assert "default" not in updated

    await delete(http_request, values(realm="corp"))
    with pytest.raises(ApiError) as missing:
        await get(http_request, values(realm="corp"))
    assert missing.value.status_code == 404


async def test_domain_delete_rejects_builtin_and_in_use_realms() -> None:
    registry = HandlerRegistry()
    register_access_handlers(registry)
    pool = RealmPool()
    http_request = request(pool)
    delete = registry.get("/access/domains/{realm}", "DELETE")
    assert delete

    with pytest.raises(ApiError) as builtin:
        await delete(http_request, values(realm="pam"))
    assert builtin.value.status_code == 400

    pool.realms["corp"] = {"kind": "ldap", "config": {}}
    pool.principals["alice@corp"] = "corp"
    with pytest.raises(ApiError) as in_use:
        await delete(http_request, values(realm="corp"))
    assert in_use.value.status_code == 400
