"""
senzib.utils.http

Общие утилиты для HTTP-запросов.

Дают:
- http_request_json(...) — запрос + JSON + простые ретраи.
"""

from __future__ import annotations

from typing import Any, Dict, Optional

import time
import requests

from .log import get_logger

logger = get_logger("http")


class HTTPRequestError(Exception):
    """Общая ошибка HTTP-запроса в senzib."""


def http_request_json(
    method: str,
    url: str,
    *,
    headers: Optional[Dict[str, str]] = None,
    params: Optional[Dict[str, Any]] = None,
    json_body: Optional[Dict[str, Any]] = None,
    timeout: int = 30,
    retries: int = 0,
    backoff_factor: float = 0.5,
    raise_for_status: bool = True,
) -> Any:
    """
    Выполнить HTTP-запрос и вернуть распарсенный JSON.

    :param method: "GET", "POST", ...
    :param url: полный URL.
    :param headers: заголовки запроса.
    :param params: query-параметры.
    :param json_body: тело запроса (JSON).
    :param timeout: таймаут в секундах.
    :param retries: количество повторов при сетевой ошибке или 5xx.
    :param backoff_factor:
        множитель для паузы между повторами:
            sleep = backoff_factor * (2 ** attempt).
    :param raise_for_status:
        Если True — при HTTP != 2xx бросает HTTPRequestError.
        Если False — просто вернёт JSON (или None) и не проверит код.
    :return:
        Любой объект, который вернёт response.json().
    :raises HTTPRequestError:
        - при сетевых ошибках;
        - при HTTP != 2xx, если raise_for_status=True;
        - при ошибке парсинга JSON.
    """
    method = method.upper()
    attempt = 0

    while True:
        try:
            resp = requests.request(
                method,
                url,
                headers=headers,
                params=params,
                json=json_body,
                timeout=timeout,
            )
        except requests.RequestException as exc:
            if attempt >= retries:
                raise HTTPRequestError(f"Network error for {method} {url}: {exc}") from exc

            sleep = backoff_factor * (2 ** attempt)
            logger.warning(
                "Network error for %s %s: %s. Retry %s/%s in %.1fs",
                method,
                url,
                exc,
                attempt + 1,
                retries,
                sleep,
            )
            time.sleep(sleep)
            attempt += 1
            continue

        # HTTP статус
        if raise_for_status and not (200 <= resp.status_code < 300):
            if attempt >= retries:
                raise HTTPRequestError(
                    f"HTTP {resp.status_code} for {method} {url}: {resp.text}"
                )

            sleep = backoff_factor * (2 ** attempt)
            logger.warning(
                "HTTP %s for %s %s. Retry %s/%s in %.1fs",
                resp.status_code,
                method,
                url,
                attempt + 1,
                retries,
                sleep,
            )
            time.sleep(sleep)
            attempt += 1
            continue

        # JSON
        try:
            return resp.json()
        except ValueError as exc:
            raise HTTPRequestError(
                f"Failed to parse JSON for {method} {url}: {exc}"
            ) from exc
