# tests/test_importer_core.py
import pytest
import asyncio
import sys
import importlib
from pathlib import Path
from unittest.mock import Mock, patch

# Import the main components
from importer_core import (
    Modulex, 
    pmeX, 
    PyPISearch,
    Source,
    size,
    installs,
    __installation__,
    get_dep_tree,
    ModuleComparator,
    CallsTracker,
    VersionTagger,
    MetaText,
    Signature
)


class TestModulex:
    """Test Modulex class for module analysis"""
    
    def test_modulex_initialization(self):
        """Test Modulex initialization with built-in module"""
        mod = Modulex('os')
        assert mod.name == 'os'
        assert mod.init is not None
        assert mod.file is not None
    
    def test_modulex_str_representation(self):
        """Test string representation"""
        mod = Modulex('sys')
        assert 'Modulex' in str(mod)
        assert 'sys' in str(mod)
    
    def test_modulex_path(self):
        """Test path method"""
        mod = Modulex('os')
        path = mod.path()
        assert isinstance(path, Path)
        assert path.exists()
    
    def test_modulex_dir(self):
        """Test directory listing"""
        mod = Modulex('os')
        dir_list = mod.dir(dunder=False)
        assert isinstance(dir_list, list)
        assert all(not attr.startswith('__') for attr in dir_list)
    
    def test_modulex_version(self):
        """Test version property"""
        mod = Modulex('os')
        version = mod.version
        assert hasattr(version, 'version')
    
    def test_modulex_is_installed(self):
        """Test installation check"""
        mod = Modulex('os')
        assert mod.is_installed() is True
    
    def test_modulex_stats(self):
        """Test statistics"""
        mod = Modulex('os')
        stats = mod.stats()
        expected_keys = ['name', 'file', 'path', 'version', 'isbuiltin']
        assert all(key in stats for key in expected_keys)


class TestPmeX:
    """Test pmeX class for module exploration"""
    
    def test_pmex_initialization(self):
        """Test pmeX initialization"""
        explorer = pmeX('os')
        assert explorer.name == 'os'
        assert explorer.modx is not None
    
    def test_pmex_dump_code(self):
        """Test code dumping functionality"""
        explorer = pmeX('os')
        # This is mostly testing that the method doesn't crash
        # Actual file writing would require proper setup
        try:
            explorer.dump_code("TEST_VAR = 123", "test_file.py", rewrite=True)
        except Exception:
            pass  # Expected in test environment
    
    def test_pmex_snapshot(self):
        """Test snapshot functionality"""
        explorer = pmeX('os')
        snapshot = explorer.snapshot(max=10)
        assert isinstance(snapshot, dict)
    
    def test_pmex_describe(self):
        """Test describe method"""
        explorer = pmeX('os')
        description = explorer.describe()
        assert isinstance(description, dict)


class TestPyPISearch:
    """Test PyPI search functionality"""
    
    @pytest.mark.asyncio
    async def test_pypi_search_initialization(self):
        """Test PyPISearch initialization"""
        async with PyPISearch() as searcher:
            assert searcher is not None
            assert hasattr(searcher, 'search')
    
    @pytest.mark.asyncio
    async def test_search_package_function(self):
        """Test the convenience search function"""
        # Mock the actual network call
        with patch('importer_core.PyPISearch.search') as mock_search:
            mock_search.return_value = []
            results = await PyPISearch.search("test")
            assert isinstance(results, list)


class TestSourceAnalysis:
    """Test Source code analysis"""
    
    def test_source_initialization(self):
        """Test Source initialization"""
        src = Source(Path)
        assert src.obj == Path
        assert src.name == 'Path'
    
    def test_source_property(self):
        """Test source code extraction"""
        src = Source(Path)
        source_code = src.source
        assert isinstance(source_code, str)
        assert 'class Path' in source_code or 'def Path' in source_code
    
    def test_source_file_property(self):
        """Test file path extraction"""
        src = Source(Path)
        file_path = src.file
        assert 'pathlib.py' in file_path
    
    def test_source_decorators(self):
        """Test decorator extraction"""
        # Test with a function that has decorators
        def decorated_func():
            pass
        decorated_func.__name__ = 'test_func'
        
        src = Source(decorated_func)
        decorators = src.decorators
        assert isinstance(decorators, list)
    
    def test_source_defs(self):
        """Test definitions extraction"""
        src = Source(Path)
        defs = src.defs
        assert isinstance(defs, list)
    
    def test_source_classes(self):
        """Test classes extraction"""
        src = Source(Path)
        classes = src.classes
        assert isinstance(classes, list)


class TestSizeAnalysis:
    """Test size analysis functionality"""
    
    def test_size_initialization(self):
        """Test size analyzer initialization"""
        sz = size('os')
        assert sz.init is not None
        assert sz.path.exists()
    
    def test_size_property(self):
        """Test size calculation"""
        sz = size('os')
        total_size = sz.size
        assert isinstance(total_size, int)
        assert total_size >= 0
    
    def test_size_find_method(self):
        """Test file finding by size"""
        sz = size('os')
        files = sz.find(size=100, cmp=">=", maxfiles=5)
        assert isinstance(files, list)


