"""
Unit tests for WhisperProcessor - Local Whisper integration.

Tests audio transcription using faster-whisper library.
Written FIRST following TDD methodology.
"""

import pytest
import os
import sys
from pathlib import Path
from unittest.mock import Mock, patch, MagicMock
import tempfile
import wave
import struct
import math

# Check if faster-whisper is available
try:
    import faster_whisper
    FASTER_WHISPER_AVAILABLE = True
except ImportError:
    FASTER_WHISPER_AVAILABLE = False
    # Create a mock module for patching (only for unit tests in this file)
    # Integration tests should see ImportError naturally
    if 'faster_whisper' not in sys.modules:
        sys.modules['faster_whisper'] = MagicMock()


# Test fixtures for audio file creation
@pytest.fixture
def temp_audio_file():
    """Create a temporary WAV audio file for testing."""
    # Create a simple sine wave audio file (1 second, 16kHz, mono)
    sample_rate = 16000
    duration = 1.0
    frequency = 440.0  # A4 note

    with tempfile.NamedTemporaryFile(suffix='.wav', delete=False) as f:
        temp_path = f.name

    try:
        # Generate audio data
        num_samples = int(sample_rate * duration)
        audio_data = []

        for i in range(num_samples):
            # Generate sine wave
            sample = math.sin(2.0 * math.pi * frequency * i / sample_rate)
            # Convert to 16-bit PCM
            audio_data.append(int(sample * 32767))

        # Write WAV file
        with wave.open(temp_path, 'w') as wav_file:
            wav_file.setnchannels(1)  # Mono
            wav_file.setsampwidth(2)  # 16-bit
            wav_file.setframerate(sample_rate)

            # Pack audio data
            packed_data = struct.pack('<' + 'h' * len(audio_data), *audio_data)
            wav_file.writeframes(packed_data)

        yield temp_path
    finally:
        if os.path.exists(temp_path):
            os.unlink(temp_path)


@pytest.fixture
def mock_whisper_model():
    """Mock faster-whisper WhisperModel."""
    mock_model = MagicMock()

    # Mock transcribe response
    mock_segment = MagicMock()
    mock_segment.start = 0.0
    mock_segment.end = 1.0
    mock_segment.text = "Test transcription"
    mock_segment.avg_logprob = -0.5
    mock_segment.words = []

    mock_info = MagicMock()
    mock_info.language = "en"
    mock_info.language_probability = 0.95
    mock_info.duration = 1.0

    mock_model.transcribe.return_value = ([mock_segment], mock_info)

    return mock_model


class TestWhisperProcessorInitialization:
    """Test WhisperProcessor initialization and configuration."""

    def test_whisper_processor_initialization(self):
        """Test basic initialization with default config."""
        from kaizen.audio.whisper_processor import WhisperProcessor, WhisperConfig

        config = WhisperConfig(model_size="base")
        processor = WhisperProcessor(config)

        assert processor.config.model_size == "base"
        assert processor.config.device == "cpu"
        assert processor.config.compute_type == "int8"
        assert processor.model is None  # Lazy loading

    def test_whisper_processor_invalid_model_size(self):
        """Test initialization with invalid model size raises error."""
        from kaizen.audio.whisper_processor import WhisperProcessor, WhisperConfig

        config = WhisperConfig(model_size="invalid_size")

        with pytest.raises(ValueError, match="Invalid model size"):
            WhisperProcessor(config)

    def test_whisper_processor_custom_config(self):
        """Test initialization with custom configuration."""
        from kaizen.audio.whisper_processor import WhisperProcessor, WhisperConfig

        config = WhisperConfig(
            model_size="small",
            device="cpu",
            compute_type="float32",
            language="en",
            task="translate",
            beam_size=10,
            temperature=0.2
        )
        processor = WhisperProcessor(config)

        assert processor.config.model_size == "small"
        assert processor.config.device == "cpu"
        assert processor.config.compute_type == "float32"
        assert processor.config.language == "en"
        assert processor.config.task == "translate"
        assert processor.config.beam_size == 10
        assert processor.config.temperature == 0.2


class TestWhisperModelLoading:
    """Test Whisper model download and loading."""

    @patch('faster_whisper.WhisperModel')
    def test_model_download_base(self, mock_whisper_model_class):
        """Test downloading and loading base model."""
        from kaizen.audio.whisper_processor import WhisperProcessor, WhisperConfig

        mock_model_instance = MagicMock()
        mock_whisper_model_class.return_value = mock_model_instance

        config = WhisperConfig(model_size="base")
        processor = WhisperProcessor(config)

        # Trigger model loading
        processor._load_model()

        # Verify model was created
        mock_whisper_model_class.assert_called_once_with(
            "base",
            device="cpu",
            compute_type="int8"
        )
        assert processor.model is not None

    @patch('faster_whisper.WhisperModel')
    def test_model_download_small(self, mock_whisper_model_class):
        """Test downloading and loading small model."""
        from kaizen.audio.whisper_processor import WhisperProcessor, WhisperConfig

        mock_model_instance = MagicMock()
        mock_whisper_model_class.return_value = mock_model_instance

        config = WhisperConfig(model_size="small")
        processor = WhisperProcessor(config)
        processor._load_model()

        mock_whisper_model_class.assert_called_once_with(
            "small",
            device="cpu",
            compute_type="int8"
        )

    @patch('faster_whisper.WhisperModel')
    def test_model_lazy_loading(self, mock_whisper_model_class):
        """Test that model is not loaded until needed."""
        from kaizen.audio.whisper_processor import WhisperProcessor, WhisperConfig

        config = WhisperConfig(model_size="base")
        processor = WhisperProcessor(config)

        # Model should not be loaded yet
        assert processor.model is None
        mock_whisper_model_class.assert_not_called()


