"""Tests for the require functionality."""

import os
import sys
import tempfile
from pathlib import Path
import importlib

# Add src to Python path for imports
sys.path.insert(0, str(Path(__file__).parent.parent / "src"))

# Import the require functionality
from inceptum.require import require
from inceptum.config import config

# Get the actual module to access internal state for testing
config_module = importlib.import_module('inceptum.config')


def test_require_config():
    """Test that require('config') returns the config function."""
    result = require('config')
    assert result is config
    print("✅ require('config') test passed")


def test_require_missing_module():
    """Test that require raises ImportError for missing modules."""
    with tempfile.TemporaryDirectory() as tmp_dir:
        tmp_path = Path(tmp_dir)
        
        # Setup basic config
        config_file = tmp_path / "config.yaml"
        config_file.write_text("before: []\nafter: []\n")
        
        # Add empty require prefix configuration
        require_config = tmp_path / "require.yaml"
        require_config.write_text("prefix: []\n")
        
        # Configure environment
        os.environ["INCEPTUM"] = str(tmp_path)
        
        # Reset loader
        config_module._loader = None
        
        try:
            require('nonexistent_module_12345')
            assert False, "Should have raised ImportError"
        except ImportError as e:
            assert "require: ''nonexistent_module_12345'' not found" in str(e)
            print("✅ require missing module test passed")


def test_require_with_config_prefixes():
    """Test require with custom prefixes from config."""
    with tempfile.TemporaryDirectory() as tmp_dir:
        tmp_path = Path(tmp_dir)
        
        # Setup config with custom require prefixes
        config_file = tmp_path / "config.yaml"
        config_file.write_text("before: []\nafter: []\n")
        
        # Add require prefix configuration
        require_config = tmp_path / "require.yaml"
        require_config.write_text("prefix: ['custom.']\n")
        
        # Configure environment
        os.environ["INCEPTUM"] = str(tmp_path)
        
        # Reset loader
        config_module._loader = None
        
        # Test that require uses the custom prefix
        # This would work if we had a 'custom.test_module' available
        # For now, just verify the config is loaded correctly
        prefixes = config('require.prefix')
        assert prefixes == ['custom.']
        
        print("✅ require with config prefixes test passed")


def test_require_with_none_prefix():
    """Test that require handles None prefix config correctly."""
    with tempfile.TemporaryDirectory() as tmp_dir:
        tmp_path = Path(tmp_dir)
        
        # Setup config without require prefix (should return None)
        config_file = tmp_path / "config.yaml"
        config_file.write_text("before: []\nafter: []\n")
        
        # Configure environment
        os.environ["INCEPTUM"] = str(tmp_path)
        
        # Reset loader
        config_module._loader = None
        
        # Test that config('require.prefix') returns None when not set
        prefixes = config('require.prefix')
        assert prefixes is None
        
        # Test that require still works with None prefix (should use default [])
        # This should not raise an error
        try:
            result = require('config')
            assert result is config
            print("✅ require with None prefix test passed")
        except Exception as e:
            assert False, f"require should handle None prefix, but got error: {e}"


def test_require_with_null_prefix_config():
    """Test that require handles explicit null prefix in config."""
    with tempfile.TemporaryDirectory() as tmp_dir:
        tmp_path = Path(tmp_dir)
        
        # Setup config with explicit null prefix
        config_file = tmp_path / "config.yaml"
        config_file.write_text("before: []\nafter: []\n")
        
        # Add require prefix configuration with explicit null
        require_config = tmp_path / "require.yaml"
        require_config.write_text("prefix: null\n")
        
        # Configure environment
        os.environ["INCEPTUM"] = str(tmp_path)
        
        # Reset loader
        config_module._loader = None
        
        # Test that config('require.prefix') returns None when explicitly set to null
        prefixes = config('require.prefix')
        assert prefixes is None
        
        # Test that require still works with explicit null prefix
        try:
            result = require('config')
            assert result is config
            print("✅ require with explicit null prefix test passed")
        except Exception as e:
            assert False, f"require should handle explicit null prefix, but got error: {e}"


