"""
Synchronous request handler — orchestrates SyncTransport with rate limiting,
authentication refresh, and error mapping.
"""

from __future__ import annotations

import time
from typing import TYPE_CHECKING

import httpx

from fortytwo.core.request.handler.base import BaseRequestHandler
from fortytwo.core.request.transport import SyncTransport
from fortytwo.exceptions import (
    FortyTwoNetworkException,
    FortyTwoRequestException,
    FortyTwoUnauthorizedException,
)
from fortytwo.logger import logger


if TYPE_CHECKING:
    from fortytwo.core.auth import SyncAuthenticator
    from fortytwo.core.config import Config
    from fortytwo.core.request.response import ApiListResponse, ApiResponse
    from fortytwo.parameter import Parameter
    from fortytwo.resources.resource import Resource, ResourceTemplate


class SyncRequestHandler(BaseRequestHandler):
    """
    Handles synchronous request execution, authentication, and error handling for the 42 API.
    """

    def __init__(
        self,
        config: Config,
        authenticator: SyncAuthenticator,
        http_client: httpx.Client,
    ) -> None:
        super().__init__(config)
        self.authenticator = authenticator
        self.__transport = SyncTransport(http_client)

    def request(
        self,
        resource: Resource[ResourceTemplate],
        *params: Parameter,
    ) -> ApiResponse[ResourceTemplate] | ApiListResponse[ResourceTemplate]:
        """
        Execute a synchronous request to the 42 API with error handling.

        Args:
            resource: The API resource to fetch.
            *params: Query parameters for the request.

        Returns:
            Parsed response data wrapped in an ApiResponse.

        Raises:
            FortyTwoRateLimitException: When rate limit is exceeded.
            FortyTwoNotFoundException: When resource is not found (404).
            FortyTwoUnauthorizedException: When authentication fails (401).
            FortyTwoServerException: When server errors occur (5xx).
            FortyTwoNetworkException: When network errors occur.
            FortyTwoRequestException: For other request-related errors.
        """
        self.__resource = resource.set_config(self.config)
        self.__params = list(params)
        self._second_rate_limit_retry_count = 0
        return self.__request()

    def __request(self) -> ApiResponse[ResourceTemplate] | ApiListResponse[ResourceTemplate]:
        self._check_hour_reset()

        if self._rate_limit_remaining <= self.config.requests_per_hour_margin:
            return self.__handle_rate_limit()

        time_elapsed = time.perf_counter() - self._request_time
        sleep_duration = (1 / self.config.requests_per_second) - time_elapsed
        if sleep_duration > 0:
            logger.debug("Sleeping for %.3f seconds to respect rate limit", sleep_duration)
            time.sleep(sleep_duration)

        try:
            response = self.__transport.send(
                resource=self.__resource,
                params=self.__params,
                tokens=self.authenticator.get_tokens(),
                timeout=self.config.request_timeout,
            )
            self._update_rate_limit(response)
            self._request_time = time.perf_counter()
            return self._build_api_response(response, self.__resource)

        except httpx.HTTPStatusError as e:
            return self.__handle_http_exception(e.response)

        except httpx.RequestError as e:
            logger.error("Network error occurred: %s", e)
            raise FortyTwoNetworkException(
                f"Network error occurred while communicating with the API: {e}",
            ) from e

        except Exception as e:
            logger.error("Unexpected error occurred: %s", e)
            raise FortyTwoRequestException(
                f"Unexpected error occurred: {e}",
            ) from e

    def __handle_http_exception(
        self,
        response: httpx.Response,
    ) -> ApiResponse[ResourceTemplate] | ApiListResponse[ResourceTemplate]:
        if response.status_code == 429:
            return self.__handle_rate_limit()

        if response.status_code == 401:
            return self.__handle_unauthorized()

        self._handle_http_status_error(response)
        raise FortyTwoRequestException("Unexpected HTTP error")

    def __handle_unauthorized(
        self,
    ) -> ApiResponse[ResourceTemplate] | ApiListResponse[ResourceTemplate]:
        logger.info("Access token expired, fetching a new one")
        try:
            self.authenticator.refresh_tokens()
            return self.__request()
        except Exception as e:
            logger.error("Failed to refresh tokens: %s", e)
            raise FortyTwoUnauthorizedException(
                "Failed to authenticate: Token refresh failed",
                error_code=401,
            ) from e

    def __handle_rate_limit(
        self,
    ) -> ApiResponse[ResourceTemplate] | ApiListResponse[ResourceTemplate]:
        if self._rate_limit_remaining <= self.config.requests_per_hour_margin:
            self._check_hourly_rate_limit_exceeded()

        if self._second_rate_limit_retry_count >= self.config.requests_per_second_retries:
            self._check_per_second_retries_exceeded()

        retry_delay = self._get_retry_delay()
        time.sleep(retry_delay)

        return self.__request()
