"""
Tests unitaires pour la capture audio
"""

import asyncio
import pytest
from unittest.mock import Mock, patch, MagicMock
import struct

from asr.audio_capture import AudioCapture


class TestAudioCapture:
    """Tests pour la capture audio."""

    @pytest.fixture
    def mock_pyaudio(self):
        """Mock de PyAudio."""
        pyaudio_mock = Mock()
        pyaudio_instance = Mock()
        stream_mock = Mock()

        pyaudio_mock.PyAudio.return_value = pyaudio_instance
        pyaudio_instance.get_default_input_device_info.return_value = {
            "name": "Test Microphone"
        }
        pyaudio_instance.open.return_value = stream_mock

        # Constantes PyAudio
        pyaudio_mock.paInt16 = 8
        pyaudio_mock.paContinue = 0

        return pyaudio_mock, pyaudio_instance, stream_mock

    @pytest.fixture
    def audio_capture(self):
        """Instance de capture audio pour les tests."""
        return AudioCapture(sample_rate=16000, channels=1, chunk_size=1024)

    @pytest.mark.asyncio
    async def test_start_with_pyaudio(self, audio_capture, mock_pyaudio):
        """Test le démarrage avec PyAudio disponible."""
        pyaudio_mock, pyaudio_instance, stream_mock = mock_pyaudio

        with patch("asr.audio_capture.pyaudio", pyaudio_mock):
            await audio_capture.start()

            assert audio_capture.is_running is True
            assert audio_capture.pyaudio_instance == pyaudio_instance
            assert audio_capture.audio_stream == stream_mock

            pyaudio_instance.open.assert_called_once()
            stream_mock.start_stream.assert_called_once()

    @pytest.mark.asyncio
    async def test_start_without_pyaudio(self, audio_capture):
        """Test le démarrage sans PyAudio (mode mock)."""
        with patch("asr.audio_capture.pyaudio", None):
            await audio_capture.start()

            assert audio_capture.is_running is True
            assert audio_capture.pyaudio_instance is None

    @pytest.mark.asyncio
    async def test_get_audio_chunk(self, audio_capture):
        """Test la récupération de chunks audio."""
        await audio_capture.start()

        # Simuler des données audio dans la queue
        test_data = b"\x00\x01" * 512
        await audio_capture.audio_queue.put(test_data)

        chunk = await audio_capture.get_audio_chunk()
        assert chunk == test_data

    @pytest.mark.asyncio
    async def test_get_audio_chunk_timeout(self, audio_capture):
        """Test le timeout lors de la récupération audio."""
        await audio_capture.start()

        # Aucune donnée dans la queue
        chunk = await audio_capture.get_audio_chunk()
        assert chunk is None

    def test_audio_level_calculation(self, audio_capture):
        """Test le calcul du niveau audio RMS."""
        # Créer des données audio avec des échantillons connus
        samples = [1000, -1000, 2000, -2000] * 256  # 1024 échantillons
        audio_data = struct.pack(f"{len(samples)}h", *samples)

        level = audio_capture.get_audio_level(audio_data)

        assert 0.0 <= level <= 1.0
        assert level > 0.0  # Doit détecter du signal

    def test_audio_level_silence(self, audio_capture):
        """Test le calcul du niveau pour le silence."""
        silence = b"\x00\x00" * 512  # 1024 bytes de silence
        level = audio_capture.get_audio_level(silence)

        assert level == 0.0

    def test_audio_callback(self, audio_capture):
        """Test le callback audio de PyAudio."""
        # Démarrer la capture pour initialiser la queue
        asyncio.run(audio_capture.start())

        test_data = b"\x00\x01" * 512

        # Simuler l'appel du callback
        result = audio_capture._audio_callback(test_data, 1024, None, None)

        # Vérifier que le callback retourne la bonne valeur
        assert result[1] == 0  # paContinue (mocké)

        # Vérifier que les données sont dans la queue
        assert not audio_capture.audio_queue.empty()

    @pytest.mark.asyncio
    async def test_audio_stream_generator(self, audio_capture):
        """Test le générateur de flux audio."""
        await audio_capture.start()

        # Ajouter quelques chunks à la queue
        test_chunks = [b"\x00\x01" * 512, b"\x01\x00" * 512, b"\x00\x02" * 512]
        for chunk in test_chunks:
            await audio_capture.audio_queue.put(chunk)

        # Récupérer les chunks via le générateur
        collected_chunks = []
        async for chunk in audio_capture.get_audio_stream():
            collected_chunks.append(chunk)
            if len(collected_chunks) >= len(test_chunks):
                break

        assert len(collected_chunks) == len(test_chunks)

    @pytest.mark.asyncio
    async def test_stop(self, audio_capture, mock_pyaudio):
        """Test l'arrêt de la capture."""
        pyaudio_mock, pyaudio_instance, stream_mock = mock_pyaudio

        with patch("asr.audio_capture.pyaudio", pyaudio_mock):
            await audio_capture.start()
            await audio_capture.stop()

            assert audio_capture.is_running is False
            stream_mock.stop_stream.assert_called_once()
            stream_mock.close.assert_called_once()
            pyaudio_instance.terminate.assert_called_once()

    def test_is_available(self):
        """Test la vérification de disponibilité."""
        # Avec PyAudio
        with patch("asr.audio_capture.pyaudio", Mock()):
            capture = AudioCapture()
            assert capture.is_available() is True

        # Sans PyAudio
        with patch("asr.audio_capture.pyaudio", None):
            capture = AudioCapture()
            assert capture.is_available() is False
