"""
ABOS AI Worker – abos-api HTTP Client (for MCP tool calls)
=============================================================
Thin async wrapper for calling the Spring Boot abos-api backend from MCP
tools (see app/mcp_tools.py).

Auth model
----------
Every MCP tool call acts on behalf of whichever ABOS user is connected to
this service's /mcp endpoint. That connection carries the *same* JWT the
caller already holds for abos-api (`Authorization: Bearer <token>`) — the
tool layer reads it off the incoming MCP HTTP request and forwards it
verbatim on every call here. This mirrors the "forward the caller's Spring
Boot token" pattern already used by app/email_analyzer.py and
app/meeting_analyzer.py for their `springBootAuthToken` field.

This module never mints, caches, or falls back to its own credentials, and
never accepts a tenant id as a parameter: abos-api's JwtAuthFilter derives
both the acting user and the tenant purely from the JWT (see
abos-api/src/main/java/.../security/JwtAuthFilter.java), so there is no
tenant-scoping logic to get wrong here — a missing/invalid token simply
fails the call closed.

Error handling
--------------
abos-api's GlobalExceptionHandler returns a consistent
`{status, error, message, path}` JSON body for every error response (see
abos-api/src/main/java/.../exception/GlobalExceptionHandler.java). AbosApiError
extracts that `message` and nothing else — never a raw stack trace or HTML —
so it's safe to surface directly to an LLM tool caller.
"""

from __future__ import annotations

import logging
from typing import Any, Optional

import httpx

from app.config import settings

logger = logging.getLogger(__name__)


class AbosApiError(Exception):
    """Raised for any failed abos-api call. `.message` is always short and LLM-readable."""

    def __init__(self, message: str, *, status_code: Optional[int] = None) -> None:
        super().__init__(message)
        self.message = message
        self.status_code = status_code


async def call_abos_api(
    *,
    method: str,
    path: str,
    auth_token: str,
    params: dict[str, Any] | None = None,
    json_body: dict[str, Any] | None = None,
    event: str | None = None,
) -> Any:
    """
    Call an abos-api endpoint using the caller's forwarded JWT.

    Parameters
    ----------
    method:
        HTTP verb, e.g. "GET" or "POST".
    path:
        abos-api path starting with "/", e.g. "/api/leads/event/lead-list".
    auth_token:
        The caller's abos-api JWT (without the "Bearer " prefix). An empty
        value fails closed with a clear AbosApiError rather than calling
        abos-api unauthenticated.
    params:
        Query-string parameters.
    json_body:
        JSON request body for POST/PUT calls.
    event:
        Value for the `X-Event` header abos-api's event/webhook-style
        controllers require on POST/PUT (e.g. "CREATE_LEAD"). Not needed
        for GET lookups.

    Returns
    -------
    The parsed JSON response body (or None for an empty 2xx response).

    Raises
    ------
    AbosApiError
        On any non-2xx response, transport failure, or missing auth_token.
        Always carries a short human/LLM-readable message — never a stack
        trace.
    """
    if not auth_token:
        raise AbosApiError(
            "No ABOS auth token was supplied for this tool call. Connect to this MCP "
            "server with an 'Authorization: Bearer <token>' header carrying a valid "
            "ABOS session token — tools cannot act without one."
        )

    headers = {
        "Authorization": f"Bearer {auth_token}",
        "Content-Type": "application/json",
    }
    if event:
        headers["X-Event"] = event

    url = f"{settings.abos_api_base_url.rstrip('/')}{path}"

    try:
        async with httpx.AsyncClient(timeout=settings.abos_api_timeout) as client:
            response = await client.request(
                method, url, headers=headers, params=params, json=json_body
            )
    except httpx.RequestError as exc:
        logger.error("abos-api request failed: %s %s -> %s", method, url, exc)
        raise AbosApiError(
            f"Could not reach abos-api for {method} {path}: {exc}"
        ) from exc

    if response.status_code >= 400:
        raise AbosApiError(
            _extract_error_message(response), status_code=response.status_code
        )

    if not response.content:
        return None
    try:
        return response.json()
    except ValueError as exc:
        raise AbosApiError(
            f"abos-api returned a non-JSON response for {method} {path}"
        ) from exc


def _extract_error_message(response: httpx.Response) -> str:
    """Pull the `message`/`error` field out of abos-api's ApiErrorResponse body."""
    try:
        body = response.json()
    except ValueError:
        return f"abos-api returned HTTP {response.status_code}"
    if isinstance(body, dict):
        message = body.get("message") or body.get("error")
        if message:
            return f"{message} (HTTP {response.status_code})"
    return f"abos-api returned HTTP {response.status_code}"
