"""
Synchronous TokenManager — handles OAuth 2.0 token acquisition, caching, and refresh.
Tokens are re-issued automatically when expired or within 60 seconds of expiry.
"""

from __future__ import annotations

import time
import threading
from dataclasses import dataclass
from typing import Optional

import requests

from .errors import AgentIdPError
from .types import TokenResponse

#: Seconds before expiry at which a token refresh is triggered.
REFRESH_BUFFER_SECONDS = 60


@dataclass
class _CachedToken:
    access_token: str
    expires_at: float  # Unix timestamp (seconds)


class TokenManager:
    """
    Thread-safe synchronous token manager.

    Acquires and caches OAuth 2.0 access tokens. Automatically refreshes
    the token when it is within :data:`REFRESH_BUFFER_SECONDS` of expiry.

    Args:
        base_url: AgentIdP server base URL (e.g. ``http://localhost:3000``).
        client_id: The agent's ``agentId`` (UUID).
        client_secret: The agent's credential secret.
        scopes: Space-separated OAuth 2.0 scopes to request.
    """

    def __init__(
        self,
        base_url: str,
        client_id: str,
        client_secret: str,
        scopes: str,
    ) -> None:
        self._base_url = base_url.rstrip("/")
        self._client_id = client_id
        self._client_secret = client_secret
        self._scopes = scopes
        self._cached: Optional[_CachedToken] = None
        self._lock = threading.Lock()

    def get_token(self) -> str:
        """
        Return a valid access token, refreshing if necessary.

        Returns:
            A valid JWT access token string.

        Raises:
            AgentIdPError: If token acquisition fails.
        """
        with self._lock:
            now = time.time()
            if (
                self._cached is not None
                and self._cached.expires_at - now > REFRESH_BUFFER_SECONDS
            ):
                return self._cached.access_token

            token_response = self._issue_token()
            self._cached = _CachedToken(
                access_token=token_response.access_token,
                expires_at=now + token_response.expires_in,
            )
            return self._cached.access_token

    def clear_cache(self) -> None:
        """Clear the cached token, forcing re-acquisition on the next call."""
        with self._lock:
            self._cached = None

    def _issue_token(self) -> TokenResponse:
        """
        POST /api/v1/token to obtain a new access token.

        Returns:
            TokenResponse from the API.

        Raises:
            AgentIdPError: On authentication failure or network error.
        """
        url = f"{self._base_url}/api/v1/token"
        data = {
            "grant_type": "client_credentials",
            "client_id": self._client_id,
            "client_secret": self._client_secret,
            "scope": self._scopes,
        }
        try:
            response = requests.post(
                url,
                data=data,
                headers={"Content-Type": "application/x-www-form-urlencoded"},
                timeout=30,
            )
        except requests.RequestException as exc:
            raise AgentIdPError.network_error(exc) from exc

        body = response.json()
        if not response.ok:
            raise AgentIdPError.from_oauth2_error(body, response.status_code)
        return TokenResponse.from_dict(body)
