"""Shared FastAPI dependencies.

get_restate_client(): returns the shared httpx.AsyncClient from app lifespan state.
restate_proxy(): calls Restate Service/VirtualObject handlers and maps errors.
"""

import json
from typing import Any, Literal

import httpx
import logfire
from fastapi import HTTPException, Request, status

from app.config import Settings, get_settings


def get_restate_client(request: Request) -> httpx.AsyncClient:
    """Return the shared httpx.AsyncClient stored in app state."""
    client: httpx.AsyncClient = request.app.state.http_client
    return client


def restate_auth_headers(settings: Settings | None = None) -> dict[str, str]:
    """Bearer auth header for Restate Cloud; empty dict for self-hosted Restate.

    Restate Cloud authenticates every ingress + admin call with a bearer
    token. Self-hosted Restate (local dev, Docker compose) listens
    unauthenticated. Returning `{}` for the unset case keeps every caller's
    header merge a single expression with no conditional branching.
    """
    if settings is None:
        settings = get_settings()
    token = settings.RESTATE_CLOUD_AUTH_TOKEN
    if not token:
        return {}
    return {"Authorization": f"Bearer {token}"}


async def restate_proxy(
    client: httpx.AsyncClient,
    service_name: str,
    key_or_handler: str,
    handler_or_payload: str | dict[str, Any],
    payload: dict[str, Any] | None = None,
    *,
    mode: Literal["service", "object"] | None = None,
    settings: Settings | None = None,
) -> dict[str, Any]:
    """Call a Restate Service or VirtualObject handler via the ingress API.

    Supports two calling conventions:
    - VirtualObject (keyed):   restate_proxy(client, ServiceName, key, handler, payload)
    - Plain Service (keyless): restate_proxy(client, ServiceName, handler, payload_dict)

    Maps Restate HTTP errors to appropriate FastAPI exceptions:
    - ConnectError -> 503 (Restate server down)
    - 4xx from Restate -> pass through
    - 5xx from Restate -> 502
    """
    if settings is None:
        settings = get_settings()

    if mode == "object" and payload is None:
        raise HTTPException(
            status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
            detail="Object mode requires object key, handler, and payload",
        )

    if mode == "service" and payload is not None:
        raise HTTPException(
            status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
            detail="Service mode does not accept object key payload signature",
        )

    if payload is not None:
        if not isinstance(handler_or_payload, str):
            raise HTTPException(
                status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
                detail="Object mode requires string handler",
            )
        key = key_or_handler
        handler = handler_or_payload
        url = f"{settings.RESTATE_INGRESS_URL}/{service_name}/{key}/{handler}"
        resolved_payload = payload
    else:
        if not isinstance(handler_or_payload, dict):
            raise HTTPException(
                status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
                detail="Service mode requires payload dictionary",
            )
        handler = key_or_handler
        resolved_payload = handler_or_payload
        url = f"{settings.RESTATE_INGRESS_URL}/{service_name}/{handler}"

    headers = {"Content-Type": "application/json", **restate_auth_headers(settings)}
    try:
        with logfire.span("restate_call", service=service_name, handler=handler):
            response = await client.post(
                url,
                json=resolved_payload,
                headers=headers,
                timeout=30.0,
            )
    except httpx.ConnectError as exc:
        logfire.error("restate_connect_error", url=url, error=str(exc))
        raise HTTPException(
            status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
            detail="Workflow service unavailable. Please try again.",
        ) from exc
    except httpx.TimeoutException as exc:
        logfire.error("restate_timeout", url=url, error=str(exc))
        raise HTTPException(
            status_code=status.HTTP_504_GATEWAY_TIMEOUT,
            detail="Workflow service timed out.",
        ) from exc

    if response.status_code >= 500:
        logfire.error(
            "restate_server_error",
            status=response.status_code,
            body=response.text[:500],
        )
        raise HTTPException(
            status_code=status.HTTP_502_BAD_GATEWAY,
            detail=f"Workflow service error: {response.status_code}",
        )

    if response.status_code >= 400:
        # Pass 4xx through (e.g., Restate TerminalError -> 409/422)
        try:
            detail = response.json()
        except json.JSONDecodeError:
            detail = response.text
        raise HTTPException(status_code=response.status_code, detail=detail)

    if response.text:
        return response.json()
    return {}
