"""
Speech-to-Text Module for NAVIG

Multi-provider STT with support for Deepgram, OpenAI Whisper,
and local Whisper models.

Usage:
    from navig.voice.stt import transcribe, STT

    # Simple usage
    text = await transcribe("audio.wav")

    # With provider selection
    stt = STT(provider="deepgram")
    result = await stt.transcribe("audio.mp3")
    print(result.text)

Providers:
- deepgram: Real-time & batch, requires DEEPGRAM_API_KEY
- whisper_api: OpenAI Whisper API, requires OPENAI_API_KEY
- whisper_local: Local Whisper model (offline, requires whisper package)
"""

from __future__ import annotations

import asyncio
import mimetypes
import os
from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
from pathlib import Path

from navig.llm_router import PROVIDER_RESOURCE_URLS as _PRUL  # noqa: F401

# =============================================================================
# Types
# =============================================================================


class STTProvider(str, Enum):
    """Supported STT providers."""

    DEEPGRAM = "deepgram"
    WHISPER_API = "whisper_api"
    WHISPER_LOCAL = "whisper_local"


@dataclass
class STTConfig:
    """STT configuration."""

    provider: STTProvider = STTProvider.WHISPER_API
    fallback_providers: list[STTProvider] = field(
        default_factory=lambda: [STTProvider.WHISPER_LOCAL]
    )

    # Language
    language: str = "en"
    detect_language: bool = False

    # Deepgram settings
    deepgram_model: str = "nova-2"
    deepgram_tier: str = "enhanced"
    deepgram_punctuate: bool = True
    deepgram_diarize: bool = False

    # Whisper API settings
    whisper_model: str = "whisper-1"

    # Whisper local settings
    whisper_local_model: str = "base"  # tiny, base, small, medium, large

    # General
    timeout_seconds: int = 60
    max_audio_size_mb: int = 25


@dataclass
class STTResult:
    """Result from STT transcription."""

    success: bool
    text: str | None = None

    # Metadata
    provider: STTProvider | None = None
    language: str | None = None
    confidence: float | None = None
    duration_ms: int | None = None
    latency_ms: int | None = None

    # Segments (if available)
    segments: list[dict] | None = None

    # Error
    error: str | None = None

    def __bool__(self) -> bool:
        return self.success


# =============================================================================
# STT Engine
# =============================================================================


