from __future__ import annotations

from dataclasses import dataclass
from typing import Any, Mapping


@dataclass(slots=True)
class RateLimitMetadata:
    limit: int | None = None
    remaining: int | None = None
    reset: int | None = None


class ChaserApiError(Exception):
    def __init__(
        self,
        message: str,
        *,
        status: int,
        code: str | None = None,
        details: Any = None,
        request_id: str | None = None,
        rate_limit: RateLimitMetadata | None = None,
    ) -> None:
        super().__init__(message)
        self.status = status
        self.code = code
        self.details = details
        self.request_id = request_id
        self.rate_limit = rate_limit


class ChaserTimeoutError(Exception):
    pass


def api_error_from_response(status: int, headers: Mapping[str, str], body: bytes) -> ChaserApiError:
    details: Any = None
    code: str | None = None
    message = f"Request failed ({status})"

    try:
        import json

        decoded = json.loads(body.decode("utf-8")) if body else None
        details = decoded
        if isinstance(decoded, dict):
            code = _read_detail_field(decoded, "code") or _read_detail_field(decoded, "error")
            message = (
                _read_detail_field(decoded, "message")
                or _read_detail_field(decoded, "error")
                or message
            )
    except Exception:
        if body:
            try:
                message = body.decode("utf-8")
            except Exception:
                pass

    return ChaserApiError(
        message,
        status=status,
        code=code,
        details=details,
        request_id=headers.get("x-request-id"),
        rate_limit=RateLimitMetadata(
            limit=_parse_int_header(headers, "x-ratelimit-limit"),
            remaining=_parse_int_header(headers, "x-ratelimit-remaining"),
            reset=_parse_int_header(headers, "x-ratelimit-reset"),
        ),
    )


def _read_detail_field(details: dict[str, Any], key: str) -> str | None:
    value = details.get(key)
    if isinstance(value, str) and value:
        return value
    return None


def _parse_int_header(headers: Mapping[str, str], name: str) -> int | None:
    raw = headers.get(name)
    if raw is None:
        return None
    try:
        return int(raw)
    except ValueError:
        return None
