"""Service primitives for managing Takumi API keys."""

from __future__ import annotations

from dataclasses import dataclass
from datetime import datetime
from typing import Optional

import asyncpg

from .crypto import IssuedKey, issue_token


@dataclass
class CreateKeyRequest:
    """Payload for provisioning a new API key."""

    org_id: str
    tier: str
    role: str = "user"
    created_by: Optional[str] = None
    expires_at: Optional[str] = None


class ApiKeyService:
    """High-level API for issuing and managing API keys."""

    def __init__(self, pool: asyncpg.Pool, pepper: str, key_prefix: str) -> None:
        self._pool = pool
        self._pepper = pepper
        self._key_prefix = key_prefix

    async def create(self, request: CreateKeyRequest) -> dict[str, object]:
        """Issue a new key, persist it, and return the token payload."""

        if not self._pepper:
            raise ValueError("Pepper must be configured to issue API keys")

        issued: IssuedKey = issue_token(self._pepper, self._key_prefix)
        expires_at = _parse_datetime(request.expires_at)
        async with self._pool.acquire() as conn:
            row = await conn.fetchrow(
                """
                INSERT INTO api_keys(
                    org_id, kid, key_hash, lookup_hmac, preview, status, role, tier,
                    created_by, expires_at, audit
                )
                VALUES($1,$2,$3,$4,$5,'active',$6,$7,$8,$9,'{}'::jsonb)
                RETURNING id, org_id, kid, preview, role, tier, created_at, expires_at
                """,
                request.org_id,
                issued.kid,
                issued.key_hash,
                issued.lookup_hmac,
                issued.preview,
                request.role,
                request.tier,
                request.created_by,
                expires_at,
            )

        payload = _row_to_dict(row) if row else {}
        payload["token"] = issued.token
        return payload

    async def list(self, org_id: str) -> list[dict[str, object]]:
        async with self._pool.acquire() as conn:
            rows = await conn.fetch(
                """
                SELECT id, kid, preview, status, role, tier,
                       created_at, expires_at, last_used_at
                FROM api_keys
                WHERE org_id = $1
                ORDER BY created_at DESC
                """,
                org_id,
            )
        return [_row_to_dict(row) for row in rows]

    async def set_status(self, api_key_id: str, status: str) -> dict[str, object]:
        status = status.lower()
        if status not in {"active", "inactive", "revoked"}:
            raise ValueError("Invalid API key status")
        async with self._pool.acquire() as conn:
            row = await conn.fetchrow(
                "UPDATE api_keys SET status=$2 WHERE id=$1 RETURNING id, status",
                api_key_id,
                status,
            )
        if not row:
            raise ValueError("API key not found")
        return _row_to_dict(row)

    async def rotate(self, api_key_id: str, actor: Optional[str]) -> dict[str, object]:
        if not self._pepper:
            raise ValueError("Pepper must be configured to rotate API keys")

        async with self._pool.acquire() as conn:
            old = await conn.fetchrow(
                "SELECT id, org_id, tier, role FROM api_keys WHERE id=$1",
                api_key_id,
            )
            if not old:
                raise ValueError("API key not found")

            issued: IssuedKey = issue_token(self._pepper, self._key_prefix)
            async with conn.transaction():
                new_row = await conn.fetchrow(
                    """
                    INSERT INTO api_keys(
                        org_id, kid, key_hash, lookup_hmac, preview, status, role, tier,
                        created_by, rotated_from, audit
                    )
                    VALUES($1,$2,$3,$4,$5,'active',$6,$7,$8,$9,'{}'::jsonb)
                    RETURNING id, org_id, kid, preview, role, tier, created_at
                    """,
                    old["org_id"],
                    issued.kid,
                    issued.key_hash,
                    issued.lookup_hmac,
                    issued.preview,
                    old["role"],
                    old["tier"],
                    actor,
                    old["id"],
                )
                await conn.execute(
                    "UPDATE api_keys SET status='inactive', rotated_to=$2 WHERE id=$1",
                    old["id"],
                    new_row["id"],
                )

        payload = _row_to_dict(new_row)
        payload["token"] = issued.token
        return payload


def _parse_datetime(value: Optional[str]) -> Optional[datetime]:
    if value is None:
        return None
    try:
        dt = datetime.fromisoformat(value)
        if dt.tzinfo is None:
            return dt
        return dt.astimezone(tz=None)
    except ValueError as exc:
        raise ValueError("expires_at must be ISO formatted") from exc


def _row_to_dict(row: Optional[asyncpg.Record]) -> dict[str, object]:
    if row is None:
        raise RuntimeError("Expected database row for API key operation, received None")
    payload = dict(row)
    for key in ("created_at", "expires_at", "last_used_at"):
        value = payload.get(key)
        if isinstance(value, datetime):
            payload[key] = value.isoformat()
    return payload
