from __future__ import annotations

import logging
import time
from typing import Any

import httpx
from flask import jsonify, request

from . import settings

logger = logging.getLogger(__name__)


class AriadneError(Exception):
    """Carry an upstream-facing error message and HTTP status code."""

    def __init__(self, message: str, status_code: int = 502) -> None:
        super().__init__(message)
        self.status_code = status_code


def enabled() -> bool:
    """Return whether Ariadne proxying is configured for this portal."""

    return bool(settings.ARIADNE_URL)


def _auth_headers() -> dict[str, str]:
    header = request.headers.get("Authorization", "").strip()
    return {"Authorization": header} if header else {}


def _url(path: str) -> str:
    base = settings.ARIADNE_URL.rstrip("/")
    suffix = path.lstrip("/")
    return f"{base}/{suffix}" if suffix else base


def request_raw(
    method: str,
    path: str,
    *,
    payload: Any | None = None,
    params: dict[str, Any] | None = None,
    timeout_sec: float | None = None,
) -> httpx.Response:
    """Send one authenticated request to Ariadne and return the raw response.

    WHY: callers need the exact upstream status/body so local routes can act as
    a transparent compatibility proxy while still applying retry telemetry.
    """

    if not enabled():
        raise AriadneError("ariadne not configured", 503)

    url = _url(path)
    attempts = max(1, settings.ARIADNE_RETRY_COUNT)
    request_timeout = timeout_sec if timeout_sec is not None else settings.ARIADNE_TIMEOUT_SEC
    for attempt in range(1, attempts + 1):
        try:
            with httpx.Client(timeout=request_timeout) as client:
                resp = client.request(
                    method,
                    url,
                    headers=_auth_headers(),
                    json=payload,
                    params=params,
                )
            if resp.status_code >= 500:
                logger.warning(
                    "ariadne error response",
                    extra={"method": method, "path": path, "status": resp.status_code},
                )
            return resp
        except httpx.RequestError as exc:
            logger.warning(
                "ariadne request failed",
                extra={
                    "method": method,
                    "path": path,
                    "attempt": attempt,
                    "timeout_sec": request_timeout,
                    "error": str(exc),
                },
            )
            if attempt >= attempts:
                raise AriadneError("ariadne unavailable", 502) from exc
            time.sleep(settings.ARIADNE_RETRY_BACKOFF_SEC * attempt)


def proxy(
    method: str,
    path: str,
    *,
    payload: Any | None = None,
    params: dict[str, Any] | None = None,
    timeout_sec: float | None = None,
) -> tuple[Any, int]:
    """Proxy an Ariadne response through Flask as JSON plus status code.

    WHY: route handlers should share upstream error normalization instead of
    duplicating JSON parsing and outage handling at each call site.
    """

    try:
        resp = request_raw(method, path, payload=payload, params=params, timeout_sec=timeout_sec)
    except AriadneError as exc:
        return jsonify({"error": str(exc)}), exc.status_code

    try:
        data = resp.json()
    except ValueError:
        detail = resp.text.strip()
        data = {"error": detail or "upstream error"}

    return jsonify(data), resp.status_code
