"""
Authentication Manager

Handles API Key → JWT exchange with automatic silent refresh.
Users never need to manage tokens directly.

Security: Uses RS256 JWTs with short TTL (15 min).
Tokens are refreshed 60 seconds before expiry.
"""

from __future__ import annotations

import asyncio
import base64
import json
import time
from typing import Any
from urllib.parse import urlencode

import aiohttp

from dvgateway.types import ApiKeyAuth, Auth, JwtAuth, Logger


def _parse_jwt_payload(token: str) -> dict[str, Any]:
    """Parse JWT payload without signature verification."""
    parts = token.split(".")
    if len(parts) != 3:
        raise ValueError("Invalid JWT format")
    payload = parts[1]
    # Base64URL decode
    padded = payload.replace("-", "+").replace("_", "/")
    # Add padding
    padded += "=" * (-len(padded) % 4)
    decoded = base64.b64decode(padded).decode("utf-8")
    return json.loads(decoded)  # type: ignore[no-any-return]


class AuthManager:
    """Manages authentication credentials with automatic token refresh."""

    _REFRESH_BUFFER_MS = 60_000

    def __init__(self, auth: Auth, base_url: str, logger: Logger) -> None:
        self._auth = auth
        self._base_url = base_url
        self._logger = logger

        self._cached_token: str | None = None
        self._token_expires_at: float = 0.0
        self._refresh_lock = asyncio.Lock()

        # Pre-load JWT if provided directly
        if isinstance(auth, JwtAuth):
            self._cached_token = auth.token
            try:
                payload = _parse_jwt_payload(auth.token)
                self._token_expires_at = payload["exp"] * 1000
            except Exception:
                self._token_expires_at = 0.0

    async def get_token(self) -> str:
        """Returns a valid bearer token, refreshing if necessary."""
        now = time.time() * 1000
        refresh_threshold = self._token_expires_at - self._REFRESH_BUFFER_MS

        if self._cached_token and now < refresh_threshold:
            return self._cached_token

        async with self._refresh_lock:
            # Double-check after acquiring lock
            now = time.time() * 1000
            refresh_threshold = self._token_expires_at - self._REFRESH_BUFFER_MS
            if self._cached_token and now < refresh_threshold:
                return self._cached_token
            return await self._fetch_token()

    async def get_bearer_header(self) -> str:
        """Returns a bearer token header value."""
        return f"Bearer {await self.get_token()}"

    async def build_ws_url(self, path: str, params: dict[str, str] | None = None) -> str:
        """Build authenticated WebSocket URL (token embedded as query param)."""
        token = await self.get_token()
        base = self._base_url.rstrip("/")
        if base.startswith("https://"):
            base = "wss://" + base[8:]
        elif base.startswith("http://"):
            base = "ws://" + base[7:]

        all_params = {**(params or {}), "token": token}
        return f"{base}{path}?{urlencode(all_params)}"

    def build_http_url(self, path: str, params: dict[str, str] | None = None) -> str:
        """Build authenticated HTTP URL."""
        base = self._base_url.rstrip("/")
        if not params:
            return f"{base}{path}"
        return f"{base}{path}?{urlencode(params)}"

    async def _fetch_token(self) -> str:
        if isinstance(self._auth, JwtAuth):
            payload = _parse_jwt_payload(self._auth.token)
            if payload["exp"] * 1000 < time.time() * 1000:
                raise RuntimeError(
                    "DVGateway: provided JWT has expired. "
                    "Use apiKey auth for automatic token refresh."
                )
            self._cached_token = self._auth.token
            self._token_expires_at = payload["exp"] * 1000
            return self._cached_token

        # API Key → JWT exchange
        assert isinstance(self._auth, ApiKeyAuth)
        url = f"{self._base_url}/api/v1/auth/token"
        self._logger.debug({"url": url}, "Fetching new JWT from gateway")

        async with aiohttp.ClientSession() as session:
            async with session.post(
                url,
                json={"apiKey": self._auth.api_key},
                headers={
                    "Content-Type": "application/json",
                    "X-API-Key": self._auth.api_key,
                },
            ) as resp:
                if resp.status != 200:
                    body = await resp.text()
                    raise RuntimeError(f"DVGateway auth failed (HTTP {resp.status}): {body}")

                data = await resp.json()
                token: str = data["token"]

        payload = _parse_jwt_payload(token)
        self._cached_token = token
        self._token_expires_at = payload["exp"] * 1000

        self._logger.debug(
            {"expires_at": self._token_expires_at},
            "JWT refreshed successfully",
        )

        return token
