"""Token provider for fetching Meta API tokens."""

from __future__ import annotations

import asyncio
from dataclasses import dataclass
import time
from typing import Optional
from urllib.parse import quote

try:
    import httpx
    HAS_HTTPX = True
except ImportError:
    HAS_HTTPX = False

from ..errors import AuthenticationError, ConfigurationError
from ..logging import get_logger

logger = get_logger(__name__)


@dataclass
class _CachedToken:
    access_token: str
    expires_at: Optional[float] = None


class TokenProvider:
    """Provides Meta API access tokens based on deployment mode.
    
    In local mode: Returns the configured direct access token
    In hosted mode: Fetches user's token from auth broker
    """
    
    def __init__(
        self,
        local_token: Optional[str] = None,
        auth_broker_url: Optional[str] = None,
        admin_api_key: Optional[str] = None,
        timeout_seconds: float = 10.0,
        hosted_mode: bool = False,
        cache_grace_seconds: int = 60,
    ):
        """Initialize token provider.
        
        Args:
            local_token: Direct Meta API token (for local mode)
            auth_broker_url: Auth broker URL (for hosted mode)
            hosted_mode: Whether running in hosted mode
            
        Raises:
            ConfigurationError: If required configuration is missing
        """
        self.local_token = local_token
        self.auth_broker_url = auth_broker_url
        self.admin_api_key = admin_api_key
        self.timeout_seconds = timeout_seconds
        self.hosted_mode = hosted_mode
        self.cache_grace_seconds = cache_grace_seconds
        self._http_client: httpx.AsyncClient | None = None
        self._cache: dict[str, _CachedToken] = {}
        self._cache_lock = asyncio.Lock()
        
        # Validate configuration
        if not hosted_mode and not local_token:
            raise ConfigurationError(
                "Local token required when not in hosted mode",
                setting="META_ACCESS_TOKEN"
            )
        
        if hosted_mode:
            if not auth_broker_url:
                raise ConfigurationError(
                    "Auth broker URL required in hosted mode",
                    setting="AUTH_BROKER_URL"
                )
            if not admin_api_key:
                raise ConfigurationError(
                    "Auth broker API key required in hosted mode",
                    setting="AUTH_BROKER_API_KEY",
                )
            if not HAS_HTTPX:
                raise ConfigurationError(
                    "httpx required for hosted mode. Install with: pip install httpx",
                    setting="HOSTED_MODE"
                )

    def _is_cache_valid(self, cached: _CachedToken) -> bool:
        if cached.expires_at is None:
            return True
        return time.time() < (cached.expires_at - self.cache_grace_seconds)

    @staticmethod
    def _parse_expires_at(raw_expires_at: object) -> Optional[float]:
        if raw_expires_at is None:
            return None
        try:
            return float(raw_expires_at)
        except (TypeError, ValueError):
            return None

    async def _fetch_token_from_broker(self, user_id: str) -> _CachedToken:
        if self._http_client is None:
            self._http_client = httpx.AsyncClient(timeout=self.timeout_seconds)

        logger.debug("fetching_token_from_broker user_id=%s", user_id)
        encoded_user_id = quote(user_id, safe="")
        url = f"{self.auth_broker_url}/tokens/meta/{encoded_user_id}"

        response = await self._http_client.get(
            url,
            headers={"X-API-Key": self.admin_api_key},
        )

        if response.status_code == 404:
            raise AuthenticationError(
                f"No token found for user: {user_id}",
                reason="user_not_found",
                user_id=user_id
            )

        if response.status_code != 200:
            raise AuthenticationError(
                f"Failed to fetch token: HTTP {response.status_code}",
                reason="broker_error",
                status_code=response.status_code
            )

        data = response.json()
        token = data.get("access_token")

        if not token:
            raise AuthenticationError(
                "Auth broker returned invalid response",
                reason="invalid_response"
            )

        return _CachedToken(
            access_token=token,
            expires_at=self._parse_expires_at(data.get("expires_at")),
        )

    async def aclose(self) -> None:
        """Close any long-lived resources."""
        if self._http_client is not None:
            await self._http_client.aclose()
            self._http_client = None
    
    async def get_token(self, user_id: Optional[str] = None) -> str:
        """Get Meta API access token.
        
        Args:
            user_id: User identifier (required in hosted mode)
            
        Returns:
            Meta API access token
            
        Raises:
            AuthenticationError: If token cannot be retrieved
            ConfigurationError: If configuration is invalid for the mode
        """
        if not self.hosted_mode:
            # Local mode: return direct token
            logger.debug("using_local_token")
            if not self.local_token:
                raise ConfigurationError(
                    "Local token not configured",
                    setting="META_ACCESS_TOKEN"
                )
            return self.local_token
        
        # Hosted mode: fetch from auth broker
        if not user_id:
            raise AuthenticationError(
                "User ID required in hosted mode",
                reason="missing_user_id"
            )
        
        try:
            cached = self._cache.get(user_id)
            if cached and self._is_cache_valid(cached):
                logger.debug("using_cached_broker_token user_id=%s", user_id)
                return cached.access_token

            async with self._cache_lock:
                cached = self._cache.get(user_id)
                if cached and self._is_cache_valid(cached):
                    logger.debug("using_cached_broker_token user_id=%s", user_id)
                    return cached.access_token

                fetched = await self._fetch_token_from_broker(user_id)
                self._cache[user_id] = fetched
                logger.info("token_fetched_from_broker user_id=%s", user_id)
                return fetched.access_token
                
        except httpx.RequestError as exc:
            logger.error("auth_broker_request_failed user_id=%s error=%s", user_id, exc)
            raise AuthenticationError(
                f"Failed to connect to auth broker: {exc}",
                reason="connection_error"
            ) from exc
        except AuthenticationError:
            raise
        
        except Exception as exc:
            logger.error("token_fetch_error user_id=%s type=%s error=%s", user_id, type(exc).__name__, exc)
            raise AuthenticationError(
                f"Failed to fetch token: {exc}",
                reason="fetch_error"
            ) from exc

