"""
Shared base class for request handlers — rate limiting, error mapping, and
response building logic.
"""

from __future__ import annotations

import time
from typing import TYPE_CHECKING

from fortytwo.core.request.response import ApiListResponse, ApiResponse
from fortytwo.exceptions import (
    FortyTwoNotFoundException,
    FortyTwoRateLimitException,
    FortyTwoRequestException,
    FortyTwoServerException,
)
from fortytwo.logger import logger


if TYPE_CHECKING:
    import httpx

    from fortytwo.core.config import Config
    from fortytwo.resources.resource import Resource, ResourceTemplate


class BaseRequestHandler:
    """
    Shared logic for request handlers (rate limiting, error mapping).
    """

    def __init__(
        self,
        config: Config,
    ) -> None:
        self.config = config

        self._second_rate_limit_retry_count = 0
        self._rate_limit_remaining = self.config.requests_per_hour_margin + 1
        self._current_hour_start = self._get_current_hour_start()
        self._request_time = 0.0

    def _check_hour_reset(self) -> None:
        """
        Check if the hour has reset and update the rate limit remaining count.
        """
        current_hour_start = self._get_current_hour_start()
        if current_hour_start > self._current_hour_start:
            self._rate_limit_remaining = self.config.requests_per_hour_margin + 1
            self._current_hour_start = current_hour_start

    def _update_rate_limit(self, response: httpx.Response) -> None:
        """
        Update the remaining rate limit count from the response headers.
        """
        rate_limit_remaining = response.headers.get(
            "x-hourly-ratelimit-remaining",
            str(self.config.requests_per_hour_margin + 1),
        )
        try:
            self._rate_limit_remaining = int(rate_limit_remaining)
        except ValueError:
            logger.warning("Invalid rate limit remaining value: %s", rate_limit_remaining)
            self._rate_limit_remaining = self.config.requests_per_hour_margin + 1
        logger.info("Rate limit remaining: %d requests", self._rate_limit_remaining)

    def _build_api_response(
        self,
        response: httpx.Response,
        resource: Resource[ResourceTemplate],
    ) -> ApiResponse[ResourceTemplate] | ApiListResponse[ResourceTemplate]:
        """
        Build an ApiResponse or ApiListResponse from the HTTP response.

        Returns an ``ApiListResponse`` when the resource is a ``ListResource``,
        otherwise a plain ``ApiResponse``.
        """
        from fortytwo.resources.resource import ListResource

        response_json = response.json()
        data = resource.parse_response(response_json)

        response_cls: type[ApiResponse | ApiListResponse] = (
            ApiListResponse if isinstance(resource, ListResource) else ApiResponse
        )
        return response_cls(
            data=data,
            status_code=response.status_code,
            headers=dict(response.headers),
        )

    def _handle_http_status_error(
        self,
        response: httpx.Response,
    ) -> None:
        """
        Raise the appropriate exception for HTTP error status codes.

        Raises:
            FortyTwoNotFoundException: When resource not found (404).
            FortyTwoServerException: For server errors (5xx).
            FortyTwoRequestException: For other HTTP errors.
        """
        if response.status_code == 404:
            logger.error("Resource not found (404): %s", response.reason_phrase)
            raise FortyTwoNotFoundException(
                f"Requested resource not found: {response.reason_phrase}",
                error_code=404,
            )

        if 500 <= response.status_code < 600:
            logger.error(
                "Server error (%s): %s",
                response.status_code,
                response.reason_phrase,
            )
            raise FortyTwoServerException(
                f"Server error ({response.status_code}): {response.reason_phrase}",
                error_code=response.status_code,
            )

        logger.error(
            "Request failed (%s): %s",
            response.status_code,
            response.reason_phrase,
        )
        raise FortyTwoRequestException(
            f"Request failed ({response.status_code}): {response.reason_phrase}",
            error_code=response.status_code,
        )

    def _check_hourly_rate_limit_exceeded(self) -> None:
        """
        Raise if hourly rate limit is exceeded.
        """
        wait_time = self._calculate_hour_reset_wait_time()
        logger.error("Hourly rate limit exceeded. Reset in %.0fs", wait_time)
        raise FortyTwoRateLimitException(
            "Hourly rate limit exceeded.",
            wait_time=wait_time,
        )

    def _check_per_second_retries_exceeded(self) -> None:
        """
        Raise if per-second rate limit retries are exhausted.
        """
        wait_time = self._calculate_hour_reset_wait_time()
        logger.error(
            "Per-second rate limit: Max retries (%d) exceeded",
            self.config.requests_per_second_retries,
        )
        raise FortyTwoRateLimitException(
            f"Rate limit exceeded after {self._second_rate_limit_retry_count} retries.",
            wait_time=wait_time,
        )

    def _get_retry_delay(self) -> float:
        """
        Get the delay before the next retry attempt for per-second rate limits.
        """
        retry_delay = self.config.requests_per_second_retry_delay * (
            2**self._second_rate_limit_retry_count
        )
        self._second_rate_limit_retry_count += 1
        logger.info(
            "Per-second rate limit hit (retry %d/%d), waiting %.1fs...",
            self._second_rate_limit_retry_count,
            self.config.requests_per_second_retries,
            retry_delay,
        )
        return retry_delay

    def _get_current_hour_start(self) -> float:
        """
        Get the timestamp for the start of the current hour.
        """
        current_time = time.time()
        return current_time - (current_time % 3600)

    def _calculate_hour_reset_wait_time(self) -> float:
        """
        Calculate the wait time until the hourly rate limit resets.
        """
        current_time = time.time()
        reset_time = self._current_hour_start + 3600
        return max(0.0, reset_time - current_time) + 1.0
