"""
DVGatewayClient — Main SDK Entry Point

Wires together all internal components and exposes a clean,
type-safe public API for integrating with the DVGateway.

Example:
    gw = DVGatewayClient(
        base_url="https://gateway.example.com",
        auth=ApiKeyAuth(api_key=os.environ["DV_API_KEY"]),
    )

    # High-level: 5-line AI voice bot
    await (
        gw.pipeline()
        .stt(DeepgramAdapter(api_key="..."))
        .llm(AnthropicAdapter(api_key="..."))
        .tts(ElevenLabsAdapter(api_key="..."))
        .start()
    )
"""

from __future__ import annotations

from typing import AsyncIterable, Awaitable, Callable, Literal

from dvgateway.auth.manager import AuthManager
from dvgateway.minutes.manager import MinutesManager
from dvgateway.observability.logger import create_logger
from dvgateway.observability.metrics import PipelineMetrics
from dvgateway.pipeline.builder import PipelineBuilder
from dvgateway.session.manager import SessionManager
from dvgateway.streams.audio_stream import AudioStream, create_audio_stream
from dvgateway.streams.call_events import CallEventStream
from dvgateway.streams.tts_stream import TtsInjector
from dvgateway.transport.http_client import HttpClient
from dvgateway.transport.ws_pool import WsPool
from dvgateway.types import (
    AudioChunk,
    CallEvent,
    CallSession,
    DVGatewayOptions,
    Logger,
    StreamDir,
    TranscriptResult,
    TtsAdapter,
    Unsubscribe,
)


