import os
import struct
import time
import wave
import pytest
import coremusic as cm
import coremusic.capi as capi


class TestAudioFileOperations:
    """Test audio file operations"""

    @pytest.fixture
    def amen_wav_path(self):
        """Fixture providing path to amen.wav test file"""
        path = os.path.join("tests", "amen.wav")
        if not os.path.exists(path):
            pytest.skip(f"Test audio file not found: {path}")
        return path

    def test_audio_file_open_close(self, amen_wav_path):
        """Test opening and closing audio files"""
        audio_file_id = capi.audio_file_open_url(
            amen_wav_path,
            capi.get_audio_file_read_permission(),
            capi.get_audio_file_wave_type(),
        )
        assert audio_file_id is not None
        capi.audio_file_close(audio_file_id)

    def test_audio_file_properties(self, amen_wav_path):
        """Test reading audio file properties"""
        audio_file_id = capi.audio_file_open_url(
            amen_wav_path,
            capi.get_audio_file_read_permission(),
            capi.get_audio_file_wave_type(),
        )
        try:
            format_data = capi.audio_file_get_property(
                audio_file_id, capi.get_audio_file_property_data_format()
            )
            assert len(format_data) >= 40
            asbd = struct.unpack("<dLLLLLLLL", format_data[:40])
            (
                sample_rate,
                format_id,
                format_flags,
                bytes_per_packet,
                frames_per_packet,
                bytes_per_frame,
                channels_per_frame,
                bits_per_channel,
                reserved,
            ) = asbd
            assert sample_rate > 0
            assert channels_per_frame > 0
            assert bits_per_channel > 0
            assert format_id == capi.get_audio_format_linear_pcm()
        finally:
            capi.audio_file_close(audio_file_id)

    def test_audio_file_packet_reading(self, amen_wav_path):
        """Test reading audio packets from file"""
        audio_file_id = capi.audio_file_open_url(
            amen_wav_path,
            capi.get_audio_file_read_permission(),
            capi.get_audio_file_wave_type(),
        )
        try:
            packet_data, packets_read = capi.audio_file_read_packets(
                audio_file_id, 0, 100
            )
            assert packets_read > 0
            assert len(packet_data) > 0
            assert isinstance(packet_data, bytes)
        finally:
            capi.audio_file_close(audio_file_id)

    def test_wav_file_format_detection(self, amen_wav_path):
        """Test WAV file format detection using Python wave module"""
        with wave.open(amen_wav_path, "rb") as wav:
            sample_rate = wav.getframerate()
            channels = wav.getnchannels()
            sample_width = wav.getsampwidth()
            frame_count = wav.getnframes()
            assert sample_rate > 0
            assert channels > 0
            assert sample_width > 0
            assert frame_count > 0
            duration = frame_count / sample_rate
            assert 0.1 < duration < 60


