import logging
import os
import threading
import time
from typing import Callable, Optional

import whisper

from app.core.config import (
    AVAILABLE_MODELS,
    WHISPER_DEFAULT_MODEL,
    WHISPER_LANGUAGE,
)

logger = logging.getLogger(__name__)

_model_cache: dict[str, whisper.Whisper] = {}
_cache_lock  = threading.Lock()


def get_model(model_type: str = WHISPER_DEFAULT_MODEL) -> whisper.Whisper:
    if model_type not in AVAILABLE_MODELS:
        raise ValueError(
            f"Model tidak valid: '{model_type}'. Pilihan: {AVAILABLE_MODELS}"
        )

    if model_type in _model_cache:
        return _model_cache[model_type]

    with _cache_lock:
        if model_type not in _model_cache:
            logger.info("[Whisper] Loading model '%s'...", model_type)
            try:
                _model_cache[model_type] = whisper.load_model(model_type)
                logger.info("[Whisper] Model '%s' siap digunakan.", model_type)
            except Exception as exc:
                logger.error("[Whisper] Gagal memuat model '%s': %s", model_type, exc, exc_info=True)
                raise RuntimeError(f"Gagal memuat model Whisper '{model_type}': {exc}") from exc

    return _model_cache[model_type]


class CancelledError(Exception):
    """Dilempar saat task di-cancel oleh admin/user."""
    pass


def transcribe_local(
    file_path:        str,
    model_type:       str = WHISPER_DEFAULT_MODEL,
    segment_callback: Optional[Callable[[list[dict]], None]] = None,
    cancel_check:     Optional[Callable[[], bool]] = None,
) -> dict:
    """
    Transkripsi audio menggunakan Whisper lokal.

    Args:
        file_path:        Path file audio.
        model_type:       Nama model Whisper.
        segment_callback: Dipanggil setiap kali ada segmen baru selesai diproses.
                          Menerima list segmen [{start, end, text}] yang sudah terkumpul.
        cancel_check:     Callable yang mengembalikan True jika task harus dibatalkan.
                          Dicek setelah setiap segmen selesai.

    Returns:
        dict: {text, processing_time, audio_duration, segments}

    Raises:
        CancelledError: Jika cancel_check() mengembalikan True di tengah proses.
    """
    if not file_path:
        raise ValueError("file_path tidak boleh kosong.")
    if not os.path.exists(file_path):
        raise FileNotFoundError(f"File audio tidak ditemukan: '{file_path}'")
    if os.path.getsize(file_path) == 0:
        raise ValueError(f"File audio kosong (0 byte): '{file_path}'")

    model = get_model(model_type)

    logger.info(
        "[Whisper] Mulai transkripsi: file='%s', model='%s', lang='%s'",
        os.path.basename(file_path), model_type, WHISPER_LANGUAGE,
    )

    start          = time.perf_counter()
    collected_segs = []

    # Whisper tidak mendukung streaming native, tapi kita bisa pakai
    # `condition_on_previous_text=True` dan decode per segmen via generator.
    # Untuk implementasi sederhana yang kompatibel semua versi whisper,
    # kita jalankan transcribe() biasa lalu emit segmen satu per satu
    # — hasilnya tetap bisa ditampilkan secara progresif di UI.
    result = model.transcribe(
        file_path,
        language=WHISPER_LANGUAGE,
        verbose=False,
    )

    elapsed = round(time.perf_counter() - start, 2)

    segments = result.get("segments") or []

    # Emit segmen satu-per-satu agar UI bisa update progresif
    for seg in segments:
        # Cek cancel setelah setiap segmen
        if cancel_check and cancel_check():
            logger.info("[Whisper] Task di-cancel saat memproses segmen.")
            raise CancelledError("Task dibatalkan oleh admin.")

        collected_segs.append({
            "start": round(float(seg.get("start", 0)), 2),
            "end":   round(float(seg.get("end", 0)), 2),
            "text":  (seg.get("text") or "").strip(),
        })

        if segment_callback:
            try:
                segment_callback(list(collected_segs))
            except Exception as cb_exc:
                logger.debug("[Whisper] segment_callback error (diabaikan): %s", cb_exc)

    raw_text = result.get("text") or ""
    text     = raw_text.strip() if isinstance(raw_text, str) else ""

    audio_duration = None
    if segments:
        try:
            audio_duration = round(float(segments[-1]["end"]), 2)
        except (KeyError, TypeError, ValueError):
            pass

    logger.info(
        "[Whisper] Transkripsi selesai: %.2f detik, durasi audio: %s detik",
        elapsed, audio_duration,
    )

    return {
        "text":            text,
        "processing_time": elapsed,
        "audio_duration":  audio_duration,
        "segments":        collected_segs,
    }


def unload_model(model_type: str) -> bool:
    with _cache_lock:
        if model_type in _model_cache:
            del _model_cache[model_type]
            logger.info("[Whisper] Model '%s' dihapus dari cache.", model_type)
            return True
    return False


def list_cached_models() -> list[str]:
    with _cache_lock:
        return list(_model_cache.keys())