"""Speech-to-text via Vosk — converts microphone input to text commands."""

from __future__ import annotations

import io
import json
import sys
import urllib.request
import zipfile
from pathlib import Path

import vosk

# Suppress Vosk's own log messages
vosk.SetLogLevel(-1)

# Default model info
_MODEL_NAME = "vosk-model-small-en-us-0.15"
_MODEL_URL = f"https://alphacephei.com/vosk/models/{_MODEL_NAME}.zip"

SAMPLE_RATE = 16000
CHUNK_SIZE = 4000  # bytes per read (~0.125s at 16kHz 16-bit mono)
SILENCE_TIMEOUT = 10.0  # seconds of total silence before giving up


def ensure_model(model_path: Path) -> Path:
    """Download the Vosk model if it doesn't exist yet."""
    if model_path.exists():
        return model_path
    print(f"Downloading Vosk speech model (~50 MB) …")
    buf = io.BytesIO()
    with urllib.request.urlopen(_MODEL_URL) as resp:
        total = int(resp.headers.get("Content-Length", 0))
        downloaded = 0
        while True:
            chunk = resp.read(1 << 16)
            if not chunk:
                break
            buf.write(chunk)
            downloaded += len(chunk)
            if total:
                pct = downloaded * 100 // total
                print(f"\r  {pct}%", end="", flush=True)
    print("\r  Extracting …    ")
    buf.seek(0)
    with zipfile.ZipFile(buf) as zf:
        zf.extractall(model_path.parent)
    print(f"  Model ready: {model_path}")
    return model_path


class STT:
    """Listens for a spoken command via the microphone and returns text."""

    def __init__(self, model_path: str | Path) -> None:
        model_path = Path(model_path)
        ensure_model(model_path)
        self._model = vosk.Model(str(model_path))

    def listen(self) -> str:
        """Record from the mic until speech + silence, return transcription.

        Returns an empty string if nothing is heard within the timeout.
        """
        try:
            import pyaudio
        except ImportError:
            print(
                "pyaudio is required for speech input. "
                "Install it with: uv pip install pyaudio\n"
                "(You may also need: sudo apt install libportaudio2 portaudio19-dev)",
                file=sys.stderr,
            )
            return ""

        rec = vosk.KaldiRecognizer(self._model, SAMPLE_RATE)
        pa = pyaudio.PyAudio()
        stream = pa.open(
            format=pyaudio.paInt16,
            channels=1,
            rate=SAMPLE_RATE,
            input=True,
            frames_per_buffer=CHUNK_SIZE // 2,  # 16-bit = 2 bytes/sample
        )

        try:
            elapsed = 0.0
            chunk_seconds = (CHUNK_SIZE / 2) / SAMPLE_RATE  # samples per chunk / rate
            while elapsed < SILENCE_TIMEOUT:
                data = stream.read(CHUNK_SIZE // 2, exception_on_overflow=False)
                if rec.AcceptWaveform(data):
                    result = json.loads(rec.Result())
                    text = result.get("text", "").strip()
                    if text:
                        return text
                elapsed += chunk_seconds
        finally:
            stream.stop_stream()
            stream.close()
            pa.terminate()

        # Check partial result on timeout
        result = json.loads(rec.FinalResult())
        return result.get("text", "").strip()