class TestWhisperTranscription:
    """Test audio transcription functionality."""

    @patch('faster_whisper.WhisperModel')
    def test_transcribe_audio_file(self, mock_whisper_model_class, temp_audio_file):
        """Test basic audio transcription."""
        from kaizen.audio.whisper_processor import WhisperProcessor, WhisperConfig

        # Setup mock
        mock_segment = MagicMock()
        mock_segment.start = 0.0
        mock_segment.end = 1.0
        mock_segment.text = "Hello world"
        mock_segment.avg_logprob = -0.3

        mock_info = MagicMock()
        mock_info.language = "en"
        mock_info.language_probability = 0.98
        mock_info.duration = 1.0

        mock_model = MagicMock()
        mock_model.transcribe.return_value = ([mock_segment], mock_info)
        mock_whisper_model_class.return_value = mock_model

        # Transcribe
        config = WhisperConfig(model_size="base")
        processor = WhisperProcessor(config)
        result = processor.transcribe(temp_audio_file)

        # Verify result
        assert result['text'] == "Hello world"
        assert result['language'] == "en"
        assert result['language_probability'] == 0.98
        assert result['duration'] == 1.0
        assert len(result['segments']) == 1
        assert result['model'] == "base"

    @patch('faster_whisper.WhisperModel')
    def test_transcribe_with_timestamps(self, mock_whisper_model_class, temp_audio_file):
        """Test transcription with word-level timestamps."""
        from kaizen.audio.whisper_processor import WhisperProcessor, WhisperConfig

        # Setup mock with word timestamps
        mock_word1 = MagicMock()
        mock_word1.word = "Hello"
        mock_word1.start = 0.0
        mock_word1.end = 0.5
        mock_word1.probability = 0.95

        mock_word2 = MagicMock()
        mock_word2.word = "world"
        mock_word2.start = 0.5
        mock_word2.end = 1.0
        mock_word2.probability = 0.93

        mock_segment = MagicMock()
        mock_segment.start = 0.0
        mock_segment.end = 1.0
        mock_segment.text = "Hello world"
        mock_segment.avg_logprob = -0.3
        mock_segment.words = [mock_word1, mock_word2]

        mock_info = MagicMock()
        mock_info.language = "en"
        mock_info.language_probability = 0.98
        mock_info.duration = 1.0

        mock_model = MagicMock()
        mock_model.transcribe.return_value = ([mock_segment], mock_info)
        mock_whisper_model_class.return_value = mock_model

        # Transcribe with timestamps
        config = WhisperConfig(model_size="base")
        processor = WhisperProcessor(config)
        result = processor.transcribe(temp_audio_file, word_timestamps=True)

        # Verify word timestamps
        assert len(result['segments']) == 1
        segment = result['segments'][0]
        assert 'words' in segment
        assert len(segment['words']) == 2
        assert segment['words'][0]['word'] == "Hello"
        assert segment['words'][0]['start'] == 0.0
        assert segment['words'][1]['word'] == "world"

    @patch('faster_whisper.WhisperModel')
    def test_transcribe_nonexistent_file(self, mock_whisper_model_class):
        """Test transcription of nonexistent file raises error."""
        from kaizen.audio.whisper_processor import WhisperProcessor, WhisperConfig

        config = WhisperConfig(model_size="base")
        processor = WhisperProcessor(config)

        with pytest.raises(FileNotFoundError):
            processor.transcribe("/nonexistent/audio.wav")


