"""Unit tests for EEG plugins."""

import pytest
from pathlib import Path
from unittest.mock import Mock, patch, MagicMock
import mne

from tests.fixtures.test_utils import BaseTestCase, EEGAssertions
from tests.fixtures.synthetic_data import create_synthetic_raw

# Test with mock plugins to avoid heavy import dependencies
try:
    from autoclean.io.import_ import BaseEEGPlugin
    PLUGIN_BASE_AVAILABLE = True
except ImportError:
    PLUGIN_BASE_AVAILABLE = False


class TestEGIRawGSN129Plugin:
    """Test EGI Raw GSN-HydroCel-129 plugin functionality."""
    
    @pytest.mark.skipif(not PLUGIN_BASE_AVAILABLE, reason="Plugin base not available")
    def test_plugin_format_montage_support(self):
        """Test plugin format and montage support detection."""
        # Create mock plugin based on real interface
        class MockEGIRawGSN129Plugin(BaseEEGPlugin):
            @classmethod
            def supports_format_montage(cls, format_id: str, montage_name: str) -> bool:
                return format_id == "EGI_RAW" and montage_name == "GSN-HydroCel-129"
            
            def import_and_configure(self, file_path: Path, autoclean_dict: dict, preload: bool = True):
                return create_synthetic_raw(montage="GSN-HydroCel-129", n_channels=129)
        
        plugin_class = MockEGIRawGSN129Plugin
        
        # Should support correct combination
        assert plugin_class.supports_format_montage("EGI_RAW", "GSN-HydroCel-129") is True
        
        # Should not support incorrect combinations
        assert plugin_class.supports_format_montage("EEGLAB_SET", "GSN-HydroCel-129") is False
        assert plugin_class.supports_format_montage("EGI_RAW", "GSN-HydroCel-128") is False
        assert plugin_class.supports_format_montage("GENERIC_FIF", "standard_1020") is False
    
    @patch('mne.io.read_raw_egi')
    def test_egi_raw_import_functionality(self, mock_read_raw):
        """Test EGI raw file import functionality."""
        # Mock the MNE import function
        mock_raw = create_synthetic_raw(montage="GSN-HydroCel-129", n_channels=129)
        mock_read_raw.return_value = mock_raw
        
        # Create mock plugin
        if PLUGIN_BASE_AVAILABLE:
            class MockEGIPlugin(BaseEEGPlugin):
                @classmethod
                def supports_format_montage(cls, format_id: str, montage_name: str) -> bool:
                    return format_id == "EGI_RAW" and montage_name == "GSN-HydroCel-129"
                
                def import_and_configure(self, file_path: Path, autoclean_dict: dict, preload: bool = True):
                    # Simulate real plugin behavior
                    raw = mne.io.read_raw_egi(
                        input_fname=file_path,
                        preload=preload,
                        events_as_annotations=True,
                        exclude=[]
                    )
                    return raw
            
            plugin = MockEGIPlugin()
            test_file = Path("/test/data.raw")
            config = {"montage": {"value": "GSN-HydroCel-129"}}
            
            result = plugin.import_and_configure(test_file, config)
            
            # Verify MNE function was called correctly
            mock_read_raw.assert_called_once_with(
                input_fname=test_file,
                preload=True,
                events_as_annotations=True,
                exclude=[]
            )
            
            # Verify result properties
            EEGAssertions.assert_raw_properties(result, expected_n_channels=129)
    
    def test_gsn129_montage_configuration(self):
        """Test GSN-HydroCel-129 montage configuration."""
        # Test montage-specific configuration
        raw = create_synthetic_raw(montage="GSN-HydroCel-129", n_channels=129)
        
        # Should have 129 channels with correct naming
        assert len(raw.ch_names) == 129
        assert raw.ch_names[-1] == 'Cz'  # 129th channel should be Cz
        assert all(ch.startswith('E') for ch in raw.ch_names[:-1])  # E1-E128
        
        # Test channel types
        assert all(ch_type == 'eeg' for ch_type in raw.get_channel_types())


