"""
Azure Bot Service — Direct Line 3.0 Connector.

Implements the three-step Direct Line protocol:
  1. POST /conversations          → start conversation, get conversationId
  2. POST /conversations/{id}/activities  → send user message
  3. GET  /conversations/{id}/activities?watermark=N  → poll for bot reply

Tokens are refreshed automatically before they expire (every ~25 min).
"""

import asyncio
import os
import time
from datetime import UTC, datetime
from typing import Any

import httpx

from src.connectors.base import BaseConnector
from src.utils.helpers import generate_uuid
from src.utils.logging import get_logger

logger = get_logger(__name__)

DEFAULT_DL_ENDPOINT = "https://directline.botframework.com/v3/directline"
TOKEN_REFRESH_MARGIN_S = 300  # refresh 5 min before expiry
TOKEN_LIFETIME_S = 1800  # Direct Line tokens live ~30 min


class AzureDirectLineConnector(BaseConnector):
    """Connector for Azure Bot Service via the Direct Line 3.0 REST API."""

    def __init__(self, config: dict[str, Any]):
        super().__init__(config)
        self.client: httpx.AsyncClient | None = None

        conn = self.connection_config
        self.endpoint = conn.get("endpoint", DEFAULT_DL_ENDPOINT).rstrip("/")
        self.timeout_seconds = conn.get("timeout", 30)
        self.poll_interval = conn.get("poll_interval", 0.8)
        self.poll_timeout = conn.get("poll_timeout", 20)

        secret_env = conn.get("direct_line_secret_env", "AZURE_DL_SECRET")
        self.secret = os.getenv(secret_env, "")
        if not self.secret:
            raise ValueError(
                f"Azure Direct Line secret not found. "
                f"Set the env var '{secret_env}' or provide "
                f"connection.direct_line_secret_env in the config."
            )

        self.conversation_id: str | None = None
        self.watermark: str | None = None
        self._token: str | None = None
        self._token_obtained_at: float = 0.0
        self._from_user = f"penbot-{generate_uuid()[:8]}"

    # ------------------------------------------------------------------
    # Lifecycle
    # ------------------------------------------------------------------

    async def initialize(self) -> None:
        """Create HTTP client and start a Direct Line conversation."""
        self._token = self.secret
        self._token_obtained_at = time.monotonic()

        self.client = httpx.AsyncClient(
            timeout=httpx.Timeout(float(self.timeout_seconds), connect=10.0),
        )

        resp = await self.client.post(
            f"{self.endpoint}/conversations",
            headers=self._auth_headers(),
        )
        resp.raise_for_status()
        data = resp.json()
        self.conversation_id = data["conversationId"]
        if "token" in data:
            self._token = data["token"]
            self._token_obtained_at = time.monotonic()
        self.watermark = None

        logger.info(
            "azure_dl_initialized",
            conversation_id=self.conversation_id,
        )

    async def close(self) -> None:
        """Close the HTTP client."""
        if self.client:
            await self.client.aclose()
            self.client = None

    # ------------------------------------------------------------------
    # Token management
    # ------------------------------------------------------------------

    def _auth_headers(self) -> dict[str, str]:
        return {
            "Authorization": f"Bearer {self._token}",
            "Content-Type": "application/json",
        }

    async def _maybe_refresh_token(self) -> None:
        """Refresh the Direct Line token if close to expiry."""
        elapsed = time.monotonic() - self._token_obtained_at
        if elapsed < TOKEN_LIFETIME_S - TOKEN_REFRESH_MARGIN_S:
            return

        if not self.client:
            return

        try:
            resp = await self.client.post(
                f"{self.endpoint}/tokens/refresh",
                headers=self._auth_headers(),
            )
            resp.raise_for_status()
            data = resp.json()
            self._token = data["token"]
            self._token_obtained_at = time.monotonic()
            logger.info("azure_dl_token_refreshed")
        except Exception as exc:
            logger.warning("azure_dl_token_refresh_failed", error=str(exc))

    # ------------------------------------------------------------------
    # Core messaging
    # ------------------------------------------------------------------

    async def send_message(
        self,
        message: str,
        context: dict | None = None,
        image_data: str | None = None,
        image_mime_type: str | None = None,
    ) -> dict[str, Any]:
        """Send a message and poll for the bot's reply."""
        if not self.client or not self.conversation_id:
            await self.initialize()

        await self._maybe_refresh_token()

        activity = {
            "type": "message",
            "from": {"id": self._from_user},
            "text": message,
        }

        start = datetime.now(UTC)

        post_resp = await self.client.post(
            f"{self.endpoint}/conversations/{self.conversation_id}/activities",
            headers=self._auth_headers(),
            json=activity,
        )
        post_resp.raise_for_status()

        bot_text = await self._poll_for_reply()

        latency_ms = (datetime.now(UTC) - start).total_seconds() * 1000

        return {
            "content": bot_text,
            "timestamp": datetime.now(UTC),
            "metadata": {
                "conversation_id": self.conversation_id,
                "watermark": self.watermark,
                "latency_ms": latency_ms,
            },
        }

    async def _poll_for_reply(self) -> str:
        """
        Poll the activities endpoint until we see a bot reply
        (an activity whose ``from.id`` differs from ours).
        """
        deadline = time.monotonic() + self.poll_timeout

        while time.monotonic() < deadline:
            params: dict[str, str] = {}
            if self.watermark:
                params["watermark"] = self.watermark

            resp = await self.client.get(
                f"{self.endpoint}/conversations/{self.conversation_id}/activities",
                headers=self._auth_headers(),
                params=params,
            )
            resp.raise_for_status()
            data = resp.json()

            self.watermark = data.get("watermark", self.watermark)

            for act in data.get("activities", []):
                sender = act.get("from", {}).get("id", "")
                if sender != self._from_user and act.get("type") == "message":
                    return act.get("text", "")

            await asyncio.sleep(self.poll_interval)

        return "[No response from Azure bot within timeout]"

    # ------------------------------------------------------------------
    # Health check
    # ------------------------------------------------------------------

    async def health_check(self) -> bool:
        """Verify Direct Line connectivity."""
        try:
            if not self.client:
                await self.initialize()
            resp = await self.client.post(
                f"{self.endpoint}/tokens/refresh",
                headers=self._auth_headers(),
            )
            return resp.status_code == 200
        except Exception:
            return False
