"""
Comprehensive unit tests for refactored DjangoStormLoggerConfig
"""

import os
import pytest
from unittest.mock import patch, MagicMock, PropertyMock
from pathlib import Path
from dataclasses import FrozenInstanceError
import copy

from storm_logger.integrations.django_config import (
    # Enums and constants
    Environment, FeatureFlags, LogFiles, DjangoApps,
    
    # Core classes
    EnvironmentDetector, EnvironmentConfig,
    DevelopmentStrategy, StagingStrategy, ProductionStrategy, TestingStrategy,
    EnvironmentStrategyFactory,
    AppLogLevelManager, HandlerManager, DirectoryManager, ConfigValidator,
    DjangoStormLoggerConfig, DjangoConfigManager,
    
    # Public functions
    configure_django_storm_logging, get_django_config
)
from storm_logger.config import LogLevel, Framework


class TestEnvironment:
    """Test Environment enum"""
    
    def test_environment_values(self):
        """Test environment enum values"""
        assert Environment.DEVELOPMENT.value == "development"
        assert Environment.STAGING.value == "staging"
        assert Environment.PRODUCTION.value == "production"
        assert Environment.TESTING.value == "testing"
        assert Environment.AUTO.value == "auto"


class TestFeatureFlags:
    """Test FeatureFlags class"""
    
    def test_feature_flag_values(self):
        """Test feature flag constant values"""
        assert FeatureFlags.REQUEST_LOGGING == "enable_django_request_logging"
        assert FeatureFlags.SECURITY_LOGGING == "enable_django_security_logging"
        assert FeatureFlags.DB_LOGGING == "enable_django_db_logging"
        assert FeatureFlags.TEMPLATE_LOGGING == "enable_django_template_logging"
        assert FeatureFlags.TEST_LOGGING == "enable_test_logging"
        assert FeatureFlags.ERROR_EMAILS == "enable_error_emails"
        assert FeatureFlags.ADMIN_INTEGRATION == "enable_admin_integration"


class TestLogFiles:
    """Test LogFiles class"""
    
    def test_log_file_names(self):
        """Test standard log file names"""
        assert LogFiles.DJANGO == "django.log"
        assert LogFiles.REQUESTS == "requests.log"
        assert LogFiles.SECURITY == "security.log"
        assert LogFiles.TESTS == "tests.log"


class TestDjangoApps:
    """Test DjangoApps class"""
    
    def test_django_app_names(self):
        """Test Django app name constants"""
        assert DjangoApps.REQUEST == "django.request"
        assert DjangoApps.SECURITY == "django.security"
        assert DjangoApps.DB_BACKENDS == "django.db.backends"
        assert DjangoApps.TEMPLATE == "django.template"
        assert DjangoApps.STATICFILES == "django.contrib.staticfiles"
        assert DjangoApps.ADMIN == "django.contrib.admin"
        assert DjangoApps.AUTH == "django.contrib.auth"
        assert DjangoApps.CONTENTTYPES == "django.contrib.contenttypes"
        assert DjangoApps.SESSIONS == "django.contrib.sessions"
        assert DjangoApps.MESSAGES == "django.contrib.messages"


class TestEnvironmentDetector:
    """Test EnvironmentDetector class"""
    
    def test_detect_from_django_environment_var(self):
        """Test detection from DJANGO_ENVIRONMENT variable"""
        with patch.dict(os.environ, {"DJANGO_ENVIRONMENT": "STAGING"}):
            assert EnvironmentDetector.detect() == "staging"
    
    def test_detect_from_django_debug_true(self):
        """Test detection from Django DEBUG=True"""
        mock_settings = MagicMock()
        mock_settings.configured = True
        mock_settings.DEBUG = True
        
        with patch('storm_logger.integrations.django_config.settings', mock_settings):
            with patch.dict(os.environ, {}, clear=True):
                assert EnvironmentDetector.detect() == Environment.DEVELOPMENT.value
    
    def test_detect_from_django_testing(self):
        """Test detection from Django TESTING=True"""
        mock_settings = MagicMock()
        mock_settings.configured = True
        mock_settings.DEBUG = False
        mock_settings.TESTING = True
        
        with patch('storm_logger.integrations.django_config.settings', mock_settings):
            with patch.dict(os.environ, {}, clear=True):
                assert EnvironmentDetector.detect() == Environment.TESTING.value
    
    def test_detect_from_django_production(self):
        """Test detection from Django production settings"""
        mock_settings = MagicMock()
        mock_settings.configured = True
        mock_settings.DEBUG = False
        mock_settings.TESTING = False
        
        with patch('storm_logger.integrations.django_config.settings', mock_settings):
            with patch.dict(os.environ, {}, clear=True):
                assert EnvironmentDetector.detect() == Environment.PRODUCTION.value
    
    def test_detect_from_debug_env_var(self):
        """Test detection from DEBUG environment variable"""
        with patch.dict(os.environ, {"DEBUG": "true"}, clear=True):
            with patch('storm_logger.integrations.django_config.settings', None):
                assert EnvironmentDetector.detect() == Environment.DEVELOPMENT.value
    
    def test_detect_default_production(self):
        """Test default detection returns production"""
        with patch.dict(os.environ, {}, clear=True):
            with patch('storm_logger.integrations.django_config.settings', None):
                assert EnvironmentDetector.detect() == Environment.PRODUCTION.value
    
    def test_detect_django_import_error(self):
        """Test detection when Django import fails"""
        mock_settings = MagicMock()
        # Simulate exception by making settings.configured raise
        type(mock_settings).configured = PropertyMock(side_effect=Exception("Test error"))
        
        with patch('storm_logger.integrations.django_config.settings', mock_settings):
            with patch.dict(os.environ, {"DEBUG": "false"}, clear=True):
                assert EnvironmentDetector.detect() == Environment.PRODUCTION.value


