"""
STT Benchmarking Script

This script benchmarks different Speech-to-Text (STT) handlers to compare their performance.
Measures: inference time, warmup time, memory usage, and transcription quality.

Usage:
    python benchmark_stt.py --audio_file path/to/audio.wav --iterations 10
    python benchmark_stt.py --audio_file path/to/audio.wav --handlers whisper mlx-audio-whisper
"""

import argparse
import json
import logging
import time
from pathlib import Path
from queue import Queue
from threading import Event
from typing import Any, Dict, List, Optional

import numpy as np
import soundfile as sf

from speech_to_speech.pipeline.messages import VADAudio

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
)
logger = logging.getLogger(__name__)


class BenchmarkResult:
    """Stores benchmark results for a single STT handler."""

    def __init__(self, handler_name: str):
        self.handler_name = handler_name
        self.warmup_time = 0.0
        self.inference_times: list[float] = []
        self.time_to_first_token: list[float] = []
        self.transcriptions: list[str] = []
        self.errors: list[str] = []

    def add_inference(self, time_taken: float, transcription: Any, ttft: Optional[float] = None):
        self.inference_times.append(time_taken)
        self.transcriptions.append(transcription)
        if ttft is not None:
            self.time_to_first_token.append(ttft)

    def add_error(self, error: str):
        self.errors.append(error)

    def get_stats(self) -> Dict[str, Any]:
        """Calculate statistics from benchmark results."""
        if not self.inference_times:
            return {
                "handler": self.handler_name,
                "status": "failed",
                "errors": self.errors,
            }

        stats = {
            "handler": self.handler_name,
            "warmup_time": self.warmup_time,
            "avg_inference_time": np.mean(self.inference_times),
            "min_inference_time": np.min(self.inference_times),
            "max_inference_time": np.max(self.inference_times),
            "std_inference_time": np.std(self.inference_times),
            "total_iterations": len(self.inference_times),
            "errors": self.errors,
            "sample_transcription": self.transcriptions[0] if self.transcriptions else None,
        }

        # Add time to first token stats if available
        if self.time_to_first_token:
            stats["avg_time_to_first_token"] = np.mean(self.time_to_first_token)
            stats["min_time_to_first_token"] = np.min(self.time_to_first_token)
            stats["max_time_to_first_token"] = np.max(self.time_to_first_token)
            stats["std_time_to_first_token"] = np.std(self.time_to_first_token)

        return stats


def load_audio(audio_path: str) -> np.ndarray:
    """Load audio file and return as numpy array."""
    logger.info(f"Loading audio from: {audio_path}")
    audio, sample_rate = sf.read(audio_path)

    # Convert to mono if stereo
    if len(audio.shape) > 1:
        audio = audio.mean(axis=1)

    # Resample to 16kHz if needed (most whisper models expect 16kHz)
    if sample_rate != 16000:
        logger.warning(f"Audio sample rate is {sample_rate}Hz, resampling to 16000Hz")
        try:
            import librosa
            audio = librosa.resample(audio, orig_sr=sample_rate, target_sr=16000)
        except ImportError:
            logger.error("librosa not installed. Please install it with: pip install librosa")
            logger.error("Attempting scipy resampling as fallback...")
            from scipy import signal
            # Calculate resampling ratio
            num_samples = int(len(audio) * 16000 / sample_rate)
            audio = signal.resample(audio, num_samples)

    return audio.astype(np.float32)