class STT:
    """
    Speech-to-Text engine with multi-provider support.

    Supports Deepgram, OpenAI Whisper API, and local Whisper models
    with automatic fallback.
    """

    def __init__(self, config: STTConfig | None = None):
        self.config = config or STTConfig()

    @staticmethod
    def _resolve_api_key(vault_label: str, env_var: str) -> str | None:
        """Try VaultV2 first, fall back to os.environ.

        This aligns stt.py with the vault-first pattern used in streaming_stt.py.
        A missing vault entry is not fatal — the env-var fallback preserves
        backwards compatibility for deployments that haven't migrated to the vault.
        """
        try:
            from navig.vault import get_vault_v2

            key = get_vault_v2().get_secret(vault_label)
            if key:
                return key
        except Exception:  # noqa: BLE001
            pass  # best-effort; failure is non-critical
        return os.environ.get(env_var)

    async def transcribe(
        self,
        audio_path: str | Path,
        provider: STTProvider | None = None,
        language: str | None = None,
        **kwargs,
    ) -> STTResult:
        """
        Transcribe audio to text.

        Args:
            audio_path: Path to audio file (MP3, WAV, FLAC, etc.)
            provider: Override provider
            language: Override language code
            **kwargs: Provider-specific options

        Returns:
            STTResult with transcribed text or error
        """
        start_time = datetime.utcnow()
        audio_path = Path(audio_path)

        if not audio_path.exists():
            return STTResult(success=False, error=f"Audio file not found: {audio_path}")

        # Check file size
        size_mb = audio_path.stat().st_size / (1024 * 1024)
        if size_mb > self.config.max_audio_size_mb:
            return STTResult(
                success=False,
                error=(
                    f"Audio file too large: {size_mb:.1f}MB (max {self.config.max_audio_size_mb}MB)"
                ),
            )

        lang = language or self.config.language
        providers = (
            [provider]
            if provider
            else [self.config.provider] + self.config.fallback_providers
        )

        last_error = None
        for prov in providers:
            if prov is None:
                continue
            try:
                result = await self._transcribe_with_provider(
                    audio_path, prov, lang, **kwargs
                )
                if result.success:
                    result.latency_ms = int(
                        (datetime.utcnow() - start_time).total_seconds() * 1000
                    )
                    return result
                else:
                    last_error = result.error
            except Exception as e:
                last_error = str(e)
                continue

        return STTResult(
            success=False,
            error=f"All STT providers failed. Last error: {last_error}",
        )

    # =========================================================================
    # Provider Implementations
    # =========================================================================

    async def _transcribe_with_provider(
        self,
        audio_path: Path,
        provider: STTProvider,
        language: str,
        **kwargs,
    ) -> STTResult:
        """Dispatch to provider-specific implementation."""
        if provider == STTProvider.DEEPGRAM:
            return await self._transcribe_deepgram(audio_path, language, **kwargs)
        elif provider == STTProvider.WHISPER_API:
            return await self._transcribe_whisper_api(audio_path, language, **kwargs)
        elif provider == STTProvider.WHISPER_LOCAL:
            return await self._transcribe_whisper_local(audio_path, language, **kwargs)
        else:
            return STTResult(success=False, error=f"Unknown STT provider: {provider}")

    async def _transcribe_deepgram(
        self, audio_path: Path, language: str, **kwargs
    ) -> STTResult:
        """Transcribe using Deepgram API."""
        try:
            import aiohttp
        except ImportError:
            return STTResult(success=False, error="aiohttp not installed")

        api_key = self._resolve_api_key("deepgram/api-key", "DEEPGRAM_API_KEY")
        if not api_key:
            return STTResult(success=False, error="DEEPGRAM_API_KEY not set")

        url = "https://api.deepgram.com/v1/listen"
        params = {
            "model": self.config.deepgram_model,
            "tier": self.config.deepgram_tier,
            "language": language,
            "punctuate": str(self.config.deepgram_punctuate).lower(),
            "diarize": str(self.config.deepgram_diarize).lower(),
        }
        if self.config.detect_language:
            params["detect_language"] = "true"
            params.pop("language", None)

        headers = {
            "Authorization": f"Token {api_key}",
            "Content-Type": self._get_content_type(audio_path),
        }

        try:
            audio_data = audio_path.read_bytes()
            async with (
                aiohttp.ClientSession() as session,
                session.post(
                    url,
                    params=params,
                    headers=headers,
                    data=audio_data,
                    timeout=aiohttp.ClientTimeout(total=self.config.timeout_seconds),
                ) as response,
            ):
                if response.status != 200:
                    error_text = await response.text()
                    return STTResult(
                        success=False,
                        error=f"Deepgram API error {response.status}: {error_text}",
                    )

                data = await response.json()
                channel = data.get("results", {}).get("channels", [{}])[0]
                alt = channel.get("alternatives", [{}])[0]
                text = alt.get("transcript", "")
                confidence = alt.get("confidence", 0.0)
                detected_lang = (
                    data.get("results", {})
                    .get("channels", [{}])[0]
                    .get("detected_language", language)
                )

                return STTResult(
                    success=bool(text),
                    text=text or None,
                    provider=STTProvider.DEEPGRAM,
                    language=detected_lang,
                    confidence=confidence,
                )
        except asyncio.TimeoutError:
            return STTResult(success=False, error="Deepgram transcription timeout")
        except Exception as e:
            return STTResult(success=False, error=f"Deepgram error: {e}")

    async def _transcribe_whisper_api(
        self, audio_path: Path, language: str, **kwargs
    ) -> STTResult:
        """Transcribe using OpenAI Whisper API."""
        try:
            import aiohttp
        except ImportError:
            return STTResult(success=False, error="aiohttp not installed")

        api_key = self._resolve_api_key("openai/api-key", "OPENAI_API_KEY")
        if not api_key:
            return STTResult(success=False, error="OPENAI_API_KEY not set")

        url = "https://api.openai.com/v1/audio/transcriptions"
        headers = {"Authorization": f"Bearer {api_key}"}

        try:
            data = aiohttp.FormData()
            data.add_field(
                "file",
                audio_path.read_bytes(),
                filename=audio_path.name,
                content_type=self._get_content_type(audio_path),
            )
            data.add_field("model", self.config.whisper_model)
            if not self.config.detect_language:
                data.add_field("language", language)
            data.add_field("response_format", "verbose_json")

            async with (
                aiohttp.ClientSession() as session,
                session.post(
                    url,
                    headers=headers,
                    data=data,
                    timeout=aiohttp.ClientTimeout(total=self.config.timeout_seconds),
                ) as response,
            ):
                if response.status != 200:
                    error_text = await response.text()
                    return STTResult(
                        success=False,
                        error=f"Whisper API error {response.status}: {error_text}",
                    )

                result_data = await response.json()
                text = result_data.get("text", "")
                detected_lang = result_data.get("language", language)
                duration = result_data.get("duration")
                segments = result_data.get("segments")

                return STTResult(
                    success=bool(text),
                    text=text or None,
                    provider=STTProvider.WHISPER_API,
                    language=detected_lang,
                    duration_ms=int(duration * 1000) if duration else None,
                    segments=segments,
                )
        except asyncio.TimeoutError:
            return STTResult(success=False, error="Whisper API timeout")
        except Exception as e:
            return STTResult(success=False, error=f"Whisper API error: {e}")

    async def _transcribe_whisper_local(
        self, audio_path: Path, language: str, **kwargs
    ) -> STTResult:
        """Transcribe using local Whisper model (offline)."""
        try:
            import whisper
        except ImportError:
            return STTResult(
                success=False,
                error="whisper not installed. Install with: pip install openai-whisper",
            )

        model_name = kwargs.get("model", self.config.whisper_local_model)

        try:
            loop = asyncio.get_running_loop()

            def _run():
                model = whisper.load_model(model_name)
                result = model.transcribe(
                    str(audio_path),
                    language=None if self.config.detect_language else language,
                )
                return result

            result = await loop.run_in_executor(None, _run)
            text = result.get("text", "").strip()
            detected_lang = result.get("language", language)
            segments = result.get("segments")

            return STTResult(
                success=bool(text),
                text=text or None,
                provider=STTProvider.WHISPER_LOCAL,
                language=detected_lang,
                segments=segments,
            )
        except Exception as e:
            return STTResult(success=False, error=f"Whisper local error: {e}")

    # =========================================================================
    # Helpers
    # =========================================================================

    @staticmethod
    def _get_content_type(path: Path) -> str:
        """Get MIME type for audio file."""
        ext = path.suffix.lower()
        return {
            ".mp3": "audio/mpeg",
            ".wav": "audio/wav",
            ".flac": "audio/flac",
            ".ogg": "audio/ogg",
            ".m4a": "audio/mp4",
            ".webm": "audio/webm",
        }.get(ext, "application/octet-stream")

    async def list_models(self, provider: STTProvider | None = None) -> list[dict]:
        """List available models for a provider."""
        prov = provider or self.config.provider

        if prov == STTProvider.DEEPGRAM:
            return [
                {
                    "id": "nova-2",
                    "name": "Nova 2",
                    "description": "Latest, most accurate",
                },
                {"id": "nova", "name": "Nova", "description": "Fast and accurate"},
                {"id": "enhanced", "name": "Enhanced", "description": "High accuracy"},
                {"id": "base", "name": "Base", "description": "Standard quality"},
            ]
        elif prov == STTProvider.WHISPER_API:
            return [
                {
                    "id": "whisper-1",
                    "name": "Whisper v1",
                    "description": "OpenAI Whisper",
                }
            ]
        elif prov == STTProvider.WHISPER_LOCAL:
            return [
                {"id": "tiny", "name": "Tiny", "description": "39M params, fastest"},
                {"id": "base", "name": "Base", "description": "74M params"},
                {"id": "small", "name": "Small", "description": "244M params"},
                {"id": "medium", "name": "Medium", "description": "769M params"},
                {
                    "id": "large",
                    "name": "Large",
                    "description": "1550M params, most accurate",
                },
            ]
        return []