def test_require_with_string_prefix():
    """Test that require handles string prefix (non-list) in config."""
    with tempfile.TemporaryDirectory() as tmp_dir:
        tmp_path = Path(tmp_dir)
        
        # Setup config with string prefix (should be handled by dpath.get)
        config_file = tmp_path / "config.yaml"
        config_file.write_text("before: []\nafter: []\n")
        
        # Add require prefix configuration as string (not list)
        require_config = tmp_path / "require.yaml"
        require_config.write_text("prefix: 'custom.'\n")
        
        # Configure environment
        os.environ["INCEPTUM"] = str(tmp_path)
        
        # Reset loader
        config_module._loader = None
        
        # Test what config returns for string prefix
        prefixes = config('require.prefix')
        print(f"String prefix config returns: {prefixes} (type: {type(prefixes)})")
        
        # Test that require handles this gracefully
        try:
            result = require('config')
            assert result is config
            print("✅ require with string prefix test passed")
        except Exception as e:
            # This might fail depending on how dpath handles string vs list
            print(f"⚠️  require with string prefix failed as expected: {e}")
            # This is acceptable - the test documents the current behavior


def test_require_with_empty_string_prefix():
    """Test that require handles empty string in prefix list."""
    with tempfile.TemporaryDirectory() as tmp_dir:
        tmp_path = Path(tmp_dir)
        
        # Setup config with empty string in prefix
        config_file = tmp_path / "config.yaml"
        config_file.write_text("before: []\nafter: []\n")
        
        # Add require prefix configuration with empty string
        require_config = tmp_path / "require.yaml"
        require_config.write_text("prefix: ['']\n")
        
        # Configure environment
        os.environ["INCEPTUM"] = str(tmp_path)
        
        # Reset loader
        config_module._loader = None
        
        # Test that config returns the empty string prefix
        prefixes = config('require.prefix')
        assert prefixes == ['']
        
        # Test that require works with empty string prefix
        try:
            result = require('config')
            assert result is config
            print("✅ require with empty string prefix test passed")
        except Exception as e:
            assert False, f"require should handle empty string prefix, but got error: {e}"


def test_require_with_whitespace_prefix():
    """Test that require handles whitespace in prefix."""
    with tempfile.TemporaryDirectory() as tmp_dir:
        tmp_path = Path(tmp_dir)
        
        # Setup config with whitespace in prefix
        config_file = tmp_path / "config.yaml"
        config_file.write_text("before: []\nafter: []\n")
        
        # Add require prefix configuration with whitespace
        require_config = tmp_path / "require.yaml"
        require_config.write_text("prefix: ['  my_prefix  ']\n")
        
        # Configure environment
        os.environ["INCEPTUM"] = str(tmp_path)
        
        # Reset loader
        config_module._loader = None
        
        # Test what config returns for whitespace prefix
        prefixes = config('require.prefix')
        print(f"Whitespace prefix config returns: {prefixes}")
        
        # Test that require handles this (might create weird module names)
        try:
            result = require('config')
            assert result is config
            print("✅ require with whitespace prefix test passed")
        except Exception as e:
            # This might fail with weird module names, which is acceptable
            print(f"⚠️  require with whitespace prefix failed as expected: {e}")


def test_require_special_characters_in_name():
    """Test require with special characters in module name."""
    # Test various special characters in module names
    special_names = [
        'my-module',      # dashes (should be converted to underscores)
        'my.module',      # dots
        'my_module_123',  # underscores and numbers
        '123module',      # starts with number
    ]
    
    for name in special_names:
        try:
            result = require(name)
            # If it doesn't raise an error, that's fine
            print(f"✅ require('{name}') handled gracefully")
        except ImportError:
            # ImportError is expected for non-existent modules
            print(f"✅ require('{name}') correctly raised ImportError")
        except Exception as e:
            # Other exceptions might indicate issues
            print(f"⚠️  require('{name}') raised {type(e).__name__}: {e}")