class TestEGIRawGSN124Plugin:
    """Test EGI Raw GSN-HydroCel-124 plugin functionality."""
    
    @pytest.mark.skipif(not PLUGIN_BASE_AVAILABLE, reason="Plugin base not available")
    def test_gsn124_plugin_support(self):
        """Test GSN-124 plugin format support."""
        class MockEGIRawGSN124Plugin(BaseEEGPlugin):
            @classmethod
            def supports_format_montage(cls, format_id: str, montage_name: str) -> bool:
                return format_id == "EGI_RAW" and montage_name == "GSN-HydroCel-124"
            
            def import_and_configure(self, file_path: Path, autoclean_dict: dict, preload: bool = True):
                return create_synthetic_raw(montage="GSN-HydroCel-124", n_channels=124)
        
        plugin_class = MockEGIRawGSN124Plugin
        
        # Should support GSN-124 specifically
        assert plugin_class.supports_format_montage("EGI_RAW", "GSN-HydroCel-124") is True
        assert plugin_class.supports_format_montage("EGI_RAW", "GSN-HydroCel-129") is False
    
    def test_gsn124_montage_differences(self):
        """Test differences between GSN-124 and GSN-129 montages."""
        raw_124 = create_synthetic_raw(montage="GSN-HydroCel-124", n_channels=124)
        raw_129 = create_synthetic_raw(montage="GSN-HydroCel-129", n_channels=129)
        
        # Should have different channel counts
        assert len(raw_124.ch_names) == 124
        assert len(raw_129.ch_names) == 129
        
        # Different reference electrode handling
        assert 'Cz' not in raw_124.ch_names  # No Cz in 124
        assert 'Cz' in raw_129.ch_names      # Cz in 129


class TestEEGLABPlugins:
    """Test EEGLAB format plugins."""
    
    @pytest.mark.skipif(not PLUGIN_BASE_AVAILABLE, reason="Plugin base not available")
    def test_eeglab_set_plugin_support(self):
        """Test EEGLAB .set plugin support."""
        class MockEEGLABGSN129Plugin(BaseEEGPlugin):
            @classmethod
            def supports_format_montage(cls, format_id: str, montage_name: str) -> bool:
                return format_id == "EEGLAB_SET" and montage_name == "GSN-HydroCel-129"
            
            def import_and_configure(self, file_path: Path, autoclean_dict: dict, preload: bool = True):
                return create_synthetic_raw(montage="GSN-HydroCel-129", n_channels=129)
        
        plugin_class = MockEEGLABGSN129Plugin
        
        # Should support EEGLAB .set format
        assert plugin_class.supports_format_montage("EEGLAB_SET", "GSN-HydroCel-129") is True
        assert plugin_class.supports_format_montage("EGI_RAW", "GSN-HydroCel-129") is False
    
    @patch('mne.io.read_raw_eeglab')
    def test_eeglab_import_functionality(self, mock_read_eeglab):
        """Test EEGLAB file import functionality."""
        mock_raw = create_synthetic_raw(montage="GSN-HydroCel-129", n_channels=129)
        mock_read_eeglab.return_value = mock_raw
        
        if PLUGIN_BASE_AVAILABLE:
            class MockEEGLABPlugin(BaseEEGPlugin):
                @classmethod
                def supports_format_montage(cls, format_id: str, montage_name: str) -> bool:
                    return format_id == "EEGLAB_SET" and montage_name == "GSN-HydroCel-129"
                
                def import_and_configure(self, file_path: Path, autoclean_dict: dict, preload: bool = True):
                    raw = mne.io.read_raw_eeglab(
                        input_fname=file_path,
                        preload=preload
                    )
                    return raw
            
            plugin = MockEEGLABPlugin()
            test_file = Path("/test/data.set")
            
            result = plugin.import_and_configure(test_file, {})
            
            mock_read_eeglab.assert_called_once_with(
                input_fname=test_file,
                preload=True
            )
            
            EEGAssertions.assert_raw_properties(result, expected_n_channels=129)


