"""Shared configuration and request utilities."""

from __future__ import annotations

import logging
import os

from ._exceptions import (
    APIError,
    AuthenticationError,
    NotFoundError,
    RateLimitError,
    ValidationError,
)

DEFAULT_BASE_URL = "https://api.influxion.io/v1"

_logger = logging.getLogger("influxion")

MAX_RETRIES = 3
_INITIAL_BACKOFF = 0.5
_MAX_BACKOFF = 8.0
_BACKOFF_MULTIPLIER = 2.0

# Status codes that warrant a retry attempt.
_RETRYABLE_STATUS_CODES = {429, 500, 502, 503, 504}


class InfluxionConfig:
    """Resolved, immutable client configuration."""

    __slots__ = ("api_key", "base_url", "fail_silently")

    def __init__(self, api_key: str, base_url: str, fail_silently: bool) -> None:
        self.api_key = api_key
        self.base_url = base_url.rstrip("/")
        self.fail_silently = fail_silently


def build_config(
    api_key: str | None,
    base_url: str | None,
    fail_silently: bool,
) -> InfluxionConfig:
    """Resolve client configuration from arguments and environment variables.

    Raises :exc:`~influxion.AuthenticationError` immediately if no API key is
    available, regardless of ``fail_silently``.
    """
    resolved_key = api_key or os.environ.get("INFLUXION_API_KEY")
    if not resolved_key:
        raise AuthenticationError(
            "No API key provided. Pass api_key= or set the INFLUXION_API_KEY"
            " environment variable."
        )
    resolved_url = base_url or os.environ.get("INFLUXION_BASE_URL", DEFAULT_BASE_URL)
    return InfluxionConfig(
        api_key=resolved_key,
        base_url=resolved_url,
        fail_silently=fail_silently,
    )


def auth_headers(api_key: str) -> dict[str, str]:
    return {"Authorization": f"Bearer {api_key}"}


def backoff_seconds(attempt: int) -> float:
    """Return the sleep duration for ``attempt`` (0-indexed): 0.5 s, 1 s, 2 s, …"""
    return min(_INITIAL_BACKOFF * (_BACKOFF_MULTIPLIER**attempt), _MAX_BACKOFF)


def should_retry_status(status_code: int) -> bool:
    return status_code in _RETRYABLE_STATUS_CODES


def map_http_error(status_code: int, detail: str) -> APIError:
    """Map an HTTP status code to the most specific :exc:`APIError` subclass."""
    if status_code == 429:
        return RateLimitError(detail, status_code=status_code)
    if status_code == 404:
        return NotFoundError(detail, status_code=status_code)
    if status_code == 422:
        return ValidationError(detail, status_code=status_code)
    return APIError(detail, status_code=status_code)