class TestEnvironmentStrategies:
    """Test environment configuration strategies"""
    
    def test_development_strategy(self):
        """Test development strategy configuration"""
        config = DjangoStormLoggerConfig()
        strategy = DevelopmentStrategy()
        
        strategy.apply(config)
        
        assert config.debug_mode is True
        assert config.production_mode is False
        assert config.enable_console is True
        assert config.enable_django_db_logging is True
        assert config.enable_django_template_logging is True
        assert config.enable_error_emails is False
        assert config.enable_test_logging is True
        assert config.log_level == LogLevel.DEBUG
    
    def test_staging_strategy(self):
        """Test staging strategy configuration"""
        config = DjangoStormLoggerConfig()
        strategy = StagingStrategy()
        
        strategy.apply(config)
        
        assert config.debug_mode is False
        assert config.production_mode is True
        assert config.enable_console is False
        assert config.enable_django_db_logging is False
        assert config.enable_django_template_logging is False
        assert config.enable_error_emails is True
        assert config.enable_test_logging is False
        assert config.log_level == LogLevel.INFO
    
    def test_production_strategy(self):
        """Test production strategy configuration"""
        config = DjangoStormLoggerConfig()
        strategy = ProductionStrategy()
        
        strategy.apply(config)
        
        assert config.debug_mode is False
        assert config.production_mode is True
        assert config.enable_console is False
        assert config.enable_django_db_logging is False
        assert config.enable_django_template_logging is False
        assert config.enable_error_emails is True
        assert config.enable_test_logging is False
        assert config.log_level == LogLevel.WARNING
    
    def test_testing_strategy(self):
        """Test testing strategy configuration"""
        config = DjangoStormLoggerConfig()
        strategy = TestingStrategy()
        
        strategy.apply(config)
        
        assert config.debug_mode is True
        assert config.production_mode is False
        assert config.enable_console is True
        assert config.enable_django_db_logging is True
        assert config.enable_django_template_logging is True
        assert config.enable_error_emails is False
        assert config.enable_test_logging is True
        assert config.log_level == LogLevel.DEBUG


class TestEnvironmentStrategyFactory:
    """Test EnvironmentStrategyFactory"""
    
    def test_create_from_enum(self):
        """Test creating strategy from Environment enum"""
        strategy = EnvironmentStrategyFactory.create(Environment.DEVELOPMENT)
        assert isinstance(strategy, DevelopmentStrategy)
        
        strategy = EnvironmentStrategyFactory.create(Environment.STAGING)
        assert isinstance(strategy, StagingStrategy)
        
        strategy = EnvironmentStrategyFactory.create(Environment.PRODUCTION)
        assert isinstance(strategy, ProductionStrategy)
        
        strategy = EnvironmentStrategyFactory.create(Environment.TESTING)
        assert isinstance(strategy, TestingStrategy)
    
    def test_create_from_string(self):
        """Test creating strategy from string"""
        strategy = EnvironmentStrategyFactory.create("development")
        assert isinstance(strategy, DevelopmentStrategy)
        
        strategy = EnvironmentStrategyFactory.create("PRODUCTION")
        assert isinstance(strategy, ProductionStrategy)
    
    def test_create_default_production(self):
        """Test unknown environment raises ValueError"""
        with pytest.raises(ValueError, match="'unknown' is not a valid Environment"):
            EnvironmentStrategyFactory.create("unknown")