class TestStandard1020Plugin:
    """Test standard 10-20 montage plugin."""
    
    @pytest.mark.skipif(not PLUGIN_BASE_AVAILABLE, reason="Plugin base not available")
    def test_standard_1020_plugin_support(self):
        """Test standard 10-20 plugin support."""
        class MockStandard1020Plugin(BaseEEGPlugin):
            @classmethod
            def supports_format_montage(cls, format_id: str, montage_name: str) -> bool:
                return montage_name == "standard_1020"  # Support multiple formats
            
            def import_and_configure(self, file_path: Path, autoclean_dict: dict, preload: bool = True):
                return create_synthetic_raw(montage="standard_1020", n_channels=32)
        
        plugin_class = MockStandard1020Plugin
        
        # Should support standard 10-20 with various formats
        assert plugin_class.supports_format_montage("EEGLAB_SET", "standard_1020") is True
        assert plugin_class.supports_format_montage("GENERIC_FIF", "standard_1020") is True
        assert plugin_class.supports_format_montage("EGI_RAW", "standard_1020") is True
        
        # Should not support other montages
        assert plugin_class.supports_format_montage("EEGLAB_SET", "GSN-HydroCel-129") is False
    
    def test_standard_1020_montage_characteristics(self):
        """Test standard 10-20 montage characteristics."""
        raw = create_synthetic_raw(montage="standard_1020", n_channels=32)
        
        # Should have standard channel names
        standard_channels = ['Fp1', 'Fp2', 'F7', 'F3', 'Fz', 'F4', 'F8', 'Cz']
        for ch in standard_channels:
            if ch in raw.ch_names:  # Some might not be included in smaller montages
                assert ch in raw.ch_names
        
        # Should be EEG type
        assert all(ch_type == 'eeg' for ch_type in raw.get_channel_types())


class TestMEA30Plugin:
    """Test MEA30 (mouse) plugin functionality."""
    
    @pytest.mark.skipif(not PLUGIN_BASE_AVAILABLE, reason="Plugin base not available")
    def test_mea30_plugin_support(self):
        """Test MEA30 plugin support."""
        class MockMEA30Plugin(BaseEEGPlugin):
            @classmethod
            def supports_format_montage(cls, format_id: str, montage_name: str) -> bool:
                return montage_name == "MEA30"
            
            def import_and_configure(self, file_path: Path, autoclean_dict: dict, preload: bool = True):
                return create_synthetic_raw(montage="MEA30", n_channels=30)
        
        plugin_class = MockMEA30Plugin
        
        # Should support MEA30 montage
        assert plugin_class.supports_format_montage("EEGLAB_SET", "MEA30") is True
        assert plugin_class.supports_format_montage("GENERIC_FIF", "MEA30") is True
        
        # Should not support other montages
        assert plugin_class.supports_format_montage("EEGLAB_SET", "GSN-HydroCel-129") is False
    
    def test_mea30_montage_characteristics(self):
        """Test MEA30 montage characteristics."""
        raw = create_synthetic_raw(montage="MEA30", n_channels=30)
        
        # Should have 30 channels
        assert len(raw.ch_names) == 30
        
        # Should have CH naming convention for mouse EEG
        expected_names = [f'CH{i}' for i in range(1, 31)]
        for expected_name in expected_names:
            if expected_name in raw.ch_names:  # Depending on implementation
                assert expected_name in raw.ch_names


class TestPluginErrorHandling:
    """Test plugin error handling and edge cases."""
    
    @pytest.mark.skipif(not PLUGIN_BASE_AVAILABLE, reason="Plugin base not available")
    def test_plugin_file_not_found_error(self):
        """Test plugin behavior with non-existent files."""
        class MockPlugin(BaseEEGPlugin):
            @classmethod
            def supports_format_montage(cls, format_id: str, montage_name: str) -> bool:
                return True
            
            def import_and_configure(self, file_path: Path, autoclean_dict: dict, preload: bool = True):
                # Simulate file not found
                raise FileNotFoundError(f"File not found: {file_path}")
        
        plugin = MockPlugin()
        
        with pytest.raises(FileNotFoundError, match="File not found"):
            plugin.import_and_configure(Path("/nonexistent/file.raw"), {})
    
    @pytest.mark.skipif(not PLUGIN_BASE_AVAILABLE, reason="Plugin base not available")
    def test_plugin_invalid_file_format_error(self):
        """Test plugin behavior with invalid file formats."""
        class MockPlugin(BaseEEGPlugin):
            @classmethod
            def supports_format_montage(cls, format_id: str, montage_name: str) -> bool:
                return True
            
            def import_and_configure(self, file_path: Path, autoclean_dict: dict, preload: bool = True):
                # Simulate invalid file format
                raise ValueError(f"Invalid file format: {file_path}")
        
        plugin = MockPlugin()
        
        with pytest.raises(ValueError, match="Invalid file format"):
            plugin.import_and_configure(Path("/test/invalid.xyz"), {})
    
    @pytest.mark.skipif(not PLUGIN_BASE_AVAILABLE, reason="Plugin base not available")
    def test_plugin_montage_mismatch_error(self):
        """Test plugin behavior with montage mismatches."""
        class MockPlugin(BaseEEGPlugin):
            @classmethod
            def supports_format_montage(cls, format_id: str, montage_name: str) -> bool:
                return format_id == "TEST_FORMAT" and montage_name == "TEST_MONTAGE"
            
            def import_and_configure(self, file_path: Path, autoclean_dict: dict, preload: bool = True):
                # Simulate montage mismatch
                raise RuntimeError("File has 64 channels but montage expects 128")
        
        plugin = MockPlugin()
        
        with pytest.raises(RuntimeError, match="montage expects"):
            plugin.import_and_configure(Path("/test/data.raw"), {})


