"""Generic HTTP evaluation channel.

Supports any REST API that follows a configurable request/response template.
Configuration keys (all under channel_config):

Required:
  send_url       URL to POST the message to (supports {message} template var)
  reply_url      URL to GET the reply from (supports {msg_id} template var)

Optional:
  health_url     URL for health check GET (defaults to send_url)
  headers        dict of extra request headers (e.g. Authorization)
  send_body      JSON body template for POST; {message} is substituted
                 Default: {"text": "{message}"}
  msg_id_path    dot-separated path to extract msg_id from send response
                 Default: "id"
  reply_path     dot-separated path to extract reply text from reply response
                 Default: "text"
  reply_ready_path  dot-separated path to a boolean indicating reply is ready
                 Default: (treat any 200 as ready)
  poll_interval  seconds between polls (default 1.0)
  timeout        seconds before poll gives up (default 30.0)
"""

import asyncio
import json
import uuid
from typing import Any, Optional

import httpx

from agenteval.channels.base import ChannelHealth, ChannelTransportError, EvalChannel, Reply, SendResult


def _get_path(data: Any, path: str) -> Any:
    """Traverse a dot-separated key path into a nested dict/list."""
    parts = path.split(".")
    current = data
    for part in parts:
        if current is None:
            return None
        if isinstance(current, dict):
            current = current.get(part)
        elif isinstance(current, list) and part.isdigit():
            idx = int(part)
            current = current[idx] if idx < len(current) else None
        else:
            return None
    return current


class HttpChannel(EvalChannel):
    """Generic HTTP channel: POST to send, GET/POST to poll for reply."""

    def __init__(self, config: dict[str, Any]):
        self.send_url: str = config["send_url"]
        self.reply_url: str = config.get("reply_url", "")
        self.health_url: str = config.get("health_url", self.send_url)
        self.headers: dict[str, str] = config.get("headers", {})
        self.send_body_template: str = config.get("send_body", '{"text": "{message}"}')
        self.msg_id_path: str = config.get("msg_id_path", "id")
        self.reply_path: str = config.get("reply_path", "text")
        self.reply_ready_path: Optional[str] = config.get("reply_ready_path")
        self._poll_interval: float = float(config.get("poll_interval", 1.0))

        self._client = httpx.AsyncClient(headers=self.headers, timeout=30)

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

    async def health_check(self) -> ChannelHealth:
        try:
            resp = await self._client.get(self.health_url)
            resp.raise_for_status()
            return ChannelHealth(ok=True, message=f"HTTP {resp.status_code}")
        except Exception as exc:
            return ChannelHealth(ok=False, message=str(exc))

    async def _send(self, content: str, **kwargs: Any) -> SendResult:
        body_str = self.send_body_template.replace("{message}", content)
        try:
            body = json.loads(body_str)
        except json.JSONDecodeError:
            body = {"text": content}

        url = self.send_url.replace("{message}", content)
        try:
            resp = await self._client.post(url, json=body)
            resp.raise_for_status()
            data = resp.json()
            msg_id = str(_get_path(data, self.msg_id_path) or uuid.uuid4())
            return SendResult(ok=True, question_msg_id=msg_id, raw_response=data)
        except (httpx.HTTPError, json.JSONDecodeError) as exc:
            return SendResult(ok=False, error=str(exc))

    async def _poll_reply(
        self,
        question_msg_id: str,
        timeout: float = 30.0,
        poll_interval: float = 1.0,
    ) -> Optional[Reply]:
        interval = poll_interval or self._poll_interval
        deadline = asyncio.get_event_loop().time() + timeout
        url = self.reply_url.replace("{msg_id}", question_msg_id)

        while asyncio.get_event_loop().time() < deadline:
            try:
                resp = await self._client.get(url)
                resp.raise_for_status()
                data = resp.json()

                # Check if reply is ready (optional readiness flag)
                if self.reply_ready_path:
                    ready = _get_path(data, self.reply_ready_path)
                    if not ready:
                        await asyncio.sleep(interval)
                        continue

                reply_text = _get_path(data, self.reply_path)
                if reply_text is not None:
                    raw = {"text": str(reply_text), "_raw": data}
                    return Reply(
                        question_msg_id=question_msg_id,
                        content=str(reply_text),
                        raw_message=raw,
                    )
            except (httpx.HTTPError, json.JSONDecodeError) as exc:
                raise ChannelTransportError(str(exc)) from exc

            await asyncio.sleep(interval)

        return None
