from __future__ import annotations

from dataclasses import dataclass
from typing import Any

import httpx

from .config import mcp_config


@dataclass
class ApiOk:
    ok: bool
    status: int
    data: dict[str, Any]

    def __init__(self, status: int, data: dict[str, Any]):
        self.ok = True
        self.status = status
        self.data = data


@dataclass
class ApiErr:
    ok: bool
    status: int
    code: str
    message: str
    retry_after_sec: int | None = None

    def __init__(
        self,
        status: int,
        code: str,
        message: str,
        retry_after_sec: int | None = None,
    ):
        self.ok = False
        self.status = status
        self.code = code
        self.message = message
        self.retry_after_sec = retry_after_sec


ApiResult = ApiOk | ApiErr


def _map_error_message(
    status: int,
    code: str | None,
    server_message: str | None,
) -> str:
    mapping: dict[str, str] = {
        "rate_limit_exceeded": server_message or "Rate limit exceeded. Please wait and retry.",
        "unauthorized": "Invalid or missing API key.",
        "forbidden": "API key does not have required permissions.",
        "size_limit_exceeded": f"File exceeds {mcp_config.max_file_size_bytes // (1024 * 1024)} MB limit.",
        "blocked_mime_type": server_message or "File type not allowed.",
        "empty_file": "File cannot be empty.",
        "upload_worker_unavailable": "Upload service temporarily unavailable. Please retry.",
        "storage_degraded": "Storage backend temporarily degraded. Please retry in 30s.",
        "concurrency_limit_reached": "Too many concurrent uploads. Please wait and retry.",
        "idempotency_in_progress": "A request with this idempotency key is already being processed.",
        "invalid_url": server_message or "URL is not allowed.",
        "ssrf_blocked": server_message or "URL is not allowed.",
        "fetch_failed": server_message or "Failed to fetch file from URL.",
        "fetch_timeout": "Timed out fetching file from URL.",
        "upload_not_found": "Upload not found.",
    }

    if code and code in mapping:
        return mapping[code]
    if status >= 500:
        return "Upload service error. Please retry."
    return server_message or f"Request failed ({status})."


def _parse_error(response: httpx.Response) -> ApiErr:
    try:
        body = response.json()
    except Exception:
        body = None

    error_obj = (body or {}).get("error", {}) if isinstance(body, dict) else {}
    code = error_obj.get("code") or f"http_{response.status_code}"
    server_message = error_obj.get("message")

    retry_after_sec = error_obj.get("retryAfterSec")
    if retry_after_sec is None:
        raw_retry = response.headers.get("Retry-After")
        if raw_retry:
            try:
                retry_after_sec = int(raw_retry)
            except ValueError:
                pass

    return ApiErr(
        status=response.status_code,
        code=code,
        message=_map_error_message(response.status_code, code, server_message),
        retry_after_sec=retry_after_sec,
    )


def _client() -> httpx.AsyncClient:
    return httpx.AsyncClient(
        timeout=httpx.Timeout(mcp_config.api_timeout_ms / 1000),
    )


async def api_get(path: str, agent_key: str) -> ApiResult:
    url = f"{mcp_config.api_base_url}{path}"
    async with _client() as client:
        try:
            resp = await client.get(url, headers={"X-Agent-Key": agent_key})
        except httpx.TimeoutException:
            return ApiErr(0, "timeout", f"Request timed out after {mcp_config.api_timeout_ms}ms")
        except httpx.HTTPError as exc:
            return ApiErr(0, "network_error", f"Upload service error: {exc}")

        if resp.is_success:
            return ApiOk(resp.status_code, resp.json())
        return _parse_error(resp)


async def api_post_json(
    path: str,
    agent_key: str,
    body: dict[str, Any],
    extra_headers: dict[str, str] | None = None,
) -> ApiResult:
    url = f"{mcp_config.api_base_url}{path}"
    headers: dict[str, str] = {"X-Agent-Key": agent_key, "Content-Type": "application/json"}
    if extra_headers:
        headers.update(extra_headers)

    async with _client() as client:
        try:
            resp = await client.post(url, headers=headers, json=body)
        except httpx.TimeoutException:
            return ApiErr(0, "timeout", f"Request timed out after {mcp_config.api_timeout_ms}ms")
        except httpx.HTTPError as exc:
            return ApiErr(0, "network_error", f"Upload service error: {exc}")

        if resp.is_success:
            return ApiOk(resp.status_code, resp.json())
        return _parse_error(resp)


async def api_post_multipart(
    path: str,
    agent_key: str,
    file_data: bytes,
    file_name: str,
    mime_type: str = "application/octet-stream",
    extra_headers: dict[str, str] | None = None,
) -> ApiResult:
    url = f"{mcp_config.api_base_url}{path}"
    headers: dict[str, str] = {"X-Agent-Key": agent_key}
    if extra_headers:
        headers.update(extra_headers)

    files = {"file": (file_name, file_data, mime_type)}

    async with _client() as client:
        try:
            resp = await client.post(url, headers=headers, files=files)
        except httpx.TimeoutException:
            return ApiErr(0, "timeout", f"Request timed out after {mcp_config.api_timeout_ms}ms")
        except httpx.HTTPError as exc:
            return ApiErr(0, "network_error", f"Upload service error: {exc}")

        if resp.is_success:
            return ApiOk(resp.status_code, resp.json())
        return _parse_error(resp)