class TestLanguageDetection:
    """Test language detection functionality."""

    @patch('faster_whisper.WhisperModel')
    def test_language_detection(self, mock_whisper_model_class, temp_audio_file):
        """Test automatic language detection."""
        from kaizen.audio.whisper_processor import WhisperProcessor, WhisperConfig

        # Setup mock for French audio
        mock_segment = MagicMock()
        mock_segment.start = 0.0
        mock_segment.end = 1.0
        mock_segment.text = "Bonjour"
        mock_segment.avg_logprob = -0.3

        mock_info = MagicMock()
        mock_info.language = "fr"
        mock_info.language_probability = 0.92
        mock_info.duration = 1.0

        mock_model = MagicMock()
        mock_model.transcribe.return_value = ([mock_segment], mock_info)
        mock_whisper_model_class.return_value = mock_model

        # Detect language
        config = WhisperConfig(model_size="base")
        processor = WhisperProcessor(config)
        result = processor.detect_language(temp_audio_file)

        # Verify detection
        assert result['language'] == "fr"
        assert result['confidence'] == 0.92
        assert result['duration'] == 1.0

    @patch('faster_whisper.WhisperModel')
    def test_translate_to_english(self, mock_whisper_model_class, temp_audio_file):
        """Test translation of non-English audio to English."""
        from kaizen.audio.whisper_processor import WhisperProcessor, WhisperConfig

        # Setup mock for translation
        mock_segment = MagicMock()
        mock_segment.start = 0.0
        mock_segment.end = 1.0
        mock_segment.text = "Hello"  # Translated from French
        mock_segment.avg_logprob = -0.3

        mock_info = MagicMock()
        mock_info.language = "fr"
        mock_info.language_probability = 0.92
        mock_info.duration = 1.0

        mock_model = MagicMock()
        mock_model.transcribe.return_value = ([mock_segment], mock_info)
        mock_whisper_model_class.return_value = mock_model

        # Transcribe with translation
        config = WhisperConfig(model_size="base")
        processor = WhisperProcessor(config)
        result = processor.transcribe(temp_audio_file, task="translate")

        # Verify model was called with translate task
        mock_model.transcribe.assert_called_once()
        call_kwargs = mock_model.transcribe.call_args[1]
        assert call_kwargs['task'] == "translate"


class TestBatchProcessing:
    """Test batch transcription functionality."""

    @patch('faster_whisper.WhisperModel')
    def test_batch_transcription(self, mock_whisper_model_class, temp_audio_file):
        """Test transcription of multiple audio files."""
        from kaizen.audio.whisper_processor import WhisperProcessor, WhisperConfig

        # Setup mock
        mock_segment = MagicMock()
        mock_segment.start = 0.0
        mock_segment.end = 1.0
        mock_segment.text = "Test audio"
        mock_segment.avg_logprob = -0.3

        mock_info = MagicMock()
        mock_info.language = "en"
        mock_info.language_probability = 0.95
        mock_info.duration = 1.0

        mock_model = MagicMock()
        mock_model.transcribe.return_value = ([mock_segment], mock_info)
        mock_whisper_model_class.return_value = mock_model

        # Batch transcribe
        config = WhisperConfig(model_size="base")
        processor = WhisperProcessor(config)

        audio_files = [temp_audio_file, temp_audio_file, temp_audio_file]
        results = processor.transcribe_batch(audio_files)

        # Verify results
        assert len(results) == 3
        for result in results:
            assert result['text'] == "Test audio"
            assert result['language'] == "en"

    @patch('faster_whisper.WhisperModel')
    def test_batch_transcription_with_errors(self, mock_whisper_model_class, temp_audio_file):
        """Test batch transcription handles errors gracefully."""
        from kaizen.audio.whisper_processor import WhisperProcessor, WhisperConfig

        # Setup mock for valid files
        mock_segment = MagicMock()
        mock_segment.start = 0.0
        mock_segment.end = 1.0
        mock_segment.text = "Test audio"
        mock_segment.avg_logprob = -0.3

        mock_info = MagicMock()
        mock_info.language = "en"
        mock_info.language_probability = 0.95
        mock_info.duration = 1.0

        mock_model = MagicMock()
        mock_model.transcribe.return_value = ([mock_segment], mock_info)
        mock_whisper_model_class.return_value = mock_model

        config = WhisperConfig(model_size="base")
        processor = WhisperProcessor(config)

        # Mix valid and invalid files
        audio_files = [temp_audio_file, "/nonexistent/file.wav", temp_audio_file]
        results = processor.transcribe_batch(audio_files)

        # Verify results
        assert len(results) == 3
        assert 'error' not in results[0]
        assert 'error' in results[1]  # Error for nonexistent file
        assert 'error' not in results[2]


class TestConvenienceFunction:
    """Test convenience function for quick transcription."""

    @patch('faster_whisper.WhisperModel')
    def test_transcribe_audio_convenience(self, mock_whisper_model_class, temp_audio_file):
        """Test convenience function for quick transcription."""
        from kaizen.audio.whisper_processor import transcribe_audio

        # Setup mock
        mock_segment = MagicMock()
        mock_segment.start = 0.0
        mock_segment.end = 1.0
        mock_segment.text = "Quick transcription"
        mock_segment.avg_logprob = -0.3

        mock_info = MagicMock()
        mock_info.language = "en"
        mock_info.language_probability = 0.95
        mock_info.duration = 1.0

        mock_model = MagicMock()
        mock_model.transcribe.return_value = ([mock_segment], mock_info)
        mock_whisper_model_class.return_value = mock_model

        # Quick transcribe
        text = transcribe_audio(temp_audio_file, model_size="base")

        assert text == "Quick transcription"


if __name__ == "__main__":
    pytest.main([__file__, "-v"])