class TestPluginIntegration:
    """Test plugin integration with the broader system."""
    
    @pytest.mark.skipif(not PLUGIN_BASE_AVAILABLE, reason="Plugin base not available")
    def test_plugin_output_validation(self):
        """Test that plugin outputs are valid Raw objects."""
        class MockPlugin(BaseEEGPlugin):
            @classmethod
            def supports_format_montage(cls, format_id: str, montage_name: str) -> bool:
                return True
            
            def import_and_configure(self, file_path: Path, autoclean_dict: dict, preload: bool = True):
                return create_synthetic_raw()
        
        plugin = MockPlugin()
        result = plugin.import_and_configure(Path("/test/data.fif"), {})
        
        # Should return valid Raw object
        EEGAssertions.assert_raw_properties(result)
        assert hasattr(result, 'info')
        assert hasattr(result, 'get_data')
        assert hasattr(result, 'ch_names')
    
    @pytest.mark.skipif(not PLUGIN_BASE_AVAILABLE, reason="Plugin base not available")
    def test_plugin_configuration_handling(self):
        """Test that plugins handle configuration dictionaries properly."""
        class MockPlugin(BaseEEGPlugin):
            @classmethod
            def supports_format_montage(cls, format_id: str, montage_name: str) -> bool:
                return True
            
            def import_and_configure(self, file_path: Path, autoclean_dict: dict, preload: bool = True):
                # Should access config without errors
                montage_name = autoclean_dict.get("montage", {}).get("value", "default")
                return create_synthetic_raw(montage=montage_name)
        
        plugin = MockPlugin()
        config = {
            "montage": {"value": "GSN-HydroCel-129"},
            "other_setting": {"enabled": True}
        }
        
        result = plugin.import_and_configure(Path("/test/data.fif"), config)
        
        # Should handle config without errors
        assert result is not None
        EEGAssertions.assert_raw_properties(result)


class TestPluginMocked:
    """Test plugin functionality with heavy mocking."""
    
    def test_plugin_interface_mocked(self):
        """Test plugin interface with complete mocking."""
        # Mock the entire plugin system
        mock_plugin = Mock()
        mock_plugin.supports_format_montage.return_value = True
        mock_plugin.import_and_configure.return_value = create_synthetic_raw()
        
        # Test interface calls
        assert mock_plugin.supports_format_montage("TEST", "TEST") is True
        result = mock_plugin.import_and_configure(Path("/test"), {})
        
        # Verify mock was called
        mock_plugin.supports_format_montage.assert_called_once()
        mock_plugin.import_and_configure.assert_called_once()
        
        # Verify result
        EEGAssertions.assert_raw_properties(result)
    
    def test_multiple_plugins_mocked(self):
        """Test multiple plugin coordination with mocking."""
        # Mock multiple plugins
        plugin1 = Mock()
        plugin1.supports_format_montage.side_effect = lambda f, m: f == "FORMAT1"
        
        plugin2 = Mock()
        plugin2.supports_format_montage.side_effect = lambda f, m: f == "FORMAT2"
        
        # Test plugin selection logic
        plugins = [plugin1, plugin2]
        
        # Find plugin for FORMAT1
        selected_plugin = None
        for plugin in plugins:
            if plugin.supports_format_montage("FORMAT1", "TEST"):
                selected_plugin = plugin
                break
        
        assert selected_plugin == plugin1
        
        # Find plugin for FORMAT2
        selected_plugin = None
        for plugin in plugins:
            if plugin.supports_format_montage("FORMAT2", "TEST"):
                selected_plugin = plugin
                break
        
        assert selected_plugin == plugin2