# === `Projects/veritell-langchain/src/veritell_langchain/http.py` ===
from __future__ import annotations

from dataclasses import dataclass
from typing import Any, Dict, Mapping, Optional, Protocol

from veritell_langchain.errors import (
    RateLimitedError,
    ServiceUnavailableError,
    VeritellAuthError,
    VeritellError,
    VeritellRequestError,
    VeritellServerError,
    VeritellTimeoutError,
)


class HttpClient(Protocol):
    def post(
        self,
        url: str,
        *,
        json: Dict[str, Any],
        headers: Optional[Mapping[str, str]] = None,
        timeout_s: Optional[float] = None,
    ) -> "HttpResponse": ...


@dataclass(frozen=True)
class HttpResponse:
    status_code: int
    json_data: Dict[str, Any]

    def json(self) -> Dict[str, Any]:
        return self.json_data


def _safe_int(value: Any) -> Optional[int]:
    try:
        return int(value)
    except (TypeError, ValueError):
        return None


def _parse_error_body(data: Any, *, status_code: int) -> Dict[str, Any]:
    if not isinstance(data, dict):
        return {
            "error": "unknown_error",
            "message": "Unexpected error response from server.",
            "status": status_code,
        }

    error = data.get("error")
    message = data.get("message")

    if not isinstance(error, str) or not error:
        error = "unknown_error"

    if not isinstance(message, str) or not message:
        message = "Request failed."

    body_status = data.get("status")
    status = _safe_int(body_status) or status_code

    upgrade_url = data.get("upgrade_url")
    if not isinstance(upgrade_url, str):
        upgrade_url = None

    retry_after_seconds = _safe_int(data.get("retry_after_seconds"))

    return {
        "error": error,
        "message": message,
        "status": status,
        "upgrade_url": upgrade_url,
        "retry_after_seconds": retry_after_seconds,
    }


def _raise_typed_http_error(*, status_code: int, data: Any) -> None:
    parsed = _parse_error_body(data, status_code=status_code)

    error = parsed["error"]
    message = parsed["message"]
    status = parsed["status"]
    upgrade_url = parsed["upgrade_url"]
    retry_after_seconds = parsed["retry_after_seconds"]

    if status_code == 503 or error == "service_unavailable":
        raise ServiceUnavailableError(
            message=message,
            status=status,
            error=error,
            upgrade_url=upgrade_url,
            retry_after_seconds=retry_after_seconds,
        )

    if status_code == 429 or error in {"rate_limited", "plan_limit_exceeded", "limit_reached"}:
        raise RateLimitedError(
            message=message,
            status=status,
            error=error,
            upgrade_url=upgrade_url,
            retry_after_seconds=retry_after_seconds,
        )

    if status_code in {401, 403} or error in {"invalid_api_key", "key_revoked"}:
        raise VeritellAuthError(
            status=status,
            error=error,
            message=message,
            upgrade_url=upgrade_url,
            retry_after_seconds=retry_after_seconds,
        )

    if 400 <= status_code < 500:
        raise VeritellRequestError(
            status=status,
            error=error,
            message=message,
            upgrade_url=upgrade_url,
            retry_after_seconds=retry_after_seconds,
        )

    if 500 <= status_code:
        raise VeritellServerError(
            status=status,
            error=error,
            message=message,
            upgrade_url=upgrade_url,
            retry_after_seconds=retry_after_seconds,
        )


def post_json(
    client: HttpClient,
    url: str,
    *,
    payload: Dict[str, Any],
    headers: Optional[Mapping[str, str]] = None,
    timeout_s: Optional[float] = None,
) -> Dict[str, Any]:
    try:
        resp = client.post(url, json=payload, headers=headers, timeout_s=timeout_s)
    except TimeoutError as e:
        raise VeritellTimeoutError(str(e) or "timeout") from e
    except OSError as e:
        raise VeritellError(str(e) or "network error") from e

    if resp.status_code < 200 or resp.status_code >= 300:
        _raise_typed_http_error(status_code=resp.status_code, data=resp.json())
        raise VeritellError(f"unexpected status: {resp.status_code}")

    data = resp.json()
    if not isinstance(data, dict):
        raise VeritellError("unexpected JSON response type")
    return data