class DVGatewayClient:
    """Main SDK entry point for the DVGateway real-time voice media gateway."""

    def __init__(
        self,
        base_url: str = "http://localhost:8080",
        auth: dict[str, str] | None = None,
        tenant_id: str | None = None,
        timeout: int = 30_000,
        reconnect: dict[str, object] | None = None,
        security: dict[str, object] | None = None,
        logger: Logger | None = None,
        *,
        options: DVGatewayOptions | None = None,
    ) -> None:
        # Support both dict-style and DVGatewayOptions
        if options:
            opts = options
        else:
            from dvgateway.types import ApiKeyAuth, JwtAuth, ReconnectOptions, SecurityOptions

            # Parse auth
            auth_dict = auth or {"type": "apiKey", "api_key": "dev-no-auth"}
            if auth_dict.get("type") == "jwt":
                parsed_auth = JwtAuth(token=auth_dict.get("token", ""))
            else:
                parsed_auth = ApiKeyAuth(api_key=auth_dict.get("api_key", "dev-no-auth"))

            # Parse reconnect
            recon_dict = reconnect or {}
            parsed_reconnect = ReconnectOptions(
                max_attempts=int(recon_dict.get("max_attempts", recon_dict.get("maxAttempts", 10))),  # type: ignore[arg-type]
                initial_delay_ms=int(recon_dict.get("initial_delay_ms", recon_dict.get("initialDelayMs", 2000))),  # type: ignore[arg-type]
                on_reconnect=recon_dict.get("on_reconnect", recon_dict.get("onReconnect")),  # type: ignore[arg-type]
            )

            # Parse security
            sec_dict = security or {}
            parsed_security = SecurityOptions(
                mask_pii=bool(sec_dict.get("mask_pii", sec_dict.get("maskPii", True))),
                force_tls=bool(sec_dict.get("force_tls", sec_dict.get("forceTls", True))),
            )

            opts = DVGatewayOptions(
                base_url=base_url,
                auth=parsed_auth,
                tenant_id=tenant_id,
                timeout=timeout,
                reconnect=parsed_reconnect,
                security=parsed_security,
                logger=logger,
            )

        # Enforce TLS in production
        normalized_url = self._normalize_base_url(opts)

        self._logger: Logger = opts.logger or create_logger(
            level="info",
            redact_pii=opts.security.mask_pii,
        )

        self._auth = AuthManager(opts.auth, normalized_url, self._logger)
        self._ws_pool = WsPool(self._auth, opts.reconnect, self._logger)
        self._http = HttpClient(self._auth, opts.timeout, self._logger, opts.tenant_id)
        self._call_events = CallEventStream(self._ws_pool, self._logger)
        self._tts_injector = TtsInjector(self._ws_pool, self._http, self._logger)
        self._sessions = SessionManager(self._http, self._logger)
        self._minutes = MinutesManager(self._http, self._logger)
        self.metrics = PipelineMetrics(self._logger)

    # ─── High-Level: Pipeline Builder ──────────────────────────────────────

    def pipeline(self) -> PipelineBuilder:
        """
        Create a fluent AI voice pipeline.

        Example:
            await (
                gw.pipeline()
                .stt(DeepgramAdapter(api_key="..."))
                .llm(AnthropicAdapter(api_key="..."))
                .tts(ElevenLabsAdapter(api_key="..."))
                .on_new_call(lambda s: print(f"Call: {s.linked_id}"))
                .start()
            )
        """
        return PipelineBuilder(self._ws_pool, self._call_events, self._tts_injector, self._logger)

    # ─── Mid-Level: Audio Streaming ─────────────────────────────────────────

    def stream_audio(
        self,
        linked_id: str,
        dir: StreamDir = "both",
    ) -> AudioStream:
        """
        Subscribe to real-time audio from a call.
        Returns an AsyncIterable that yields AudioChunk (Float32, 16kHz).
        """
        return create_audio_stream(self._ws_pool, linked_id, self._logger, dir)

    # ─── Mid-Level: TTS Injection ───────────────────────────────────────────

    async def inject_tts(self, linked_id: str, audio_chunks: AsyncIterable[bytes]) -> None:
        """Inject synthesized speech into an active call."""
        await self._tts_injector.inject_streaming(linked_id, audio_chunks)

    async def broadcast_tts(self, conf_id: str, audio_chunks: AsyncIterable[bytes]) -> None:
        """Broadcast synthesized speech to all conference participants."""
        await self._tts_injector.broadcast_to_conference(conf_id, audio_chunks)

    async def say(self, linked_id: str, text: str, tts: TtsAdapter) -> None:
        """
        Convenience: synthesize text and inject it directly.

        Tip: Pass a CachedTtsAdapter to avoid redundant API calls
        for repeated announcements (e.g. IVR greetings).

        Example::

            from dvgateway.adapters.tts import ElevenLabsAdapter, CachedTtsAdapter

            tts = CachedTtsAdapter(
                ElevenLabsAdapter(api_key="..."),
                provider="elevenlabs", cache_dir="./tts-cache",
            )
            await tts.warmup([{"text": "안녕하세요"}])
            await gw.say(linked_id, "안녕하세요", tts)  # cache hit
        """
        await self._tts_injector.inject_streaming(linked_id, tts.synthesize(text))

    async def broadcast_say(self, conf_id: str, text: str, tts: TtsAdapter) -> None:
        """Broadcast synthesized speech to a conference.

        Pass a CachedTtsAdapter for zero-cost repeated broadcasts.
        """
        await self._tts_injector.broadcast_to_conference(conf_id, tts.synthesize(text))

    # ─── Comfort Noise / Thinking Signals ─────────────────────────────────

    async def start_thinking(self, linked_id: str) -> None:
        """
        Notify the gateway that AI processing has started (STT → LLM → TTS).
        If comfort noise is enabled (GW_COMFORT_NOISE_ENABLED=true),
        low-level background audio will be injected into the call to prevent dead air.

        Can also be sent via the audio stream WebSocket:
            await audio_stream.send_thinking_start()

        Or via REST API:
            POST /api/v1/comfort/{linkedId}/start
        """
        await self._http.post(f"/api/v1/comfort/{linked_id}/start", {})
        self._logger.debug({"linked_id": linked_id}, "Thinking signal: start")

    async def stop_thinking(self, linked_id: str) -> None:
        """
        Notify the gateway that AI processing has completed.
        Comfort noise injection will fade out and stop.
        """
        await self._http.post(f"/api/v1/comfort/{linked_id}/stop", {})
        self._logger.debug({"linked_id": linked_id}, "Thinking signal: stop")

    # ─── Mid-Level: Call Events ─────────────────────────────────────────────

    def on_call_event(
        self,
        handler: Callable[[CallEvent], None | Awaitable[None]],
    ) -> Unsubscribe:
        """Subscribe to all call lifecycle events. Returns an unsubscribe function."""
        return self._call_events.on(handler)

    def on(
        self,
        event_type: str,
        handler: Callable[..., None | Awaitable[None]],
    ) -> Unsubscribe:
        """Subscribe to a specific call event type."""
        return self._call_events.on_type(event_type, handler)

    # ─── Session Management ─────────────────────────────────────────────────

    async def list_sessions(self) -> list[CallSession]:
        """List all currently active call sessions."""
        return await self._sessions.list_sessions()

    async def list_sessions_by_tenant(self, tenant_id: str) -> list[CallSession]:
        """List active call sessions filtered by tenant ID."""
        return await self._sessions.list_sessions_by_tenant(tenant_id)

    async def update_session_meta(self, linked_id: str, meta: dict[str, str]) -> None:
        """Update metadata on a session."""
        await self._sessions.update_metadata(linked_id, meta)

    # ─── Minutes / Transcripts ──────────────────────────────────────────────

    async def submit_transcript(self, conf_id: str, result: TranscriptResult) -> None:
        """Submit an STT transcript result to the gateway's minutes store."""
        await self._minutes.submit_transcript(conf_id, result)

    async def download_minutes(
        self,
        conf_id: str,
        format: Literal["json", "txt"] = "txt",
    ) -> str:
        """Download conference minutes in JSON or plain-text format."""
        return await self._minutes.download_minutes(conf_id, format)

    def auto_submit_transcripts(
        self,
        conf_id: str,
    ) -> Callable[[TranscriptResult], Awaitable[None]]:
        """Returns a function that auto-submits final transcripts to minutes."""
        return self._minutes.create_auto_submitter(conf_id)

    # ─── Lifecycle ──────────────────────────────────────────────────────────

    def close(self) -> None:
        """Gracefully close all WebSocket connections and release resources."""
        self._ws_pool.close_all()
        self._logger.info({}, "DVGatewayClient closed")

    # ─── Private helpers ────────────────────────────────────────────────────

    def _normalize_base_url(self, opts: DVGatewayOptions) -> str:
        url = opts.base_url.rstrip("/")
        if opts.security.force_tls:
            if url.startswith("http://") and not self._is_local(url):
                self._logger: Logger = opts.logger or create_logger(level="info")  # type: ignore[assignment]
                url = url.replace("http://", "https://", 1)
        return url

    @staticmethod
    def _is_local(url: str) -> bool:
        """Return True if the URL points to a loopback address."""
        from urllib.parse import urlparse

        host = urlparse(url).hostname or ""
        return host in ("localhost", "127.0.0.1", "::1") or host.endswith(".local")
