"""Comprehensive tests for the object-oriented coremusic API functionality."""
import pytest
import os
import coremusic as cm
import coremusic.capi as capi


class TestObjectOrientedAPIFunctionality:
    """Test that the OO API is fully functional"""

    def test_audio_format_functionality(self):
        """Test AudioFormat class functionality"""
        format = cm.AudioFormat(44100.0, 'lpcm', channels_per_frame=2,
            bits_per_channel=16)
        assert format.is_pcm
        assert format.is_stereo
        assert not format.is_mono
        assert format.sample_rate == 44100.0
        assert format.format_id == 'lpcm'
        assert format.channels_per_frame == 2
        assert format.bits_per_channel == 16
        repr_str = repr(format)
        assert 'AudioFormat' in repr_str
        assert '44100.0' in repr_str

    def test_exception_hierarchy_functionality(self):
        """Test exception hierarchy functionality"""
        try:
            raise cm.CoreAudioError('Test error', 42)
        except cm.CoreAudioError as e:
            assert str(e) == 'Test error'
            assert e.status_code == 42
        with pytest.raises(cm.AudioFileError):
            raise cm.AudioFileError('File error')
        with pytest.raises(cm.CoreAudioError):
            raise cm.MIDIError('MIDI error')

    def test_core_audio_object_functionality(self):
        """Test CoreAudioObject base functionality"""
        obj = cm.CoreAudioObject()
        assert not obj.is_disposed
        obj.dispose()
        assert obj.is_disposed
        with pytest.raises(RuntimeError, match='has been disposed'):
            obj._ensure_not_disposed()

    @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_functionality(self, amen_wav_path):
        """Test AudioFile functionality with real file"""
        with cm.AudioFile(amen_wav_path) as audio_file:
            assert isinstance(audio_file, cm.AudioFile)
            assert not audio_file.is_disposed
            format = audio_file.format
            assert isinstance(format, cm.AudioFormat)
            data, packet_count = audio_file.read_packets(0, 10)
            assert isinstance(data, bytes)
            assert isinstance(packet_count, int)
            assert len(data) > 0
        assert audio_file.is_disposed

    def test_audio_file_stream_functionality(self):
        """Test AudioFileStream functionality"""
        stream = cm.AudioFileStream()
        stream.open()
        assert not stream.is_disposed
        ready = stream.ready_to_produce_packets
        assert isinstance(ready, bool)
        stream.close()
        assert stream.is_disposed

    def test_audio_queue_functionality(self):
        """Test AudioQueue functionality"""
        format = cm.AudioFormat(44100.0, 'lpcm', channels_per_frame=2,
            bits_per_channel=16)
        try:
            queue = cm.AudioQueue.new_output(format)
            assert isinstance(queue, cm.AudioQueue)
            assert not queue.is_disposed
            try:
                buffer = queue.allocate_buffer(1024)
                assert isinstance(buffer, cm.AudioBuffer)
                assert buffer.buffer_size == 1024
                queue.enqueue_buffer(buffer)
                try:
                    queue.start()
                    queue.stop()
                except cm.AudioQueueError:
                    pass
            finally:
                queue.dispose()
                assert queue.is_disposed
        except cm.AudioQueueError as e:
            if 'status: -50' in str(e):
                pytest.skip(
                    'AudioQueue creation failed - no audio hardware available')
            else:
                raise

    def test_audio_component_description_functionality(self):
        """Test AudioComponentDescription functionality"""
        desc = cm.AudioComponentDescription('auou', 'def ', 'appl', flags=1,
            flags_mask=2)
        assert desc.type == 'auou'
        assert desc.subtype == 'def '
        assert desc.manufacturer == 'appl'
        assert desc.flags == 1
        assert desc.flags_mask == 2
        dict_repr = desc.to_dict()
        expected = {'type': capi.fourchar_to_int('auou'), 'subtype': capi.
            fourchar_to_int('def '), 'manufacturer': capi.fourchar_to_int(
            'appl'), 'flags': 1, 'flags_mask': 2}
        assert dict_repr == expected

    def test_audio_unit_functionality(self):
        """Test AudioUnit functionality"""
        try:
            unit = cm.AudioUnit.default_output()
            assert isinstance(unit, cm.AudioUnit)
            assert not unit.is_disposed
            assert not unit.is_initialized
            with unit:
                assert unit.is_initialized
            assert not unit.is_initialized
            assert unit.is_disposed
        except cm.AudioUnitError as e:
            if 'not found' in str(e):
                pytest.skip(
                    'Default output AudioUnit not available in test environment'
                    )
            else:
                raise

    def test_midi_client_functionality(self):
        """Test MIDIClient functionality"""
        client = cm.MIDIClient('Test Client')
        assert isinstance(client, cm.MIDIClient)
        assert client.name == 'Test Client'
        assert not client.is_disposed
        try:
            input_port = client.create_input_port('Test Input')
            output_port = client.create_output_port('Test Output')
            assert isinstance(input_port, cm.MIDIInputPort)
            assert isinstance(output_port, cm.MIDIOutputPort)
            assert input_port.name == 'Test Input'
            assert output_port.name == 'Test Output'
        finally:
            client.dispose()
            assert client.is_disposed

    def test_dual_api_consistency(self, amen_wav_path):
        """Test consistency between functional and OO APIs"""
        fourcc_func = capi.fourchar_to_int('TEST')
        format_oo = cm.AudioFormat(44100.0, 'lpcm', channels_per_frame=2)
        assert isinstance(fourcc_func, int)
        assert format_oo.is_pcm
        func_file_id = capi.audio_file_open_url(amen_wav_path)
        try:
            func_data, func_count = capi.audio_file_read_packets(func_file_id,
                0, 5)
        finally:
            capi.audio_file_close(func_file_id)
        with cm.AudioFile(amen_wav_path) as oo_file:
            oo_data, oo_count = oo_file.read_packets(0, 5)
        assert func_data == oo_data
        assert func_count == oo_count

    def test_error_handling_functionality(self):
        """Test error handling in OO API"""
        with pytest.raises(cm.AudioFileError):
            with cm.AudioFile('/nonexistent/file.wav'):
                pass
        invalid_format = cm.AudioFormat(0.0, '', channels_per_frame=0)
        with pytest.raises(cm.AudioQueueError):
            cm.AudioQueue.new_output(invalid_format)
        obj = cm.CoreAudioObject()
        obj.dispose()
        with pytest.raises(RuntimeError, match='has been disposed'):
            obj._ensure_not_disposed()

    def test_resource_management_functionality(self, amen_wav_path):
        """Test proper resource management"""
        objects = []
        for _ in range(3):
            audio_file = cm.AudioFile(amen_wav_path)
            audio_file.open()
            objects.append(audio_file)
        format = cm.AudioFormat(44100.0, 'lpcm', channels_per_frame=2,
            bits_per_channel=16)
        for _ in range(3):
            try:
                queue = cm.AudioQueue.new_output(format)
                objects.append(queue)
            except cm.AudioQueueError:
                pass
        for i in range(3):
            client = cm.MIDIClient(f'Test Client {i}')
            objects.append(client)
        for obj in objects:
            obj.dispose()
            assert obj.is_disposed

    def test_polymorphism_functionality(self):
        """Test polymorphic behavior"""
        objects = [cm.CoreAudioObject(), cm.AudioFile('/dummy/path'), cm.
            AudioFileStream()]
        format = cm.AudioFormat(44100.0, 'lpcm', channels_per_frame=2,
            bits_per_channel=16)
        try:
            queue = cm.AudioQueue.new_output(format)
            objects.append(queue)
        except cm.AudioQueueError:
            pass
        try:
            client = cm.MIDIClient('Test Client')
            objects.append(client)
        except cm.MIDIError:
            pass
        for obj in objects:
            assert isinstance(obj, cm.CoreAudioObject)
            assert hasattr(obj, 'is_disposed')
            assert hasattr(obj, 'dispose')
            assert hasattr(obj, '_ensure_not_disposed')
            if not obj.is_disposed:
                obj.dispose()