def benchmark_handler(
    handler_name: str,
    audio: np.ndarray,
    iterations: int,
    handler_kwargs: Optional[Dict[str, Any]] = None
) -> BenchmarkResult:
    """Benchmark a single STT handler."""
    logger.info(f"Benchmarking {handler_name}...")
    result = BenchmarkResult(handler_name)

    try:
        # Create queues and events for handler
        stop_event = Event()
        queue_in: Queue[Any] = Queue()
        queue_out: Queue[Any] = Queue()

        handler: Any = None
        if handler_name == "whisper":
            from speech_to_speech.STT.whisper_stt_handler import WhisperSTTHandler
            setup_kwargs = handler_kwargs or {
                "model_name": "distil-whisper/distil-large-v3",
                "device": "cuda",
                "torch_dtype": "float16",
            }
            handler = WhisperSTTHandler(
                stop_event,
                queue_in=queue_in,
                queue_out=queue_out,
                setup_kwargs=setup_kwargs
            )

        elif handler_name == "whisper-mlx":
            from speech_to_speech.STT.lightning_whisper_mlx_handler import LightningWhisperSTTHandler
            setup_kwargs = handler_kwargs or {
                "model_name": "large-v3",
                "device": "mps",
            }
            handler = LightningWhisperSTTHandler(
                stop_event,
                queue_in=queue_in,
                queue_out=queue_out,
                setup_kwargs=setup_kwargs
            )

        elif handler_name == "mlx-audio-whisper":
            from speech_to_speech.STT.mlx_audio_whisper_handler import MLXAudioWhisperSTTHandler
            setup_kwargs = handler_kwargs or {
                "model_name": "mlx-community/whisper-large-v3-turbo",
            }
            handler = MLXAudioWhisperSTTHandler(
                stop_event,
                queue_in=queue_in,
                queue_out=queue_out,
                setup_kwargs=setup_kwargs
            )

        elif handler_name == "faster-whisper":
            from speech_to_speech.STT.faster_whisper_handler import FasterWhisperSTTHandler
            setup_kwargs = handler_kwargs or {
                "model_name": "large-v3",
                "device": "auto",
                "compute_type": "float16",
            }
            handler = FasterWhisperSTTHandler(
                stop_event,
                queue_in=queue_in,
                queue_out=queue_out,
                setup_kwargs=setup_kwargs
            )

        elif handler_name == "moonshine":
            from archive.STT.moonshine_handler import MoonshineSTTHandler
            handler = MoonshineSTTHandler(
                stop_event,
                queue_in=queue_in,
                queue_out=queue_out,
            )

        elif handler_name == "parakeet-tdt":
            from speech_to_speech.STT.parakeet_tdt_handler import ParakeetTDTSTTHandler
            setup_kwargs = handler_kwargs or {
                "device": "mps",
                "enable_live_transcription": False,
            }
            handler = ParakeetTDTSTTHandler(
                stop_event,
                queue_in=queue_in,
                queue_out=queue_out,
                setup_kwargs=setup_kwargs
            )

        elif handler_name == "parakeet-tdt-progressive":
            from speech_to_speech.STT.parakeet_tdt_handler import ParakeetTDTSTTHandler
            setup_kwargs = handler_kwargs or {
                "device": "mps",
                "enable_live_transcription": True,
                "live_transcription_update_interval": 0.25,
            }
            handler = ParakeetTDTSTTHandler(
                stop_event,
                queue_in=queue_in,
                queue_out=queue_out,
                setup_kwargs=setup_kwargs
            )
        else:
            raise ValueError(f"Unknown handler: {handler_name}")

        # Warmup is done in handler setup
        logger.info(f"Handler {handler_name} initialized and warmed up")

        # Additional warmup on the actual audio (excluded from timings)
        for _ in handler.process(VADAudio(audio=audio)):
            pass

        # Run benchmark iterations
        for i in range(iterations):
            logger.info(f"Iteration {i+1}/{iterations} for {handler_name}")

            start_time = time.perf_counter()

            # Process audio
            transcription = None
            time_to_first_token = None
            first_output = True

            for output in handler.process(VADAudio(audio=audio)):
                # Measure time to first token
                if first_output:
                    time_to_first_token = time.perf_counter() - start_time
                    first_output = False

                if isinstance(output, tuple):
                    transcription = output[0]  # (text, language)
                else:
                    transcription = output

            end_time = time.perf_counter()

            time_taken = end_time - start_time
            result.add_inference(time_taken, transcription, time_to_first_token)

            ttft_str = f", TTFT: {time_to_first_token:.4f}s" if time_to_first_token else ""
            text_preview = str(transcription)[:50] if transcription is not None else "(none)"
            logger.info(f"  Time: {time_taken:.4f}s{ttft_str}, Text: {text_preview}...")

        # Cleanup
        handler.cleanup()
        stop_event.set()

    except Exception as e:
        logger.error(f"Error benchmarking {handler_name}: {e}", exc_info=True)
        result.add_error(str(e))

    return result