class TestAppLogLevelManager:
    """Test AppLogLevelManager class"""
    
    def test_init(self):
        """Test manager initialization"""
        manager = AppLogLevelManager()
        assert manager._app_levels == {}
        assert manager._excluded_apps == []
        assert manager._included_apps == []
    
    def test_set_app_level(self):
        """Test setting app log level"""
        manager = AppLogLevelManager()
        
        manager.set_app_level("myapp", LogLevel.DEBUG)
        assert manager._app_levels["myapp"] == LogLevel.DEBUG
    
    def test_set_app_level_removes_from_excluded(self):
        """Test setting level removes app from excluded list"""
        manager = AppLogLevelManager()
        manager._excluded_apps = ["myapp", "otherapp"]
        
        manager.set_app_level("myapp", LogLevel.INFO)
        
        assert "myapp" not in manager._excluded_apps
        assert "otherapp" in manager._excluded_apps
    
    def test_exclude_app(self):
        """Test excluding an app"""
        manager = AppLogLevelManager()
        
        manager.exclude_app("myapp")
        assert "myapp" in manager._excluded_apps
        
        # Test idempotence
        manager.exclude_app("myapp")
        assert manager._excluded_apps.count("myapp") == 1
    
    def test_include_only(self):
        """Test including only specific apps"""
        manager = AppLogLevelManager()
        
        apps = ["app1", "app2", "app3"]
        manager.include_only(apps)
        
        assert manager._included_apps == apps
        # Ensure it's a copy
        apps.append("app4")
        assert "app4" not in manager._included_apps
    
    def test_get_app_config_excluded(self):
        """Test getting config for excluded app"""
        manager = AppLogLevelManager()
        manager.exclude_app("excluded_app")
        
        config = manager.get_app_config("excluded_app", LogLevel.INFO)
        assert config is None
    
    def test_get_app_config_not_included(self):
        """Test getting config when only specific apps included"""
        manager = AppLogLevelManager()
        manager.include_only(["app1", "app2"])
        
        config = manager.get_app_config("app3", LogLevel.INFO)
        assert config is None
    
    def test_get_app_config_included(self):
        """Test getting config for included app"""
        manager = AppLogLevelManager()
        manager.include_only(["app1", "app2"])
        
        config = manager.get_app_config("app1", LogLevel.INFO)
        assert config == {
            "level": "INFO",
            "propagate": False
        }
    
    def test_get_app_config_with_custom_level(self):
        """Test getting config with custom log level"""
        manager = AppLogLevelManager()
        manager.set_app_level("myapp", LogLevel.DEBUG)
        
        config = manager.get_app_config("myapp", LogLevel.INFO)
        assert config["level"] == "DEBUG"
    
    def test_get_app_config_default_level(self):
        """Test getting config with default level"""
        manager = AppLogLevelManager()
        
        config = manager.get_app_config("myapp", LogLevel.WARNING)
        assert config["level"] == "WARNING"
    
    def test_get_default_app_levels(self):
        """Test getting default app log levels"""
        manager = AppLogLevelManager()
        
        # Debug mode
        levels = manager.get_default_app_levels(debug_mode=True, enable_template_logging=True)
        assert levels[DjangoApps.REQUEST] == LogLevel.ERROR
        assert levels[DjangoApps.SECURITY] == LogLevel.WARNING
        assert levels[DjangoApps.DB_BACKENDS] == LogLevel.DEBUG
        assert levels[DjangoApps.TEMPLATE] == LogLevel.INFO
        assert levels[DjangoApps.STATICFILES] == LogLevel.WARNING
        
        # Production mode
        levels = manager.get_default_app_levels(debug_mode=False, enable_template_logging=False)
        assert levels[DjangoApps.DB_BACKENDS] == LogLevel.INFO
        assert levels[DjangoApps.TEMPLATE] == LogLevel.ERROR
    
    def test_get_default_excluded_apps(self):
        """Test getting default excluded apps"""
        manager = AppLogLevelManager()
        
        excluded = manager.get_default_excluded_apps()
        assert DjangoApps.STATICFILES in excluded
        assert DjangoApps.ADMIN in excluded
        assert DjangoApps.AUTH in excluded
        assert DjangoApps.CONTENTTYPES in excluded
        assert DjangoApps.SESSIONS in excluded
        assert DjangoApps.MESSAGES in excluded


