# src/chatiss_open_sdk/core/async_token.py
from __future__ import annotations

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

import httpx

from .common import build_auth_payload, extract_request_id, is_expiring_soon, parse_token_result
from ..config import SDKConfig
from ..exceptions import APIError, NetworkError
from ..types import TokenResult


@dataclass
class _CachedToken:
    token: str
    expires_at: datetime


class AsyncTokenProvider:
    def __init__(self, config: SDKConfig, http: httpx.AsyncClient) -> None:
        self._config = config
        self._http = http
        self._lock = asyncio.Lock()
        self._cached: Optional[_CachedToken] = None

    async def get_token(self) -> str:
        cached = self._cached
        if cached and not self._is_expiring_soon(cached.expires_at):
            return cached.token

        async with self._lock:
            cached = self._cached
            if cached and not self._is_expiring_soon(cached.expires_at):
                return cached.token

            token_result = await self._fetch_token()
            self._cached = _CachedToken(token_result.token, token_result.expiration)
            return token_result.token

    def _is_expiring_soon(self, expires_at: datetime) -> bool:
        return is_expiring_soon(expires_at, self._config.refresh_skew_seconds)

    async def _fetch_token(self) -> TokenResult:
        try:
            resp = await self._http.post(
                "/auth/token/",
                json=build_auth_payload(
                    access_key_id=self._config.access_key_id,
                    access_key_secret=self._config.access_key_secret,
                ),
                headers={"Accept": "application/json"},
            )
        except httpx.RequestError as e:
            raise NetworkError(f"request failed: {e}") from e

        request_id = extract_request_id(resp)

        if resp.status_code >= 400:
            raise APIError(resp.status_code, resp.text, request_id=request_id, body=resp.text)

        return parse_token_result(resp.json())