class TestAudioPlayerIntegration:
    """Test integrated audio player functionality"""

    @pytest.fixture
    def amen_wav_path(self):
        """Fixture providing path to amen.wav test file"""
        path = os.path.join("tests", "amen.wav")
        if not os.path.exists(path):
            pytest.skip(f"Test audio file not found: {path}")
        return path

    def test_audio_file_loading_and_analysis(self, amen_wav_path):
        """Test loading and analyzing audio file with both Python and CoreAudio"""
        with wave.open(amen_wav_path, "rb") as wav:
            python_format = {
                "sample_rate": wav.getframerate(),
                "channels": wav.getnchannels(),
                "sample_width": wav.getsampwidth(),
                "frame_count": wav.getnframes(),
                "duration": wav.getnframes() / wav.getframerate(),
            }
            audio_data = wav.readframes(wav.getnframes())
        assert python_format["sample_rate"] > 0
        assert python_format["channels"] > 0
        assert python_format["sample_width"] > 0
        assert python_format["frame_count"] > 0
        assert len(audio_data) > 0
        audio_file_id = capi.audio_file_open_url(
            amen_wav_path,
            capi.get_audio_file_read_permission(),
            capi.get_audio_file_wave_type(),
        )
        try:
            format_data = capi.audio_file_get_property(
                audio_file_id, capi.get_audio_file_property_data_format()
            )
            if len(format_data) >= 40:
                asbd = struct.unpack("<dLLLLLLLL", format_data[:40])
                coreaudio_format = {
                    "sample_rate": asbd[0],
                    "channels": asbd[6],
                    "bits_per_channel": asbd[7],
                }
                assert (
                    abs(coreaudio_format["sample_rate"] - python_format["sample_rate"])
                    < 1
                )
                assert coreaudio_format["channels"] == python_format["channels"]
                assert (
                    coreaudio_format["bits_per_channel"]
                    == python_format["sample_width"] * 8
                )
            packet_data, packets_read = capi.audio_file_read_packets(
                audio_file_id, 0, 1000
            )
            assert packets_read > 0
            assert len(packet_data) > 0
        finally:
            capi.audio_file_close(audio_file_id)

    def test_complete_audio_pipeline(self, amen_wav_path):
        """Test complete audio processing pipeline"""
        with wave.open(amen_wav_path, "rb") as wav:
            format_info = {
                "sample_rate": float(wav.getframerate()),
                "channels": wav.getnchannels(),
                "sample_width": wav.getsampwidth(),
                "frame_count": wav.getnframes(),
            }
            audio_data = wav.readframes(wav.getnframes())
        assert len(audio_data) > 0
        assert format_info["sample_rate"] > 0
        audio_file_id = capi.audio_file_open_url(
            amen_wav_path,
            capi.get_audio_file_read_permission(),
            capi.get_audio_file_wave_type(),
        )
        assert audio_file_id is not None
        capi.audio_file_close(audio_file_id)
        description = {
            "type": capi.get_audio_unit_type_output(),
            "subtype": capi.get_audio_unit_subtype_default_output(),
            "manufacturer": capi.get_audio_unit_manufacturer_apple(),
            "flags": 0,
            "flags_mask": 0,
        }
        component_id = capi.audio_component_find_next(description)
        assert component_id is not None
        audio_unit = capi.audio_component_instance_new(component_id)
        assert audio_unit is not None
        try:
            capi.audio_unit_initialize(audio_unit)
            capi.audio_output_unit_start(audio_unit)
            time.sleep(0.1)
            capi.audio_output_unit_stop(audio_unit)
            capi.audio_unit_uninitialize(audio_unit)
        finally:
            capi.audio_component_instance_dispose(audio_unit)
        audio_format = {
            "sample_rate": format_info["sample_rate"],
            "format_id": capi.get_audio_format_linear_pcm(),
            "format_flags": capi.get_linear_pcm_format_flag_is_signed_integer()
            | capi.get_linear_pcm_format_flag_is_packed(),
            "bytes_per_packet": format_info["channels"] * format_info["sample_width"],
            "frames_per_packet": 1,
            "bytes_per_frame": format_info["channels"] * format_info["sample_width"],
            "channels_per_frame": format_info["channels"],
            "bits_per_channel": format_info["sample_width"] * 8,
        }
        queue_id = capi.audio_queue_new_output(audio_format)
        assert queue_id is not None
        try:
            buffer_id = capi.audio_queue_allocate_buffer(queue_id, 8192)
            assert buffer_id is not None
        finally:
            capi.audio_queue_dispose(queue_id, True)


class TestErrorHandling:
    """Test error handling and edge cases"""

    def test_invalid_file_handling(self):
        """Test handling of invalid file paths"""
        with pytest.raises(Exception):
            capi.audio_file_open_url(
                "nonexistent_file.wav",
                capi.get_audio_file_read_permission(),
                capi.get_audio_file_wave_type(),
            )


class TestPerformance:
    """Test performance characteristics"""

    def test_audio_file_loading_performance(self):
        """Test that audio file loading is reasonably fast"""
        amen_path = os.path.join("tests", "amen.wav")
        if not os.path.exists(amen_path):
            pytest.skip(f"Test audio file not found: {amen_path}")
        start_time = time.time()
        with wave.open(amen_path, "rb") as wav:
            audio_data = wav.readframes(wav.getnframes())
        python_time = time.time() - start_time
        start_time = time.time()
        audio_file_id = capi.audio_file_open_url(
            amen_path,
            capi.get_audio_file_read_permission(),
            capi.get_audio_file_wave_type(),
        )
        try:
            packet_data, packets_read = capi.audio_file_read_packets(
                audio_file_id, 0, 1000
            )
        finally:
            capi.audio_file_close(audio_file_id)
        coreaudio_time = time.time() - start_time
        assert python_time < 1.0
        assert coreaudio_time < 1.0
        assert len(audio_data) > 0
        assert len(packet_data) > 0