class TestHandlerManager:
    """Test HandlerManager class"""
    
    @pytest.fixture
    def config(self):
        """Create test configuration"""
        return DjangoStormLoggerConfig(
            log_directory=Path("/tmp/logs"),
            enable_console=True,
            console_colors=True
        )
    
    def test_get_handlers_basic(self, config):
        """Test getting basic handlers"""
        config.enable_console = True
        manager = HandlerManager(config)
        
        handlers = manager.get_handlers()
        assert "console" in handlers
        assert "file" in handlers
    
    def test_get_handlers_no_console(self, config):
        """Test handlers without console"""
        config.enable_console = False
        manager = HandlerManager(config)
        
        handlers = manager.get_handlers()
        assert "console" not in handlers
        assert "file" in handlers
    
    def test_get_handlers_with_features(self, config):
        """Test handlers with all features enabled"""
        config.enable_django_request_logging = True
        config.enable_django_security_logging = True
        config.enable_error_emails = True
        config.production_mode = True
        config.separate_test_logs = True
        
        manager = HandlerManager(config)
        handlers = manager.get_handlers()
        
        assert "django_request_file" in handlers
        assert "django_security_file" in handlers
        assert "mail_admins" in handlers
        assert "test_file" in handlers
    
    def test_get_handler_config_file(self, config):
        """Test file handler configuration"""
        manager = HandlerManager(config)
        
        handler_config = manager.get_handler_config("file")
        assert handler_config["class"] == "logging.FileHandler"
        assert handler_config["filename"] == str(config.log_directory / LogFiles.DJANGO)
        assert handler_config["formatter"] == "standard"
    
    def test_get_handler_config_console(self, config):
        """Test console handler configuration"""
        manager = HandlerManager(config)
        
        handler_config = manager.get_handler_config("console")
        assert handler_config["class"] == "logging.StreamHandler"
        assert handler_config["stream"] == "ext://sys.stdout"
        assert handler_config["formatter"] == "colored"
        
        # Test without colors
        config.console_colors = False
        handler_config = manager.get_handler_config("console")
        assert handler_config["formatter"] == "standard"
    
    def test_get_handler_config_django_files(self, config):
        """Test Django-specific file handlers"""
        manager = HandlerManager(config)
        
        # Request handler
        handler_config = manager.get_handler_config("django_request_file")
        assert handler_config["filename"] == str(config.log_directory / "requests" / LogFiles.REQUESTS)
        
        # Security handler
        handler_config = manager.get_handler_config("django_security_file")
        assert handler_config["filename"] == str(config.log_directory / "security" / LogFiles.SECURITY)
        
        # Test handler
        handler_config = manager.get_handler_config("test_file")
        assert handler_config["filename"] == str(config.log_directory / "tests" / LogFiles.TESTS)
    
    def test_get_handler_config_mail_admins(self, config):
        """Test mail admins handler configuration"""
        config.error_email_threshold = LogLevel.CRITICAL
        manager = HandlerManager(config)
        
        handler_config = manager.get_handler_config("mail_admins")
        assert handler_config["class"] == "django.utils.log.AdminEmailHandler"
        assert handler_config["level"] == "CRITICAL"
        assert handler_config["include_html"] is True
    
    def test_get_handler_config_unknown(self, config):
        """Test unknown handler returns empty dict"""
        manager = HandlerManager(config)
        
        handler_config = manager.get_handler_config("unknown_handler")
        assert handler_config == {}


class TestDirectoryManager:
    """Test DirectoryManager class"""
    
    def test_create_directories_none_path(self):
        """Test with None log directory"""
        DirectoryManager.create_directories(None, False)
        # Should not raise any exceptions
    
    def test_create_directories_basic(self, tmp_path):
        """Test creating basic directories"""
        log_dir = tmp_path / "logs"
        
        DirectoryManager.create_directories(log_dir, separate_test_logs=False)
        
        assert log_dir.exists()
        assert (log_dir / "django").exists()
        assert (log_dir / "requests").exists()
        assert (log_dir / "security").exists()
        assert not (log_dir / "tests").exists()
    
    def test_create_directories_with_test_logs(self, tmp_path):
        """Test creating directories with test logs"""
        log_dir = tmp_path / "logs"
        
        DirectoryManager.create_directories(log_dir, separate_test_logs=True)
        
        assert (log_dir / "tests").exists()
    
    def test_create_directories_already_exist(self, tmp_path):
        """Test creating directories that already exist"""
        log_dir = tmp_path / "logs"
        log_dir.mkdir()
        (log_dir / "django").mkdir()
        
        # Should not raise exception
        DirectoryManager.create_directories(log_dir, separate_test_logs=False)
        
        assert log_dir.exists()
        assert (log_dir / "django").exists()


