"""API Key -> JWT token exchange with caching."""

from __future__ import annotations

import asyncio
import hashlib
import threading
import time
from typing import Any

import httpx

from melogenai.config import Config
from melogenai.exceptions import AuthenticationError

_MAX_CACHE_SIZE = 100
_TOKEN_CACHE_SECONDS = 3500  # ~58 min (JWT is 60 min)


class AuthManager:
    """Manages API Key -> JWT token exchange with thread-safe caching."""

    def __init__(self, config: Config):
        self._config = config
        self._cache: dict[str, dict[str, Any]] = {}
        self._sync_lock = threading.Lock()
        self._async_lock: asyncio.Lock | None = None

    @property
    def _alock(self) -> asyncio.Lock:
        if self._async_lock is None:
            self._async_lock = asyncio.Lock()
        return self._async_lock

    @staticmethod
    def _cache_key(api_key: str) -> str:
        return hashlib.sha256(api_key.encode()).hexdigest()

    def _evict_expired(self) -> None:
        now = time.time()
        expired = [k for k, v in self._cache.items() if v["expires_at"] <= now]
        for k in expired:
            del self._cache[k]

    def _get_cached(self, api_key: str) -> dict[str, Any] | None:
        key = self._cache_key(api_key)
        cached = self._cache.get(key)
        if cached and cached["expires_at"] > time.time():
            return cached
        return None

    def _store_token(self, api_key: str, data: dict[str, Any]) -> dict[str, Any]:
        key = self._cache_key(api_key)
        token_data = {
            "token": data["access_token"],
            "user_id": data["user_id"],
            "scopes": data.get("scopes"),
            "expires_at": time.time() + _TOKEN_CACHE_SECONDS,
        }
        self._evict_expired()
        if len(self._cache) >= _MAX_CACHE_SIZE:
            oldest_key = min(self._cache, key=lambda k: self._cache[k]["expires_at"])
            del self._cache[oldest_key]
        self._cache[key] = token_data
        return token_data

    def _parse_response(self, data: dict[str, Any]) -> dict[str, Any]:
        if not data.get("valid"):
            raise AuthenticationError("Invalid API key")
        return data

    def get_token(self, api_key: str) -> dict[str, Any]:
        """Exchange API key for a JWT token (sync)."""
        with self._sync_lock:
            cached = self._get_cached(api_key)
            if cached:
                return cached

            with httpx.Client(timeout=10) as client:
                resp = client.post(
                    f"{self._config.base_url}/api/api-keys/verify",
                    headers={"X-API-Key": api_key},
                )
                resp.raise_for_status()
                data = self._parse_response(resp.json())

            return self._store_token(api_key, data)

    async def async_get_token(self, api_key: str) -> dict[str, Any]:
        """Exchange API key for a JWT token (async)."""
        async with self._alock:
            cached = self._get_cached(api_key)
            if cached:
                return cached

            async with httpx.AsyncClient(timeout=10) as client:
                resp = await client.post(
                    f"{self._config.base_url}/api/api-keys/verify",
                    headers={"X-API-Key": api_key},
                )
                resp.raise_for_status()
                data = self._parse_response(resp.json())

            return self._store_token(api_key, data)

    def invalidate(self, api_key: str) -> None:
        """Remove cached token for an API key."""
        key = self._cache_key(api_key)
        self._cache.pop(key, None)
