"""
AISTALK Adapter — HTTP bridge integration.

Enables forwarding BFF events/messages to an external AISTALK bridge service.
The adapter is best-effort and non-blocking for callers.
"""
from __future__ import annotations

import logging
import os
import threading
import time
from concurrent.futures import ThreadPoolExecutor
from typing import Any, Dict, List, Optional

import httpx

logger = logging.getLogger(__name__)


def _split_paths(raw: str, default: str) -> List[str]:
    src = (raw or "").strip()
    if not src:
        src = default
    parts = [p.strip() for p in src.split(",") if p.strip()]
    normalized: List[str] = []
    for p in parts:
        if not p.startswith("/"):
            p = "/" + p
        normalized.append(p)
    return normalized


class AISTALKAdapter:
    """
    AISTALK relay adapter.

    Env overrides (optional):
      AISTALK_HEALTH_PATHS=/healthz,/health,/api/health
      AISTALK_EVENT_PATHS=/api/events,/events,/v1/events
      AISTALK_TEXT_PATHS=/api/text,/text,/v1/text
      AISTALK_AUDIO_PATHS=/api/audio,/audio,/v1/audio
    """

    def __init__(self, base_url: str, api_key: Optional[str] = None) -> None:
        self.base_url = base_url.rstrip("/") if base_url else ""
        self.api_key = api_key or ""
        self._enabled = bool(self.base_url)

        self._health_paths = _split_paths(
            os.getenv("AISTALK_HEALTH_PATHS", ""),
            "/healthz,/health,/api/health",
        )
        self._event_paths = _split_paths(
            os.getenv("AISTALK_EVENT_PATHS", ""),
            "/api/events,/events,/v1/events",
        )
        self._text_paths = _split_paths(
            os.getenv("AISTALK_TEXT_PATHS", ""),
            "/api/text,/text,/v1/text",
        )
        self._audio_paths = _split_paths(
            os.getenv("AISTALK_AUDIO_PATHS", ""),
            "/api/audio,/audio,/v1/audio",
        )

        self._lock = threading.Lock()
        self._last_ok_at: Optional[float] = None
        self._last_error: str = ""
        self._last_endpoint: str = ""
        self._last_probe_ok: Optional[bool] = None
        self._last_probe_at: Optional[float] = None

        # Fire-and-forget outbound queue to avoid adding latency to BFF handlers.
        self._pool = ThreadPoolExecutor(max_workers=2, thread_name_prefix="aistalk-relay")

        if self._enabled:
            logger.info("AISTALKAdapter init: url=%s (HTTP relay mode)", self.base_url)
        else:
            logger.info("AISTALKAdapter init: no base_url, adapter disabled")

    @property
    def enabled(self) -> bool:
        return self._enabled

    def _headers(self) -> Dict[str, str]:
        headers = {"Content-Type": "application/json"}
        if self.api_key:
            headers["Authorization"] = f"Bearer {self.api_key}"
            headers["X-API-Key"] = self.api_key
        return headers

    def _mark_ok(self, endpoint: str) -> None:
        with self._lock:
            self._last_ok_at = time.time()
            self._last_error = ""
            self._last_endpoint = endpoint

    def _mark_err(self, err: str) -> None:
        with self._lock:
            self._last_error = (err or "")[:300]

    def _post_json(self, payload: Dict[str, Any], paths: List[str], kind: str) -> bool:
        if not self._enabled:
            return False
        last_err = "unreachable"
        timeout = httpx.Timeout(connect=0.6, read=1.8, write=1.8, pool=0.6)
        for path in paths:
            endpoint = f"{self.base_url}{path}"
            try:
                with httpx.Client(timeout=timeout) as client:
                    r = client.post(endpoint, headers=self._headers(), json=payload)
                if 200 <= r.status_code < 300:
                    self._mark_ok(endpoint)
                    return True
                last_err = f"HTTP {r.status_code} @ {path}"
            except Exception as e:
                last_err = f"{e.__class__.__name__}: {str(e)[:180]} @ {path}"
                continue
        self._mark_err(last_err)
        logger.debug("AISTALK %s relay failed: %s", kind, last_err)
        return False

    def _post_audio(self, payload: Dict[str, Any], audio_bytes: bytes, mime: str) -> bool:
        if not self._enabled:
            return False
        last_err = "unreachable"
        timeout = httpx.Timeout(connect=0.8, read=2.5, write=2.5, pool=0.8)
        for path in self._audio_paths:
            endpoint = f"{self.base_url}{path}"
            files = {"audio": ("chunk", audio_bytes, mime or "audio/wav")}
            data = {"meta": str(payload)}
            try:
                with httpx.Client(timeout=timeout) as client:
                    headers = {}
                    if self.api_key:
                        headers["Authorization"] = f"Bearer {self.api_key}"
                        headers["X-API-Key"] = self.api_key
                    r = client.post(endpoint, headers=headers, data=data, files=files)
                if 200 <= r.status_code < 300:
                    self._mark_ok(endpoint)
                    return True
                last_err = f"HTTP {r.status_code} @ {path}"
            except Exception as e:
                last_err = f"{e.__class__.__name__}: {str(e)[:180]} @ {path}"
                continue
        self._mark_err(last_err)
        logger.debug("AISTALK audio relay failed: %s", last_err)
        return False

    def _dispatch(self, fn, *args: Any) -> None:
        if not self._enabled:
            return
        try:
            self._pool.submit(fn, *args)
        except Exception as e:
            self._mark_err(str(e))
            logger.debug("AISTALK dispatch failed: %s", e)

    def send_text(
        self,
        project_id: str,
        session_id: str,
        text: str,
        user_id: str = "console_user",
    ) -> None:
        if not self._enabled:
            return
        payload = {
            "v": 1,
            "type": "chat.reply",
            "project_id": project_id,
            "session_id": session_id,
            "user_id": user_id,
            "data": {"text": text},
        }
        self._dispatch(self._post_json, payload, self._text_paths, "text")

    def send_audio(
        self,
        project_id: str,
        session_id: str,
        audio_bytes: bytes,
        mime: str = "audio/wav",
    ) -> None:
        if not self._enabled:
            return
        payload = {
            "v": 1,
            "type": "voice.tts",
            "project_id": project_id,
            "session_id": session_id,
            "user_id": "console_user",
            "data": {"mime": mime, "bytes": len(audio_bytes)},
        }
        self._dispatch(self._post_audio, payload, audio_bytes, mime)

    def handle_event(self, event: Dict[str, Any]) -> None:
        if not self._enabled:
            return
        self._dispatch(self._post_json, event, self._event_paths, "event")

    def on_event(self, event: Dict[str, Any]) -> None:
        self.handle_event(event)

    def probe_health(self) -> Dict[str, Any]:
        if not self._enabled:
            return {"enabled": False, "ok": False, "error": "disabled"}
        timeout = httpx.Timeout(connect=0.5, read=1.2, write=1.2, pool=0.5)
        last_err = "unreachable"
        for path in self._health_paths:
            endpoint = f"{self.base_url}{path}"
            try:
                with httpx.Client(timeout=timeout) as client:
                    headers = {}
                    if self.api_key:
                        headers["Authorization"] = f"Bearer {self.api_key}"
                        headers["X-API-Key"] = self.api_key
                    r = client.get(endpoint, headers=headers)
                if r.status_code < 500:
                    with self._lock:
                        self._last_probe_ok = r.status_code == 200
                        self._last_probe_at = time.time()
                    if r.status_code == 200:
                        self._mark_ok(endpoint)
                        return {"enabled": True, "ok": True, "url": endpoint, "status": r.status_code}
                    last_err = f"HTTP {r.status_code} @ {path}"
                else:
                    last_err = f"HTTP {r.status_code} @ {path}"
            except Exception as e:
                last_err = f"{e.__class__.__name__}: {str(e)[:180]} @ {path}"
                continue
        with self._lock:
            self._last_probe_ok = False
            self._last_probe_at = time.time()
        self._mark_err(last_err)
        return {"enabled": True, "ok": False, "error": last_err}

    def status(self) -> Dict[str, Any]:
        with self._lock:
            return {
                "enabled": self._enabled,
                "base_url": self.base_url,
                "last_ok_at": self._last_ok_at,
                "last_endpoint": self._last_endpoint,
                "last_error": self._last_error,
                "last_probe_ok": self._last_probe_ok,
                "last_probe_at": self._last_probe_at,
                "paths": {
                    "health": self._health_paths,
                    "events": self._event_paths,
                    "text": self._text_paths,
                    "audio": self._audio_paths,
                },
            }

    def __repr__(self) -> str:
        s = self.status()
        return (
            f"AISTALKAdapter(url={s['base_url']!r}, enabled={s['enabled']}, "
            f"last_probe_ok={s['last_probe_ok']}, last_endpoint={s['last_endpoint']!r})"
        )