class TestConfigValidator:
    """Test ConfigValidator class"""
    
    def test_validate_log_level_string(self):
        """Test validating log level from string"""
        assert ConfigValidator.validate_log_level("DEBUG") == LogLevel.DEBUG
        assert ConfigValidator.validate_log_level("info") == LogLevel.INFO
        assert ConfigValidator.validate_log_level("WARNING") == LogLevel.WARNING
    
    def test_validate_log_level_enum(self):
        """Test validating log level from enum"""
        assert ConfigValidator.validate_log_level(LogLevel.ERROR) == LogLevel.ERROR
    
    def test_validate_log_level_invalid_string(self):
        """Test invalid log level string"""
        with pytest.raises(ValueError, match="Invalid log level: INVALID"):
            ConfigValidator.validate_log_level("INVALID")
    
    def test_validate_log_level_invalid_type(self):
        """Test invalid log level type"""
        with pytest.raises(TypeError, match="Log level must be string or LogLevel"):
            ConfigValidator.validate_log_level(123)
    
    def test_validate_app_log_levels(self):
        """Test validating app log levels dictionary"""
        app_levels = {
            "app1": "DEBUG",
            "app2": LogLevel.INFO,
            "app3": "warning"
        }
        
        validated = ConfigValidator.validate_app_log_levels(app_levels)
        
        assert validated["app1"] == LogLevel.DEBUG
        assert validated["app2"] == LogLevel.INFO
        assert validated["app3"] == LogLevel.WARNING
    
    def test_validate_app_log_levels_invalid(self):
        """Test invalid app log levels"""
        app_levels = {"app1": "INVALID"}
        
        with pytest.raises(ValueError):
            ConfigValidator.validate_app_log_levels(app_levels)
    
    def test_validate_list_field_none(self):
        """Test validating None list field"""
        result = ConfigValidator.validate_list_field("test_field", None)
        assert result == []
    
    def test_validate_list_field_valid(self):
        """Test validating valid list field"""
        test_list = ["item1", "item2"]
        result = ConfigValidator.validate_list_field("test_field", test_list)
        assert result == test_list
    
    def test_validate_list_field_invalid_type(self):
        """Test invalid list field type"""
        with pytest.raises(TypeError, match="test_field must be a list"):
            ConfigValidator.validate_list_field("test_field", "not_a_list")