def print_results(results: List[BenchmarkResult]):
    """Print benchmark results in a formatted table."""
    print("\n" + "="*80)
    print("BENCHMARK RESULTS")
    print("="*80)

    for result in results:
        stats = result.get_stats()
        print(f"\nHandler: {stats['handler']}")
        print("-" * 80)

        if stats.get("status") == "failed":
            print("  Status: FAILED")
            print(f"  Errors: {stats['errors']}")
            continue

        print(f"  Warmup Time:          {stats['warmup_time']:.4f}s")
        print(f"  Avg Inference Time:   {stats['avg_inference_time']:.4f}s")
        print(f"  Min Inference Time:   {stats['min_inference_time']:.4f}s")
        print(f"  Max Inference Time:   {stats['max_inference_time']:.4f}s")
        print(f"  Std Deviation:        {stats['std_inference_time']:.4f}s")

        # Print time to first token stats if available
        if 'avg_time_to_first_token' in stats:
            print("\n  Time to First Token:")
            print(f"    Avg TTFT:           {stats['avg_time_to_first_token']:.4f}s")
            print(f"    Min TTFT:           {stats['min_time_to_first_token']:.4f}s")
            print(f"    Max TTFT:           {stats['max_time_to_first_token']:.4f}s")
            print(f"    Std TTFT:           {stats['std_time_to_first_token']:.4f}s")

        print(f"\n  Total Iterations:     {stats['total_iterations']}")
        print(f"  Sample Transcription: {stats['sample_transcription']}")

        if stats['errors']:
            print(f"  Errors: {stats['errors']}")

    # Comparison table
    print("\n" + "="*80)
    print("COMPARISON (Average Inference Time)")
    print("="*80)

    successful_results = [r for r in results if r.inference_times]
    if successful_results:
        sorted_results = sorted(successful_results, key=lambda x: np.mean(x.inference_times))

        fastest = sorted_results[0]
        fastest_time = np.mean(fastest.inference_times)

        for result in sorted_results:
            avg_time = np.mean(result.inference_times)
            speedup = avg_time / fastest_time
            print(f"  {result.handler_name:25s}: {avg_time:.4f}s  ({speedup:.2f}x slower than fastest)")


def save_results(results: List[BenchmarkResult], output_file: str):
    """Save benchmark results to JSON file."""
    data = {
        "results": [r.get_stats() for r in results],
        "timestamp": time.strftime("%Y-%m-%d %H:%M:%S"),
    }

    with open(output_file, 'w') as f:
        json.dump(data, f, indent=2)

    logger.info(f"Results saved to: {output_file}")


def main():
    parser = argparse.ArgumentParser(description="Benchmark STT handlers")
    parser.add_argument(
        "--audio_file",
        type=str,
        required=True,
        help="Path to audio file for benchmarking"
    )
    parser.add_argument(
        "--handlers",
        nargs="+",
        default=["whisper", "whisper-mlx", "mlx-audio-whisper", "faster-whisper", "parakeet-tdt", "parakeet-tdt-progressive"],
        help="List of handlers to benchmark (default: all)"
    )
    parser.add_argument(
        "--iterations",
        type=int,
        default=5,
        help="Number of iterations per handler (default: 5)"
    )
    parser.add_argument(
        "--output",
        type=str,
        default="stt_benchmark_results.json",
        help="Output JSON file for results (default: stt_benchmark_results.json)"
    )

    args = parser.parse_args()

    # Validate audio file exists
    if not Path(args.audio_file).exists():
        logger.error(f"Audio file not found: {args.audio_file}")
        return

    # Load audio
    audio = load_audio(args.audio_file)
    logger.info(f"Audio loaded: {len(audio)} samples, {len(audio)/16000:.2f}s duration")

    # Run benchmarks
    results = []
    for handler_name in args.handlers:
        result = benchmark_handler(handler_name, audio, args.iterations)
        results.append(result)

    # Print and save results
    print_results(results)
    save_results(results, args.output)

    logger.info("Benchmarking complete!")


if __name__ == "__main__":
    main()
