"""
Internal HTTP client and authentication middleware.
Based on PHP library's AuthenticationPlugin and HTTP architecture.
"""

import asyncio
from abc import ABC, abstractmethod
from http import HTTPStatus
from typing import Any

import httpx

from .exceptions import raise_for_status


class AuthProvider(ABC):
    """Abstract interface for authentication provider."""

    @abstractmethod
    async def get_token(self) -> dict[str, Any] | None:
        """Get current access token data."""

    @abstractmethod
    async def refresh(self, refresh_token: str) -> dict[str, Any] | None:
        """Refresh access token using refresh token."""


class AsyncHTTPClient:
    """
    Async HTTP client with automatic token refresh on 401 responses.

    Based on PHP's AuthenticationPlugin behavior:
    - Adds Bearer authorization header
    - On 401 response, attempts token refresh once
    - Retries request with new token (max 2 attempts)
    """

    def __init__(
        self,
        base_url: str,
        auth_provider: AuthProvider,
        default_headers: dict[str, str] | None = None,
        timeout: float = 10.0,
    ):
        self.base_url = base_url
        self.auth_provider = auth_provider
        self.default_headers = default_headers or {}
        self.timeout = timeout
        self._refresh_lock = asyncio.Lock()
        self.max_retries = 2  # Same as PHP AuthenticationPlugin::RETRY_LIMIT

    async def _get_auth_headers(self) -> dict[str, str]:
        """Get authorization headers from current token."""
        token_data = await self.auth_provider.get_token()
        if not token_data or 'token' not in token_data:
            return {}

        return {'Authorization': f'Bearer {token_data["token"]}'}

    async def _handle_401_response(self, client: httpx.AsyncClient, request: httpx.Request) -> httpx.Response | None:
        """
        Handle 401 response by refreshing token and retrying request.

        Uses asyncio.Lock to prevent concurrent refresh attempts,
        similar to PHP's retry storage mechanism.
        """
        async with self._refresh_lock:
            token_data = await self.auth_provider.get_token()
            if not token_data or 'refreshToken' not in token_data:
                return None

            # Attempt token refresh
            new_token_data = await self.auth_provider.refresh(token_data['refreshToken'])
            if not new_token_data or 'token' not in new_token_data:
                return None

            # Update request with new authorization header
            new_auth_headers = {'Authorization': f'Bearer {new_token_data["token"]}'}
            request.headers.update(new_auth_headers)

            # Retry request with new token
            return await client.send(request)

    async def request(
        self,
        method: str,
        path: str,
        headers: dict[str, str] | None = None,
        json_data: dict[str, Any] | None = None,
        **kwargs: Any,
    ) -> httpx.Response:
        """
        Make HTTP request with automatic auth and 401 retry logic.

        Args:
            method: HTTP method (GET, POST, etc.)
            path: API path (e.g., "/income")
            headers: Additional headers
            json_data: JSON request body
            **kwargs: Additional httpx.AsyncClient.request arguments

        Returns:
            httpx.Response object

        Raises:
            Domain exceptions via raise_for_status()
        """
        # Prepare headers
        request_headers = self.default_headers.copy()
        auth_headers = await self._get_auth_headers()
        request_headers.update(auth_headers)
        if headers:
            request_headers.update(headers)

        # Prepare request parameters
        request_kwargs = {
            'method': method,
            'url': self.base_url + path,
            'headers': request_headers,
            'timeout': self.timeout,
            **kwargs,
        }

        if json_data is not None:
            request_kwargs['json'] = json_data

        async with httpx.AsyncClient() as client:
            # Initial request
            response = await client.request(**request_kwargs)

            # Handle 401 with token refresh (max 1 retry)
            if response.status_code == HTTPStatus.UNAUTHORIZED:
                # Build request object for retry
                request = client.build_request(**request_kwargs)
                retry_response = await self._handle_401_response(client, request)
                if retry_response is not None:
                    response = retry_response

            # Check for domain exceptions
            raise_for_status(response)

            return response

    async def get(
        self,
        path: str,
        headers: dict[str, str] | None = None,
        **kwargs: Any,
    ) -> httpx.Response:
        """GET request."""
        return await self.request('GET', path, headers=headers, **kwargs)

    async def post(
        self,
        path: str,
        json_data: dict[str, Any] | None = None,
        headers: dict[str, str] | None = None,
        **kwargs: Any,
    ) -> httpx.Response:
        """POST request with JSON data."""
        return await self.request('POST', path, headers=headers, json_data=json_data, **kwargs)

    async def put(
        self,
        path: str,
        json_data: dict[str, Any] | None = None,
        headers: dict[str, str] | None = None,
        **kwargs: Any,
    ) -> httpx.Response:
        """PUT request with JSON data."""
        return await self.request('PUT', path, headers=headers, json_data=json_data, **kwargs)

    async def delete(
        self,
        path: str,
        headers: dict[str, str] | None = None,
        **kwargs: Any,
    ) -> httpx.Response:
        """DELETE request."""
        return await self.request('DELETE', path, headers=headers, **kwargs)