class TestDjangoStormLoggerConfig:
    """Test DjangoStormLoggerConfig class"""
    
    @pytest.fixture
    def config(self):
        """Create test configuration"""
        return DjangoStormLoggerConfig()
    
    def test_init_defaults(self, config):
        """Test default initialization"""
        assert config.enable_django_request_logging is True
        assert config.enable_django_security_logging is True
        assert config.enable_django_db_logging is True
        assert config.enable_django_template_logging is False
        assert config.enable_test_logging is True
        assert config.test_log_level == LogLevel.INFO
        assert config.separate_test_logs is True
        assert config.test_log_file == LogFiles.TESTS
        assert config.framework == Framework.DJANGO
    
    def test_post_init_managers(self, config):
        """Test manager initialization in post_init"""
        assert isinstance(config._app_manager, AppLogLevelManager)
        assert isinstance(config._handler_manager, HandlerManager)
    
    def test_post_init_default_app_levels(self):
        """Test default app levels are set"""
        config = DjangoStormLoggerConfig()
        
        assert DjangoApps.REQUEST in config.app_log_levels
        assert DjangoApps.SECURITY in config.app_log_levels
        assert config.app_log_levels[DjangoApps.REQUEST] == LogLevel.ERROR
    
    def test_post_init_custom_app_levels(self):
        """Test custom app levels are preserved"""
        custom_levels = {"myapp": LogLevel.DEBUG}
        config = DjangoStormLoggerConfig(app_log_levels=custom_levels)
        
        assert config.app_log_levels["myapp"] == LogLevel.DEBUG
    
    def test_post_init_default_excluded_apps(self):
        """Test default excluded apps are set"""
        config = DjangoStormLoggerConfig()
        
        assert DjangoApps.STATICFILES in config.excluded_apps
        assert DjangoApps.ADMIN in config.excluded_apps
    
    def test_post_init_custom_excluded_apps(self):
        """Test custom excluded apps are preserved"""
        custom_excluded = ["custom_app"]
        config = DjangoStormLoggerConfig(excluded_apps=custom_excluded)
        
        assert config.excluded_apps == custom_excluded
    
    def test_for_environment_auto(self):
        """Test creating config for auto environment"""
        with patch.object(EnvironmentDetector, 'detect', return_value='development'):
            config = DjangoStormLoggerConfig.for_environment(Environment.AUTO.value)
            
            assert config.debug_mode is True
            assert config.enable_console is True
    
    def test_for_environment_specific(self):
        """Test creating config for specific environment"""
        config = DjangoStormLoggerConfig.for_environment("production")
        
        assert config.debug_mode is False
        assert config.production_mode is True
        assert config.log_level == LogLevel.WARNING
    
    def test_for_production(self):
        """Test production configuration factory"""
        config = DjangoStormLoggerConfig.for_production()
        
        assert config.debug_mode is False
        assert config.production_mode is True
        assert config.enable_error_emails is True
    
    def test_for_development(self):
        """Test development configuration factory"""
        config = DjangoStormLoggerConfig.for_development()
        
        assert config.debug_mode is True
        assert config.production_mode is False
        assert config.enable_console is True
    
    def test_for_testing(self):
        """Test testing configuration factory"""
        config = DjangoStormLoggerConfig.for_testing()
        
        assert config.debug_mode is True
        assert config.enable_test_logging is True
        assert config.log_level == LogLevel.DEBUG
    
    def test_for_staging(self):
        """Test staging configuration factory"""
        config = DjangoStormLoggerConfig.for_staging()
        
        assert config.production_mode is True
        assert config.enable_error_emails is True
        assert config.log_level == LogLevel.INFO
    
    @patch('storm_logger.integrations.django_config.create_django_logging_config')
    def test_for_django_settings(self, mock_create_config, config):
        """Test generating Django settings"""
        mock_create_config.return_value = {"test": "config"}
        
        result = config.for_django_settings()
        
        mock_create_config.assert_called_once_with(config)
        assert result == {"test": "config"}
    
    def test_create_log_directories(self, config, tmp_path):
        """Test creating log directories"""
        config.log_directory = tmp_path / "logs"
        config.separate_test_logs = True
        
        with patch.object(config.__class__.__bases__[0], 'create_log_directories') as mock_parent:
            config.create_log_directories()
            
            mock_parent.assert_called_once()
            assert (config.log_directory / "django").exists()
            assert (config.log_directory / "requests").exists()
            assert (config.log_directory / "security").exists()
            assert (config.log_directory / "tests").exists()
    
    def test_get_handlers(self, config):
        """Test getting handlers list"""
        handlers = config.get_handlers()
        assert isinstance(handlers, list)
    
    def test_get_handler_config(self, config):
        """Test getting handler configuration"""
        handler_config = config.get_handler_config("file")
        assert isinstance(handler_config, dict)
    
    def test_add_app_config(self, config):
        """Test adding app configuration"""
        config.add_app_config("myapp", LogLevel.DEBUG)
        
        assert config.app_log_levels["myapp"] == LogLevel.DEBUG
        assert "myapp" not in config.excluded_apps
    
    def test_exclude_app(self, config):
        """Test excluding an app"""
        config.exclude_app("myapp")
        
        assert "myapp" in config.excluded_apps
    
    def test_include_only_apps(self, config):
        """Test including only specific apps"""
        apps = ["app1", "app2"]
        config.include_only_apps(apps)
        
        assert config.included_apps == apps
    
    def test_get_app_config(self, config):
        """Test getting app configuration"""
        config.add_app_config("myapp", LogLevel.DEBUG)
        
        app_config = config.get_app_config("myapp")
        assert app_config["level"] == "DEBUG"
        assert "handlers" in app_config
    
    def test_validate_success(self, config):
        """Test successful validation"""
        errors = config.validate()
        assert errors == []
    
    def test_validate_invalid_log_level(self):
        """Test validation with invalid log level"""
        config = DjangoStormLoggerConfig()
        config.log_level = "INVALID"
        
        errors = config.validate()
        assert len(errors) > 0
        assert any("Invalid log level: INVALID" in error for error in errors)
    
    def test_validate_invalid_app_log_levels(self):
        """Test validation with invalid app log levels"""
        config = DjangoStormLoggerConfig()
        config.app_log_levels = {"app": "INVALID"}
        
        errors = config.validate()
        assert len(errors) > 0
        assert any("Invalid log level" in error for error in errors)
    
    def test_validate_invalid_excluded_apps_type(self):
        """Test validation with invalid excluded_apps type"""
        config = DjangoStormLoggerConfig()
        config.excluded_apps = "not_a_list"
        
        errors = config.validate()
        assert len(errors) > 0
        assert any("excluded_apps must be a list" in error for error in errors)
    
    def test_validate_parent_errors(self, config):
        """Test validation with parent class errors"""
        with patch.object(config.__class__.__bases__[0], 'validate', return_value=["parent error"]):
            errors = config.validate()
            assert "parent error" in errors
    
    def test_copy(self, config):
        """Test creating a copy of configuration"""
        config.add_app_config("myapp", LogLevel.DEBUG)
        
        config_copy = config.copy()
        
        assert config_copy is not config
        assert config_copy.app_log_levels == config.app_log_levels
        assert config_copy.app_log_levels is not config.app_log_levels
    
    def test_merge_configs(self):
        """Test merging configurations"""
        config1 = DjangoStormLoggerConfig()
        config1.add_app_config("app1", LogLevel.DEBUG)
        config1.excluded_apps = ["excluded1"]
        
        config2 = DjangoStormLoggerConfig()
        config2.add_app_config("app2", LogLevel.INFO)
        config2.excluded_apps = ["excluded2"]
        config2.log_level = LogLevel.ERROR
        
        merged = config1.merge(config2)
        
        assert merged is not config1
        assert merged is not config2
        assert merged.app_log_levels["app1"] == LogLevel.DEBUG
        assert merged.app_log_levels["app2"] == LogLevel.INFO
        assert merged.excluded_apps == ["excluded2"]
        assert merged.log_level == LogLevel.ERROR
    
    def test_update_from_django_settings(self):
        """Test updating from Django settings"""
        mock_settings = MagicMock()
        mock_settings.configured = True
        mock_settings.DEBUG = True
        mock_settings.LOGGING = {
            "loggers": {
                "myapp": {"level": "WARNING"},
                "otherapp": {"level": "error"}
            }
        }
        
        with patch('storm_logger.integrations.django_config.settings', mock_settings):
            config = DjangoStormLoggerConfig()
            updated = config.update_from_django_settings()
            
            assert updated is not config  # Should be a copy
            assert updated.debug_mode is True
            assert updated.production_mode is False
            assert updated.enable_console is True
            assert "myapp" in updated.app_log_levels
            assert updated.app_log_levels["myapp"] == LogLevel.WARNING
            assert "otherapp" in updated.app_log_levels
            assert updated.app_log_levels["otherapp"] == LogLevel.ERROR
    
    def test_update_from_django_settings_no_django(self):
        """Test updating when Django not available"""
        with patch('storm_logger.integrations.django_config.settings', None):
            config = DjangoStormLoggerConfig()
            updated = config.update_from_django_settings()
            
            assert updated is not config  # Should always return a copy
    
    def test_context_manager(self, config):
        """Test context manager support"""
        with config as ctx_config:
            assert ctx_config is config
    
    def test_dataclass_frozen(self):
        """Test that config is not frozen (can be modified)"""
        config = DjangoStormLoggerConfig()
        config.log_level = LogLevel.ERROR  # Should not raise


