"""HTTP hooks server for external service integration."""

import asyncio
import json
import uuid

from aiohttp import web
from loguru import logger

from nanobot.bus.events import InboundMessage
from nanobot.bus.queue import MessageBus
from nanobot.config.schema import HooksConfig


class HooksServer:
    """
    HTTP server exposing a /hooks endpoint.

    External services POST JSON messages. The server publishes them
    to the bus as InboundMessages and uses bus-level correlation
    to return the agent's response synchronously.
    """

    def __init__(
        self,
        host: str,
        port: int,
        config: HooksConfig,
        bus: MessageBus,
    ):
        self.host = host
        self.port = port
        self.config = config
        self.bus = bus
        self._app = web.Application()
        self._app.router.add_post(self.config.path, self._handle_hook)
        self._app.router.add_get("/health", self._handle_health)
        self._runner: web.AppRunner | None = None

    async def start(self) -> None:
        """Start the HTTP server."""
        if not self.config.has_tokens:
            logger.warning("Hooks server has no tokens configured — endpoint disabled for security")
            return

        self._runner = web.AppRunner(self._app)
        await self._runner.setup()
        site = web.TCPSite(self._runner, self.host, self.port)
        await site.start()
        logger.info(f"Hooks server listening on {self.host}:{self.port}{self.config.path}")

    async def stop(self) -> None:
        """Stop the HTTP server."""
        if self._runner:
            await self._runner.cleanup()
            self._runner = None

    def _resolve_auth(self, request: web.Request) -> str | None:
        """
        Validate auth and return token name if valid, None otherwise.
        Checks Authorization: Bearer <token> and X-Hook-Token headers.
        """
        # Try Authorization: Bearer <token>
        auth = request.headers.get("Authorization", "")
        if auth.startswith("Bearer "):
            token = auth[7:]
        else:
            # Try X-Hook-Token header
            token = request.headers.get("X-Hook-Token", "")

        return self.config.resolve_token(token) if token else None

    async def _handle_health(self, request: web.Request) -> web.Response:
        """Health check endpoint — no auth required."""
        return web.json_response({"status": "ok"})

    async def _handle_hook(self, request: web.Request) -> web.Response:
        """Handle incoming hook request."""
        # Auth check — resolve token name
        token_name = self._resolve_auth(request)
        if not token_name:
            return web.json_response({"error": "unauthorized"}, status=401)

        # Parse body
        try:
            body = await request.json()
        except (json.JSONDecodeError, Exception):
            return web.json_response({"error": "invalid JSON body"}, status=400)

        # Validate required fields
        message = body.get("message")
        if not message or not isinstance(message, str):
            return web.json_response(
                {"error": "missing or invalid 'message' field"}, status=400
            )

        # Optional fields
        channel = body.get("channel", "hook")
        chat_id = body.get("chat_id", token_name)
        timeout = body.get("timeout", self.config.timeout_seconds)

        # Create correlation
        correlation_id = str(uuid.uuid4())

        # Build InboundMessage
        msg = InboundMessage(
            channel=channel,
            sender_id=f"hook:{token_name}",
            chat_id=str(chat_id),
            content=message,
            metadata={
                "correlation_id": correlation_id,
                "hook_source": token_name,
            },
        )

        # Fire-and-forget mode
        if timeout == 0:
            await self.bus.publish_inbound(msg)
            return web.json_response({"ok": True}, status=202)

        # Request-response mode
        future = self.bus.register_correlation(correlation_id)
        await self.bus.publish_inbound(msg)

        try:
            response = await asyncio.wait_for(future, timeout=timeout)
            return web.json_response({"ok": True, "response": response})
        except asyncio.TimeoutError:
            return web.json_response(
                {"ok": False, "error": f"agent did not respond within {timeout}s"},
                status=504,
            )
        except Exception as e:
            logger.error(f"Hook processing error: {e}")
            return web.json_response({"error": "internal error"}, status=500)
        finally:
            # Clean up correlation on any failure
            self.bus.cancel_correlation(correlation_id)