# =============================================================================
# Module-level convenience
# =============================================================================

_default_stt: STT | None = None


def _resolve_audio_file_params(
    filename: str, *, is_voice: bool = False
) -> tuple[str, str]:
    """Return a normalized upload filename and MIME type for an audio file.

    Voice messages are normalized to Telegram's preferred ``.oga`` filename
    while preserving the Ogg MIME type.
    """
    path = Path(filename)

    if is_voice:
        return "voice.oga", "audio/ogg"

    mime, _ = mimetypes.guess_type(str(path))
    if mime:
        return path.name, mime

    mime = STT._get_content_type(path)
    return path.name, mime


def get_stt() -> STT:
    """Get default STT instance."""
    global _default_stt
    if _default_stt is None:
        _default_stt = STT()
    return _default_stt


async def transcribe(audio_path: str | Path, **kwargs) -> str | None:
    """Transcribe audio file to text. Returns None on failure."""
    result = await get_stt().transcribe(audio_path, **kwargs)
    return result.text if result.success else None


async def transcribe_full(audio_path: str | Path, **kwargs) -> STTResult:
    """Transcribe audio and return full result."""
    return await get_stt().transcribe(audio_path, **kwargs)


def transcribe_sync(audio_path: str | Path, **kwargs) -> str | None:
    """Synchronous wrapper for transcribe()."""
    return asyncio.run(transcribe(audio_path, **kwargs))
