from __future__ import annotations
import re
from typing import Any, Dict, Optional
import httpx

from .openapi_loader import OpenAPIOperation

_PATH_PARAM = re.compile(r"{([^}]+)}")

class HttpAuth:
    def __init__(self, bearer_token: Optional[str] = None, headers: Optional[Dict[str, str]] = None):
        self.bearer_token = bearer_token
        self.headers = headers or {}

    def apply(self, headers: Dict[str, str]) -> Dict[str, str]:
        out = dict(headers)
        out.update(self.headers)
        if self.bearer_token and "authorization" not in {k.lower(): v for k, v in out.items()}:
            out["Authorization"] = f"Bearer {self.bearer_token}"
        return out

class OpenAPIHttpExecutor:
    """Executes OpenAPI-discovered operations over HTTP using httpx.AsyncClient."""
    def __init__(
        self,
        *,
        base_url: str,
        auth: Optional[HttpAuth] = None,
        timeout_s: float = 60.0,
        verify_tls: bool = True,
    ):
        self.base_url = base_url.rstrip("/")
        self.auth = auth or HttpAuth()
        self._client = httpx.AsyncClient(timeout=timeout_s, verify=verify_tls)

    async def aclose(self) -> None:
        await self._client.aclose()

    def _render_path(self, path: str, params: Dict[str, Any]) -> str:
        def repl(m: re.Match[str]) -> str:
            key = m.group(1)
            if key not in params:
                raise ValueError(f"Missing path param: {key}")
            return str(params.pop(key))
        return _PATH_PARAM.sub(repl, path)

    async def call(self, op: OpenAPIOperation, **kwargs: Any) -> Any:
        params = dict(kwargs)
        path = self._render_path(op.path, params)
        url = f"{self.base_url}{path}"

        # heuristics: if method is GET/DELETE -> query params, else JSON body + remaining query if 'query_' prefixed
        headers = self.auth.apply({"Accept": "application/json"})
        json_body: Optional[Dict[str, Any]] = None
        query: Dict[str, Any] = {}

        if op.method in {"GET", "DELETE"}:
            query = params
        else:
            # allow explicit query_ prefix
            query = {k[6:]: v for k, v in list(params.items()) if k.startswith("query_")}
            for k in list(params.keys()):
                if k.startswith("query_"):
                    params.pop(k)
            json_body = params or None
            headers["Content-Type"] = "application/json"

        r = await self._client.request(op.method, url, params=query or None, json=json_body, headers=headers)
        r.raise_for_status()
        ctype = r.headers.get("content-type", "")
        if "application/json" in ctype:
            return r.json()
        return r.text
