"""{{ display_name }} MCP server — generated by mcpify. Do not hand-edit.

Generic operationId -> HTTP request dispatcher (rather than one method per
operation), so generation stays robust against specs with hundreds of
operations and this file's size never grows with the spec.
"""

from __future__ import annotations

from typing import Any
from urllib.parse import quote, urlencode, urljoin

import httpx

from ..auth.auth_manager import AuthManager
from ..auth.request_credentials import RequestCredentials
from ..core.circuit_breaker import CircuitBreaker
from ..core.config import Config
from ..core.rate_limiter import RateLimiter
from ..data.store import EndpointRecord


def build_api_url(base_url: str, path: str, query: list[tuple[str, str]]) -> str:
    """Joins a configured base URL with an operation path and query
    params, normalizing slashes so callers never have to think about
    trailing/leading separators.
    """
    base = base_url if base_url.endswith("/") else f"{base_url}/"
    url = urljoin(base, path.lstrip("/"))
    if query:
        url = f"{url}?{urlencode(query)}"
    return url


def _parameters_of(endpoint: EndpointRecord) -> list[dict[str, Any]]:
    schema = endpoint.input_schema or {}
    return schema.get("parameters", []) if isinstance(schema, dict) else []


def _names_with_location(endpoint: EndpointRecord, location: str) -> list[str]:
    return [
        param["name"]
        for param in _parameters_of(endpoint)
        if param.get("in") == location and "name" in param
    ]


def _value_to_plain_string(value: Any) -> str:
    return value if isinstance(value, str) else str(value)


def _apply_path_params(endpoint: EndpointRecord, args: dict[str, Any]) -> str:
    """Substitutes every `{name}` path template segment with the matching
    argument, percent-encoded.
    """
    result: list[str] = []
    chars = iter(endpoint.path)
    for char in chars:
        if char != "{":
            result.append(char)
            continue
        name_chars: list[str] = []
        for next_char in chars:
            if next_char == "}":
                break
            name_chars.append(next_char)
        name = "".join(name_chars)
        value = _value_to_plain_string(args.get(name, ""))
        result.append(quote(value, safe=""))
    return "".join(result)


def _pick_by_location(
    endpoint: EndpointRecord, args: dict[str, Any], location: str
) -> list[tuple[str, str]]:
    return [
        (name, _value_to_plain_string(args[name]))
        for name in _names_with_location(endpoint, location)
        if name in args
    ]


class ApiClient:
    def __init__(self, config: Config) -> None:
        self._config = config
        self._circuit_breaker = CircuitBreaker()
        self._rate_limiter = RateLimiter(limit=config.rate_limit, window_s=1.0)

    async def execute(
        self,
        endpoint: EndpointRecord,
        args: dict[str, Any],
        auth_manager: AuthManager,
        request_override: RequestCredentials | None = None,
    ) -> Any:
        """Executes `endpoint` against the configured target API, retrying
        transient failures up to `config.retry_attempts` times, all inside
        the circuit breaker and rate limiter (REQ-2.3.3). `args` may
        contain a `body` key holding the JSON request body, alongside any
        path/query/header parameter values.
        """
        self._rate_limiter.acquire()

        query = _pick_by_location(endpoint, args, "query")
        path = _apply_path_params(endpoint, args)
        url = build_api_url(self._config.url, path, query)

        headers = {"Content-Type": "application/json"}
        headers.update(_pick_by_location(endpoint, args, "header"))
        headers = await auth_manager.apply_auth_headers(
            headers, endpoint.method, url, self._config.transport, request_override
        )

        body = args.get("body")

        return await self._circuit_breaker.execute(
            lambda: self._dispatch(endpoint.method, url, body, headers)
        )

    async def _dispatch(self, method: str, url: str, body: Any, headers: dict[str, str]) -> Any:
        timeout = self._config.timeout_ms / 1000
        attempt = 0
        async with httpx.AsyncClient(timeout=timeout) as client:
            while True:
                try:
                    response = await client.request(method, url, json=body, headers=headers)
                    response.raise_for_status()
                    try:
                        return response.json()
                    except ValueError:
                        return None
                except httpx.HTTPError:
                    attempt += 1
                    if attempt > self._config.retry_attempts:
                        raise