class TestInstallation:
    """Test package installation functionality"""
    
    def test_installation_initialization(self):
        """Test installation manager initialization"""
        installer = __installation__('requests')
        assert installer.lib == 'requests'
    
    def test_installation_is_installed(self):
        """Test installation check"""
        installer = __installation__('os')  # os should be installed
        assert installer.is_installed() is True
    
    def test_installation_get_version(self):
        """Test version retrieval"""
        installer = __installation__('os')
        version = installer.get_version()
        assert version is not None


class TestDependencyTree:
    """Test dependency tree functionality"""
    
    def test_get_dep_tree(self):
        """Test dependency tree generation"""
        # Test with a built-in package that has few dependencies
        tree = get_dep_tree('os', max_depth=1)
        assert tree is not None


class TestModuleComparator:
    """Test module comparison functionality"""
    
    def test_comparator_initialization(self):
        """Test module comparator initialization"""
        # This might need proper setup with actual modules to compare
        try:
            comparator = ModuleComparator('os', '.')
            assert comparator is not None
        except Exception:
            pass  # Expected in some environments


class TestCallsTracker:
    """Test calls tracking functionality"""
    
    def test_tracker_initialization(self):
        """Test calls tracker initialization"""
        tracker = CallsTracker()
        assert tracker is not None
        assert hasattr(tracker, 'start')
        assert hasattr(tracker, 'stop')
    
    def test_tracker_configuration(self):
        """Test tracker configuration"""
        tracker = CallsTracker()
        tracker.configure(track_arguments=True, track_memory=False)
        assert tracker.config['track_arguments'] is True
        assert tracker.config['track_memory'] is False


class TestVersionTagger:
    """Test version tagging functionality"""
    
    def test_tagger_initialization(self):
        """Test version tagger initialization"""
        tagger = VersionTagger('.')
        assert tagger is not None
        assert hasattr(tagger, 'tag')
        assert hasattr(tagger, 'get_current_version')
    
    def test_tagger_get_current_version(self):
        """Test current version retrieval"""
        tagger = VersionTagger('.')
        version = tagger.get_current_version()
        assert isinstance(version, str)
        assert len(version.split('.')) == 3  # Should be semantic version


class TestMetaText:
    """Test metadata text parsing"""
    
    def test_metatext_initialization(self):
        """Test MetaText initialization"""
        sample_metadata = """
Name: test-package
Version: 1.0.0
Summary: A test package
Author: Test Author
"""
        meta = MetaText(sample_metadata)
        assert meta.metadata_text == sample_metadata.strip()
    
    def test_metatext_name_property(self):
        """Test name extraction"""
        sample_metadata = "Name: test-package\nVersion: 1.0.0"
        meta = MetaText(sample_metadata)
        assert meta.name == "test-package"
    
    def test_metatext_version_property(self):
        """Test version extraction"""
        sample_metadata = "Name: test-package\nVersion: 1.0.0"
        meta = MetaText(sample_metadata)
        assert meta.version == "1.0.0"


class TestSignature:
    """Test signature analysis"""
    
    def test_signature_initialization(self):
        """Test Signature initialization"""
        code = "def test_func(a, b=1): pass"
        sig = Signature(code)
        assert sig.body == code
    
    def test_signature_analysis(self):
        """Test signature analysis"""
        code = "def test_func(a: int, b: str = 'hello') -> bool: pass"
        sig = Signature(code)
        result = sig.signature()
        assert result['name'] == 'test_func'
        assert 'a' in result['parameters']
        assert 'b' in result['parameters']


class TestBatchInstallation:
    """Test batch installation functionality"""
    
    def test_installs_function(self):
        """Test batch installation function"""
        # Mock subprocess to avoid actual installation
        with patch('subprocess.run') as mock_run:
            mock_run.return_value = Mock(returncode=0, stdout='', stderr='')
            results = installs(['test-package-1', 'test-package-2'], mode='thread')
            assert isinstance(results, list)
            assert len(results) == 2
            assert all('package' in result for result in results)


class TestIntegration:
    """Integration tests for multiple components"""
    
    def test_full_workflow(self):
        """Test a complete workflow using multiple components"""
        # Analyze a module
        mod = Modulex('os')
        assert mod.is_installed()
        
        # Get statistics
        stats = mod.stats()
        assert 'name' in stats
        
        # Explore with pmeX
        explorer = pmeX('os')
        description = explorer.describe()
        assert isinstance(description, dict)
        
        # Analyze source
        src = Source(Path)
        source_code = src.source
        assert isinstance(source_code, str)
    
    @pytest.mark.asyncio
    async def test_async_workflow(self):
        """Test async workflow"""
        async with PyPISearch() as searcher:
            # This will actually try to search PyPI
            try:
                results = await searcher.search("test", max_results=1)
                assert isinstance(results, list)
            except Exception:
                # Network errors are acceptable in tests
                pass


# Utility functions for testing
def test_getsitepath():
    """Test site packages path detection"""
    from importer_core import getsitepath
    sites = getsitepath()
    assert isinstance(sites, list)
    assert any('site-packages' in site for site in sites)


def test_error_handling():
    """Test error handling for invalid inputs"""
    # Test with non-existent module
    with pytest.raises(ImportError):
        Modulex('non_existent_module_12345')
    
    # Test with invalid type
    with pytest.raises(TypeError):
        Source(123)  # Should raise TypeError


@pytest.mark.parametrize("module_name", ['os', 'sys', 'pathlib'])
def test_multiple_modules(module_name):
    """Test with multiple different modules"""
    mod = Modulex(module_name)
    assert mod.name == module_name
    assert mod.is_installed()


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