def test_require_unicode_names():
    """Test require with unicode characters in module names."""
    unicode_names = [
        'módulo',        # Spanish
        '模块',          # Chinese
        'модуль',        # Russian
        '🎮game',        # Emoji (might fail, but should fail gracefully)
    ]
    
    for name in unicode_names:
        try:
            result = require(name)
            print(f"✅ require('{name}') handled gracefully")
        except ImportError:
            print(f"✅ require('{name}') correctly raised ImportError")
        except UnicodeError as e:
            print(f"⚠️  require('{name}') raised UnicodeError: {e}")
        except Exception as e:
            print(f"⚠️  require('{name}') raised {type(e).__name__}: {e}")


def test_require_long_module_paths():
    """Test require with very long module/attribute paths."""
    # Test very long module path
    long_name = 'a.' * 50 + 'module'  # Very long path
    
    try:
        result = require(long_name)
        print(f"✅ require with long path handled gracefully")
    except ImportError:
        print(f"✅ require with long path correctly raised ImportError")
    except RecursionError:
        print(f"❌ require with long path caused RecursionError")
    except Exception as e:
        print(f"⚠️  require with long path raised {type(e).__name__}: {e}")


def test_require_empty_and_whitespace_names():
    """Test require with empty or whitespace-only names."""
    edge_names = [
        '',           # Empty string
        '   ',        # Whitespace only
        '\t',        # Tab only
        '\n',        # Newline only
    ]
    
    for name in edge_names:
        try:
            result = require(name)
            print(f"⚠️  require('{repr(name)}') unexpectedly succeeded")
        except ImportError:
            print(f"✅ require('{repr(name)}') correctly raised ImportError")
        except ValueError as e:
            print(f"✅ require('{repr(name)}') correctly raised ValueError: {e}")
        except Exception as e:
            print(f"⚠️  require('{repr(name)}') raised {type(e).__name__}: {e}")


def test_require_cli_mode():
    """Test require in CLI mode with different suffixes."""
    # Test that CLI mode uses different suffixes
    # This is more of a behavioral test since we don't have actual CLI modules
    
    # Test that cli=True doesn't break the basic functionality
    try:
        require('config', cli=True)
        # Should still work for built-in names
    except Exception:
        pass  # Expected for most cases
        
    print("✅ require CLI mode test passed")


def test_require_module_import():
    """Test require can import actual Python modules."""
    # Test importing a built-in module
    try:
        # Try to require a common built-in module
        os_module = require('os')
        assert os_module is os
        print("✅ require module import test passed")
    except ImportError:
        # If os isn't found via require, that's also acceptable
        # The test is mainly to verify the import mechanism works
        print("✅ require module import test passed (os not found via require)")


def test_require_attribute_error_behavior():
    """Test that AttributeError in require is handled correctly."""
    # This test verifies the current behavior where AttributeError
    # is caught and treated as "not found", resulting in ImportError
    
    with tempfile.TemporaryDirectory() as tmp_dir:
        tmp_path = Path(tmp_dir)
        
        # Setup basic config
        config_file = tmp_path / "config.yaml"
        config_file.write_text("before: []\nafter: []\n")
        
        # Add empty require prefix configuration
        require_config = tmp_path / "require.yaml"
        require_config.write_text("prefix: []\n")
        
        # Configure environment
        os.environ["INCEPTUM"] = str(tmp_path)
        
        # Reset loader
        config_module._loader = None
        
        # Create a mock module that raises AttributeError when accessed
        import sys
        import types
        
        # Create a mock module
        mock_module = types.ModuleType('mock_module')
        mock_module.non_callable_attr = "This will cause AttributeError when called"
        
        # Add it to sys.modules temporarily
        original_modules = sys.modules.copy()
        sys.modules['mock_module'] = mock_module
        
        try:
            # This should raise ImportError (not AttributeError)
            # because the current implementation catches AttributeError and continues
            result = require('mock_module.non_callable_attr.some_method')
            # If we get here, something unexpected happened
            assert False, "Expected ImportError to be raised"
        except ImportError as e:
            # This is the current behavior - AttributeError is caught and ImportError is raised
            assert "require: ''mock_module.non_callable_attr.some_method'' not found" in str(e)
            print("✅ require attribute error behavior test passed")
        except AttributeError:
            # If we get AttributeError, the behavior has changed
            assert False, "Expected ImportError, not AttributeError"
        finally:
            # Restore original sys.modules
            sys.modules.clear()
            sys.modules.update(original_modules)