class TestDjangoConfigManager:
    """Test DjangoConfigManager singleton"""
    
    def test_get_config_creates_default(self):
        """Test getting config creates default instance"""
        DjangoConfigManager.reset()
        
        with patch.object(DjangoStormLoggerConfig, 'for_environment') as mock_for_env:
            mock_config = MagicMock()
            mock_for_env.return_value = mock_config
            
            config = DjangoConfigManager.get_config()
            
            assert config is mock_config
            mock_for_env.assert_called_once_with(Environment.AUTO)
    
    def test_get_config_returns_existing(self):
        """Test getting config returns existing instance"""
        DjangoConfigManager.reset()
        
        config1 = DjangoConfigManager.get_config()
        config2 = DjangoConfigManager.get_config()
        
        assert config1 is config2
    
    def test_get_config_with_custom(self):
        """Test setting custom config"""
        DjangoConfigManager.reset()
        
        custom_config = DjangoStormLoggerConfig()
        result = DjangoConfigManager.get_config(custom_config)
        
        assert result is custom_config
        assert DjangoConfigManager._instance is custom_config
    
    def test_reset(self):
        """Test resetting manager"""
        config = DjangoConfigManager.get_config()
        DjangoConfigManager.reset()
        
        assert DjangoConfigManager._instance is None


