"""TranscriptionRouter — the single object every consumer talks to.

Mirrors the full public TranscriptionService surface so app.py can inject it
in place of the service without any caller changing. It routes batch work
(dictation, file conversion, meetings) to a remote VoxCast Server when remote
mode is enabled, and keeps latency-critical live windowing local.

Live dictation over the network uses a dedicated streaming session (see
app.py + remote_transcription), NOT this router's transcribe_window_sync, so
that path stays local here.
"""
from __future__ import annotations

import numpy as np
from loguru import logger

from .remote_transcription import RemoteTranscriptionBackend
from .transcription_service import TranscriptionService


class TranscriptionRouter:
    def __init__(self, local: TranscriptionService, signals) -> None:
        self._local = local
        self._signals = signals
        self._remote = RemoteTranscriptionBackend(signals)
        self._remote_enabled = False

    # ── remote configuration ──────────────────────────────────────────────

    def set_remote(self, enabled: bool, url: str, api_key: str,
                   tls_mode: str = "verify", cert_pem: str = "") -> None:
        self._remote_enabled = bool(enabled) and bool(url)
        if url:
            self._remote.configure(url, api_key, tls_mode, cert_pem)
        logger.info("Remote transcription {}", "enabled" if self._remote_enabled else "disabled")

    @property
    def remote_active(self) -> bool:
        return self._remote_enabled and self._remote.configured

    @property
    def remote_backend(self) -> RemoteTranscriptionBackend:
        return self._remote

    @property
    def local(self) -> TranscriptionService:
        return self._local

    async def refresh_remote(self) -> dict | None:
        return await self._remote.get_status(force=True)

    # ── batch routing ─────────────────────────────────────────────────────

    async def transcribe(self, audio: np.ndarray) -> str:
        text, _ = await self.transcribe_with_language(audio)
        return text

    async def transcribe_with_language(
        self, audio: np.ndarray,
    ) -> tuple[str, tuple[str, float] | None]:
        if self.remote_active:
            return await self._remote.transcribe_with_language(audio)
        return await self._local.transcribe_with_language(audio)

    async def transcribe_file(
        self, path, *, language=None, with_timestamps=False,
        on_progress=None, multilingual=False,
    ) -> tuple[str, dict]:
        if self.remote_active:
            return await self._remote.transcribe_file(
                path, language=language, with_timestamps=with_timestamps,
                on_progress=on_progress, multilingual=multilingual)
        return await self._local.transcribe_file(
            path, language=language, with_timestamps=with_timestamps,
            on_progress=on_progress, multilingual=multilingual)

    def transcribe_window_sync(self, audio, language):
        # Latency-critical live path: always local.
        return self._local.transcribe_window_sync(audio, language)

    # ── readiness / info ──────────────────────────────────────────────────

    @property
    def is_ready(self) -> bool:
        """Whether transcription can serve a request now. For remote mode this
        is optimistic (configured + not-known-unready): an actually-down server
        surfaces as an honest error on the call itself, never a silent local
        fallback."""
        if self.remote_active:
            return self._remote.maybe_ready
        return self._local.is_ready

    @property
    def live_capable(self) -> bool:
        """Live captions need a LOCAL model window-by-window (or a remote WS
        session driven by app.py). This reports local engine readiness; the
        app decides local vs. remote live from remote_active."""
        return self._local.is_ready

    @property
    def device(self) -> str:
        if self.remote_active and self._remote._status_cache:
            return str(self._remote._status_cache.get("device", "remote"))
        return self._local.device

    @property
    def model_name(self) -> str:
        if self.remote_active and self._remote._status_cache:
            return str(self._remote._status_cache.get("model", ""))
        return self._local.model_name

    @property
    def fell_back_to_cpu(self) -> bool:
        if self.remote_active:
            return False
        return self._local.fell_back_to_cpu

    # ── lifecycle / config (delegate to the local engine) ─────────────────

    def set_model(self, name: str) -> None:
        self._local.set_model(name)

    def set_compute_device(self, device: str) -> None:
        self._local.set_compute_device(device)

    def set_language(self, lang) -> None:
        self._local.set_language(lang)

    async def start(self) -> None:
        await self._local.start()

    async def stop(self) -> None:
        await self._local.stop()