def test_require_good_module():
    """Test that require can import a good module correctly."""
    import sys
    import os
    import tempfile
    from pathlib import Path
    
    # Setup config to avoid None issues
    with tempfile.TemporaryDirectory() as tmp_dir:
        tmp_path = Path(tmp_dir)
        config_file = tmp_path / "config.yaml"
        config_file.write_text("before: []\nafter: []\n")
        require_config = tmp_path / "require.yaml"
        require_config.write_text("prefix: []\n")
        os.environ["INCEPTUM"] = str(tmp_path)
        
        # Reset loader
        import importlib
        config_module = importlib.import_module('inceptum.config')
        config_module._loader = None
        
        # Add test modules directory to path
        test_modules_dir = os.path.join(os.path.dirname(__file__), 'test_modules')
        if test_modules_dir not in sys.path:
            sys.path.insert(0, test_modules_dir)
        
        try:
            # Test importing the good module
            good_mod = require('good_module')
            assert good_mod is not None
            assert hasattr(good_mod, 'test_function')
            assert hasattr(good_mod, 'TestClass')
            assert hasattr(good_mod, 'TEST_VAR')
            
            # Test accessing functions
            assert good_mod.test_function() == "good_module works!"
            assert good_mod.TEST_VAR == "test_variable_value"
            
            # Test accessing nested attributes
            test_class = require('good_module.TestClass')
            assert test_class is good_mod.TestClass
            
            # Test accessing methods
            test_method = require('good_module.TestClass.method')
            instance = test_class()
            assert test_method(instance) == "TestClass.method works!"
            
            print("✅ require good module test passed")
            
        finally:
            # Clean up
            if test_modules_dir in sys.path:
                sys.path.remove(test_modules_dir)


def test_require_syntax_error_module():
    """Test that require propagates SyntaxError from bad modules."""
    import sys
    import os
    import tempfile
    from pathlib import Path
    
    # Setup config to avoid None issues
    with tempfile.TemporaryDirectory() as tmp_dir:
        tmp_path = Path(tmp_dir)
        config_file = tmp_path / "config.yaml"
        config_file.write_text("before: []\nafter: []\n")
        require_config = tmp_path / "require.yaml"
        require_config.write_text("prefix: []\n")
        os.environ["INCEPTUM"] = str(tmp_path)
        
        # Reset loader
        import importlib
        config_module = importlib.import_module('inceptum.config')
        config_module._loader = None
        
        # Add test modules directory to path
        test_modules_dir = os.path.join(os.path.dirname(__file__), 'test_modules')
        if test_modules_dir not in sys.path:
            sys.path.insert(0, test_modules_dir)
        
        try:
            # This should raise SyntaxError, not ImportError
            result = require('syntax_error_module')
            assert False, f"Expected SyntaxError but got result: {result}"
            
        except SyntaxError as e:
            # This is the expected behavior - SyntaxError should propagate
            assert "syntax_error_module.py" in str(e)
            print("✅ require syntax error propagation test passed")
            
        except ImportError as e:
            # If we get ImportError, the SyntaxError was incorrectly caught
            assert False, f"Expected SyntaxError to propagate, but got ImportError: {e}"
            
        except Exception as e:
            # Other exceptions might be acceptable depending on implementation
            # But SyntaxError should definitely propagate
            assert False, f"Expected SyntaxError to propagate, but got {type(e)}: {e}"
            
        finally:
            # Clean up
            if test_modules_dir in sys.path:
                sys.path.remove(test_modules_dir)


if __name__ == "__main__":
    test_require_config()
    test_require_missing_module()
    test_require_with_config_prefixes()
    test_require_with_none_prefix()
    test_require_with_null_prefix_config()
    test_require_with_string_prefix()
    test_require_with_empty_string_prefix()
    test_require_with_whitespace_prefix()
    test_require_special_characters_in_name()
    test_require_unicode_names()
    test_require_long_module_paths()
    test_require_empty_and_whitespace_names()
    test_require_cli_mode()
    test_require_module_import()
    test_require_attribute_error_behavior()
    test_require_good_module()
    test_require_syntax_error_module()
    print("\n🎉 All require tests passed!")