class TestPublicAPI:
    """Test public API functions"""
    
    @patch('storm_logger.integrations.django_config.setup_storm_logging')
    @patch.object(DjangoConfigManager, 'get_config')
    def test_configure_django_storm_logging_no_config(self, mock_get_config, mock_setup):
        """Test configuring with no config"""
        mock_config = MagicMock()
        mock_config.validate.return_value = []  # Return empty errors list
        mock_config.for_django_settings.return_value = {"test": "settings"}
        mock_get_config.return_value = mock_config
        
        result = configure_django_storm_logging()
        
        mock_get_config.assert_called_once_with(None)
        mock_config.validate.assert_called_once()
        mock_setup.assert_called_once_with(mock_config)
        assert result == {"test": "settings"}
    
    @patch('storm_logger.integrations.django_config.setup_storm_logging')
    def test_configure_django_storm_logging_with_dict(self, mock_setup):
        """Test configuring with dictionary"""
        config_dict = {"log_level": "ERROR"}
        
        with patch.object(DjangoStormLoggerConfig, 'validate', return_value=[]):  # Return empty errors
            result = configure_django_storm_logging(config_dict)
            
            # Check that config was created from dict
            call_args = mock_setup.call_args[0]
            assert isinstance(call_args[0], DjangoStormLoggerConfig)
            assert call_args[0].log_level == "ERROR"
    
    @patch('storm_logger.integrations.django_config.setup_storm_logging')
    def test_configure_django_storm_logging_with_config(self, mock_setup):
        """Test configuring with config object"""
        config = DjangoStormLoggerConfig()
        config.validate = MagicMock(return_value=[])  # Return empty errors list
        config.for_django_settings = MagicMock(return_value={"django": "config"})
        
        result = configure_django_storm_logging(config)
        
        config.validate.assert_called_once()
        mock_setup.assert_called_once_with(config)
        assert result == {"django": "config"}
    
    def test_configure_django_storm_logging_invalid_config(self):
        """Test configuring with invalid config"""
        config = DjangoStormLoggerConfig()
        config.log_level = "INVALID"
        
        with pytest.raises(ValueError, match="Invalid configuration"):
            configure_django_storm_logging(config)
    
    def test_get_django_config_auto_environment(self):
        """Test getting config with auto environment"""
        with patch.object(DjangoStormLoggerConfig, 'for_environment') as mock_for_env:
            mock_config = MagicMock()
            mock_for_env.return_value = mock_config
            
            result = get_django_config()
            
            mock_for_env.assert_called_once_with(Environment.AUTO.value)
            assert result is mock_config
    
    def test_get_django_config_specific_environment(self):
        """Test getting config with specific environment"""
        with patch.object(DjangoStormLoggerConfig, 'for_environment') as mock_for_env:
            mock_config = MagicMock()
            mock_for_env.return_value = mock_config
            
            result = get_django_config("production", log_level="ERROR")
            
            mock_for_env.assert_called_once_with("production", log_level="ERROR")
            assert result is mock_config
    
    @patch('logging.config.dictConfig')
    @patch('storm_logger.integrations.django_config.configure_django_storm_logging')
    def test_configure_django_logging_backward_compat(self, mock_configure, mock_dict_config):
        """Test backward compatibility function"""
        mock_configure.return_value = {"logging": "config"}
        
        from storm_logger.integrations.django_config import configure_django_logging
        configure_django_logging()
        
        mock_configure.assert_called_once_with(None)
        mock_dict_config.assert_called_once_with({"logging": "config"})


class TestIntegration:
    """Integration tests for the refactored config"""
    
    def test_full_configuration_workflow(self, tmp_path):
        """Test complete configuration workflow"""
        # Create config for development
        config = DjangoStormLoggerConfig.for_development(
            log_directory=tmp_path / "logs",
            enable_business_metrics=True
        )
        
        # Add custom app configuration
        config.add_app_config("myapp.views", LogLevel.DEBUG)
        config.add_app_config("myapp.models", LogLevel.INFO)
        
        # Exclude some apps
        config.exclude_app("django.contrib.staticfiles")
        
        # Validate configuration
        errors = config.validate()
        assert errors == []
        
        # Create directories
        config.create_log_directories()
        assert (tmp_path / "logs" / "django").exists()
        
        # Get Django settings
        django_settings = config.for_django_settings()
        assert isinstance(django_settings, dict)
        
        # Get app configs
        views_config = config.get_app_config("myapp.views")
        assert views_config["level"] == "DEBUG"
        
        static_config = config.get_app_config("django.contrib.staticfiles")
        assert static_config is None
    
    def test_environment_auto_detection(self):
        """Test automatic environment detection"""
        with patch.dict(os.environ, {"DJANGO_ENVIRONMENT": "staging"}):
            config = DjangoStormLoggerConfig.for_environment("auto")
            
            assert config.production_mode is True
            assert config.log_level == LogLevel.INFO
            assert config.enable_error_emails is True
    
    def test_configuration_merging(self):
        """Test merging configurations"""
        # Base config
        base = DjangoStormLoggerConfig.for_production()
        
        # Override config
        override = DjangoStormLoggerConfig()
        override.enable_console = True
        override.add_app_config("critical.app", LogLevel.CRITICAL)
        
        # Merge
        final = base.merge(override)
        
        # Check merged values
        assert final.production_mode is True  # From base
        assert final.enable_console is True   # From override
        assert final.app_log_levels["critical.app"] == LogLevel.CRITICAL
    
    def test_django_settings_integration(self):
        """Test integration with Django settings"""
        mock_settings = MagicMock()
        mock_settings.configured = True
        mock_settings.DEBUG = False
        mock_settings.TESTING = True
        
        with patch('storm_logger.integrations.django_config.settings', mock_settings):
            # Create config with auto environment
            config = DjangoStormLoggerConfig.for_environment("auto")
            
            # Should detect testing environment
            assert config.debug_mode is True
            assert config.enable_test_logging is True
            
            # Update from Django settings
            mock_settings.LOGGING = {
                "loggers": {
                    "django.db": {"level": "DEBUG"}
                }
            }
            
            updated = config.update_from_django_settings()
            assert "django.db" in updated.app_log_levels
            assert updated.app_log_levels["django.db"] == LogLevel.DEBUG