"""
Django Storm Logger Configuration
Maintainable, extensible configuration for Django integration
"""

import os
from dataclasses import dataclass, field
from enum import Enum
from pathlib import Path
from typing import Any, Literal, Protocol, TypeVar

from ..config import Framework, LogLevel, StormLoggerConfig
from ..core.logger import setup_storm_logging
from .django import create_django_logging_config

# For Django settings patching in tests
try:
    from django.conf import settings
except ImportError:
    settings = None  # type: ignore[assignment]

# Type definitions
T = TypeVar("T")
ConfigDict = dict[str, Any]


class Environment(Enum):
    """Supported environment types"""

    DEVELOPMENT = "development"
    STAGING = "staging"
    PRODUCTION = "production"
    TESTING = "testing"
    AUTO = "auto"


class FeatureFlags:
    """Feature flags for Django logging features"""

    REQUEST_LOGGING = "enable_django_request_logging"
    SECURITY_LOGGING = "enable_django_security_logging"
    DB_LOGGING = "enable_django_db_logging"
    TEMPLATE_LOGGING = "enable_django_template_logging"
    TEST_LOGGING = "enable_test_logging"
    ERROR_EMAILS = "enable_error_emails"
    ADMIN_INTEGRATION = "enable_admin_integration"


class LogFiles:
    """Standard log file names"""

    DJANGO = "django.log"
    REQUESTS = "requests.log"
    SECURITY = "security.log"
    TESTS = "tests.log"


class DjangoApps:
    """Common Django app names"""

    REQUEST = "django.request"
    SECURITY = "django.security"
    DB_BACKENDS = "django.db.backends"
    TEMPLATE = "django.template"
    STATICFILES = "django.contrib.staticfiles"
    ADMIN = "django.contrib.admin"
    AUTH = "django.contrib.auth"
    CONTENTTYPES = "django.contrib.contenttypes"
    SESSIONS = "django.contrib.sessions"
    MESSAGES = "django.contrib.messages"


class EnvironmentDetector:
    """Detects the current environment"""

    @staticmethod
    def detect() -> str:
        """Auto-detect the current environment"""
        # Check DJANGO_ENVIRONMENT first
        if env := os.getenv("DJANGO_ENVIRONMENT"):
            return env.lower()

        # Check Django settings if available
        if settings is not None:
            try:
                if hasattr(settings, "configured") and settings.configured:
                    if hasattr(settings, "DEBUG") and settings.DEBUG:
                        return Environment.DEVELOPMENT.value
                    elif hasattr(settings, "TESTING") and settings.TESTING:
                        return Environment.TESTING.value
                    else:
                        return Environment.PRODUCTION.value
            except Exception:
                pass

        # Fall back to DEBUG environment variable
        if os.getenv("DEBUG", "false").lower() == "true":
            return Environment.DEVELOPMENT.value

        return Environment.PRODUCTION.value


class ConfigurationStrategy(Protocol):
    """Protocol for environment-specific configuration strategies"""

    def apply(self, config: "DjangoStormLoggerConfig") -> None:
        """Apply environment-specific settings to config"""
        ...


@dataclass
class EnvironmentConfig:
    """Environment-specific configuration settings"""

    debug_mode: bool
    production_mode: bool
    enable_console: bool
    enable_django_db_logging: bool
    enable_django_template_logging: bool
    enable_error_emails: bool
    enable_test_logging: bool
    log_level: LogLevel


class DevelopmentStrategy:
    """Configuration strategy for development environment"""

    def apply(self, config: "DjangoStormLoggerConfig") -> None:
        config.debug_mode = True
        config.production_mode = False
        config.enable_console = True
        config.enable_django_db_logging = True
        config.enable_django_template_logging = True
        config.enable_error_emails = False
        config.enable_test_logging = True
        config.log_level = LogLevel.DEBUG


class StagingStrategy:
    """Configuration strategy for staging environment"""

    def apply(self, config: "DjangoStormLoggerConfig") -> None:
        config.debug_mode = False
        config.production_mode = True
        config.enable_console = False
        config.enable_django_db_logging = False
        config.enable_django_template_logging = False
        config.enable_error_emails = True
        config.enable_test_logging = False
        config.log_level = LogLevel.INFO


class ProductionStrategy:
    """Configuration strategy for production environment"""

    def apply(self, config: "DjangoStormLoggerConfig") -> None:
        config.debug_mode = False
        config.production_mode = True
        config.enable_console = False
        config.enable_django_db_logging = False
        config.enable_django_template_logging = False
        config.enable_error_emails = True
        config.enable_test_logging = False
        config.log_level = LogLevel.WARNING


class TestingStrategy:
    """Configuration strategy for testing environment"""

    def apply(self, config: "DjangoStormLoggerConfig") -> None:
        config.debug_mode = True
        config.production_mode = False
        config.enable_console = True
        config.enable_django_db_logging = True
        config.enable_django_template_logging = True
        config.enable_error_emails = False
        config.enable_test_logging = True
        config.log_level = LogLevel.DEBUG


class EnvironmentStrategyFactory:
    """Factory for creating environment strategies"""

    _strategies = {
        Environment.DEVELOPMENT: DevelopmentStrategy,
        Environment.STAGING: StagingStrategy,
        Environment.PRODUCTION: ProductionStrategy,
        Environment.TESTING: TestingStrategy,
    }

    @classmethod
    def create(cls, environment: str | Environment) -> ConfigurationStrategy:
        """Create strategy for the given environment"""
        if isinstance(environment, str):
            environment = Environment(environment.lower())

        strategy_class = cls._strategies.get(environment, ProductionStrategy)
        from typing import cast

        return cast(ConfigurationStrategy, strategy_class())


class AppLogLevelManager:
    """Manages log levels for Django apps"""

    def __init__(self) -> None:
        self._app_levels: dict[str, LogLevel] = {}
        self._excluded_apps: list[str] = []
        self._included_apps: list[str] = []

    def set_app_level(self, app_name: str, level: LogLevel) -> None:
        """Set log level for an app"""
        self._app_levels[app_name] = level
        # Remove from excluded if present
        if app_name in self._excluded_apps:
            self._excluded_apps.remove(app_name)

    def exclude_app(self, app_name: str) -> None:
        """Exclude an app from logging"""
        if app_name not in self._excluded_apps:
            self._excluded_apps.append(app_name)

    def include_only(self, app_names: list[str]) -> None:
        """Include only specific apps"""
        self._included_apps = app_names.copy()

    def get_app_config(self, app_name: str, default_level: LogLevel) -> dict[str, Any] | None:
        """Get configuration for an app"""
        # Check if excluded
        if app_name in self._excluded_apps:
            return None

        # Check if only specific apps are included
        if self._included_apps and app_name not in self._included_apps:
            return None

        # Get log level
        level = self._app_levels.get(app_name, default_level)

        return {
            "level": level.value,
            "propagate": False,
        }

    def get_default_app_levels(
        self, debug_mode: bool, enable_template_logging: bool
    ) -> dict[str, LogLevel]:
        """Get default app log levels"""
        return {
            DjangoApps.REQUEST: LogLevel.ERROR,
            DjangoApps.SECURITY: LogLevel.WARNING,
            DjangoApps.DB_BACKENDS: LogLevel.DEBUG if debug_mode else LogLevel.INFO,
            DjangoApps.TEMPLATE: LogLevel.INFO if enable_template_logging else LogLevel.ERROR,
            DjangoApps.STATICFILES: LogLevel.WARNING,
        }

    def get_default_excluded_apps(self) -> list[str]:
        """Get default excluded apps"""
        return [
            DjangoApps.STATICFILES,
            DjangoApps.ADMIN,
            DjangoApps.AUTH,
            DjangoApps.CONTENTTYPES,
            DjangoApps.SESSIONS,
            DjangoApps.MESSAGES,
        ]


class HandlerManager:
    """Manages log handlers configuration"""

    def __init__(self, config: "DjangoStormLoggerConfig") -> None:
        self.config = config

    def get_handlers(self) -> list[str]:
        """Get list of enabled handlers"""
        handlers = ["console", "file"] if self.config.enable_console else ["file"]

        if self.config.enable_django_request_logging:
            handlers.append("django_request_file")

        if self.config.enable_django_security_logging:
            handlers.append("django_security_file")

        if self.config.enable_error_emails and self.config.production_mode:
            handlers.append("mail_admins")

        if self.config.separate_test_logs:
            handlers.append("test_file")

        return handlers

    def get_handler_config(self, handler_name: str) -> dict[str, Any]:
        """Get configuration for a specific handler"""
        handler_configs: dict[str, dict[str, Any]] = {
            "file": {
                "class": "logging.FileHandler",
                "filename": str((self.config.log_directory or Path(".")) / LogFiles.DJANGO),
                "formatter": "standard",
            },
            "console": {
                "class": "logging.StreamHandler",
                "stream": "ext://sys.stdout",
                "formatter": "colored" if self.config.console_colors else "standard",
            },
            "django_request_file": {
                "class": "logging.FileHandler",
                "filename": str(
                    (self.config.log_directory or Path(".")) / "requests" / LogFiles.REQUESTS
                ),
                "formatter": "standard",
            },
            "django_security_file": {
                "class": "logging.FileHandler",
                "filename": str(
                    (self.config.log_directory or Path(".")) / "security" / LogFiles.SECURITY
                ),
                "formatter": "standard",
            },
            "test_file": {
                "class": "logging.FileHandler",
                "filename": str(
                    (self.config.log_directory or Path(".")) / "tests" / LogFiles.TESTS
                ),
                "formatter": "standard",
            },
            "mail_admins": {
                "class": "django.utils.log.AdminEmailHandler",
                "level": self.config.error_email_threshold.value,
                "include_html": True,
            },
        }

        config = handler_configs.get(handler_name, {})
        return config


class DirectoryManager:
    """Manages log directory creation"""

    @staticmethod
    def create_directories(log_directory: Path, separate_test_logs: bool) -> None:
        """Create all required log directories"""
        if not log_directory:
            return

        # Create base directory
        log_directory.mkdir(parents=True, exist_ok=True)

        # Create subdirectories
        subdirs = ["django", "requests", "security"]
        if separate_test_logs:
            subdirs.append("tests")

        for subdir in subdirs:
            (log_directory / subdir).mkdir(exist_ok=True)


class ConfigValidator:
    """Validates configuration"""

    @staticmethod
    def validate_log_level(level: str | LogLevel) -> LogLevel:
        """Validate and convert log level"""
        if isinstance(level, LogLevel):
            return level
        elif isinstance(level, str):
            try:
                return LogLevel(level.upper())
            except ValueError as err:
                raise ValueError(f"Invalid log level: {level}") from err
        else:
            raise TypeError(f"Log level must be string or LogLevel, got {type(level)}")

    @staticmethod
    def validate_app_log_levels(app_levels: dict[str, str | LogLevel]) -> dict[str, LogLevel]:
        """Validate app log levels"""
        validated = {}
        for app_name, level in app_levels.items():
            validated[app_name] = ConfigValidator.validate_log_level(level)
        return validated

    @staticmethod
    def validate_list_field(field_name: str, value: Any) -> list[Any]:
        """Validate list fields"""
        if value is None:
            return []
        if not isinstance(value, list):
            raise TypeError(f"{field_name} must be a list, got {type(value)}")
        return value


@dataclass
class DjangoStormLoggerConfig(StormLoggerConfig):
    """
    Django-specific Storm Logger configuration.

    This sophisticated configuration class provides granular control
    over Django logging with Storm Logger integration. It supports
    environment-specific settings, app-level configuration, and
    comprehensive Django integration features.
    """

    # Django feature flags
    enable_django_request_logging: bool = True
    enable_django_security_logging: bool = True
    enable_django_db_logging: bool = True
    enable_django_template_logging: bool = False

    # Test configuration
    enable_test_logging: bool = True
    test_log_level: LogLevel = LogLevel.INFO
    separate_test_logs: bool = True
    test_log_file: str = LogFiles.TESTS

    # Business intelligence
    track_user_actions: bool = True
    track_performance_metrics: bool = True
    track_business_events: bool = True
    track_request_metrics: bool = True

    # App configuration
    app_log_levels: dict[str, LogLevel] = field(default_factory=dict)
    excluded_apps: list[str] = field(default_factory=list)
    included_apps: list[str] = field(default_factory=list)

    # Django integrations
    enable_admin_integration: bool = True
    admin_log_viewer: bool = True
    django_debug_toolbar_integration: bool = True

    # Error handling
    enable_error_emails: bool = True
    error_email_threshold: LogLevel = LogLevel.ERROR

    # File names
    django_log_file: str = LogFiles.DJANGO
    request_log_file: str = LogFiles.REQUESTS
    security_log_file: str = LogFiles.SECURITY

    # Internal managers
    _app_manager: AppLogLevelManager = field(default_factory=AppLogLevelManager, init=False)
    _handler_manager: HandlerManager | None = field(default=None, init=False)

    def __post_init__(self) -> None:
        """Initialize configuration"""
        super().__post_init__()

        # Force framework to Django
        self.framework = Framework.DJANGO

        # Initialize managers
        self._app_manager = AppLogLevelManager()
        self._handler_manager = HandlerManager(self)

        # Set up default app levels
        if not self.app_log_levels:
            self.app_log_levels = self._app_manager.get_default_app_levels(
                self.debug_mode, self.enable_django_template_logging
            )

        # Set up default excluded apps
        if not self.excluded_apps:
            self.excluded_apps = self._app_manager.get_default_excluded_apps()

    # Factory methods
    @classmethod
    def for_environment(
        cls, environment: str | Environment, **kwargs: Any
    ) -> "DjangoStormLoggerConfig":
        """Create configuration for specific environment"""
        config = cls(**kwargs)

        # Auto-detect if needed
        if environment == Environment.AUTO.value:
            environment = EnvironmentDetector.detect()

        # Apply environment strategy
        strategy = EnvironmentStrategyFactory.create(environment)
        strategy.apply(config)

        return config

    @classmethod
    def for_production(cls, **kwargs: Any) -> "DjangoStormLoggerConfig":
        """Create production configuration"""
        return cls.for_environment(Environment.PRODUCTION, **kwargs)

    @classmethod
    def for_development(cls, **kwargs: Any) -> "DjangoStormLoggerConfig":
        """Create development configuration"""
        return cls.for_environment(Environment.DEVELOPMENT, **kwargs)

    @classmethod
    def for_testing(cls, **kwargs: Any) -> "DjangoStormLoggerConfig":
        """Create testing configuration"""
        return cls.for_environment(Environment.TESTING, **kwargs)

    @classmethod
    def for_staging(cls, **kwargs: Any) -> "DjangoStormLoggerConfig":
        """Create staging configuration"""
        return cls.for_environment(Environment.STAGING, **kwargs)

    # Configuration methods
    def for_django_settings(self) -> dict[str, Any]:
        """Generate Django LOGGING configuration"""
        return create_django_logging_config(self)

    def create_log_directories(self) -> None:
        """Create all required log directories"""
        super().create_log_directories()
        if self.log_directory:
            DirectoryManager.create_directories(self.log_directory, self.separate_test_logs)

    def get_handlers(self) -> list[str]:
        """Get list of enabled handlers"""
        if self._handler_manager:
            return self._handler_manager.get_handlers()
        return []

    def get_handler_config(self, handler_name: str) -> dict[str, Any]:
        """Get handler configuration"""
        if self._handler_manager:
            return self._handler_manager.get_handler_config(handler_name)
        return {}

    # App management
    def add_app_config(self, app_name: str, log_level: LogLevel) -> None:
        """Add configuration for a Django app"""
        self._app_manager.set_app_level(app_name, log_level)
        self.app_log_levels[app_name] = log_level

    def exclude_app(self, app_name: str) -> None:
        """Exclude an app from logging"""
        self._app_manager.exclude_app(app_name)
        if app_name not in self.excluded_apps:
            self.excluded_apps.append(app_name)

    def include_only_apps(self, app_names: list[str]) -> None:
        """Include only specific apps"""
        self._app_manager.include_only(app_names)
        self.included_apps = app_names.copy()

    def get_app_config(self, app_name: str) -> dict[str, Any] | None:
        """Get configuration for an app"""
        config = self._app_manager.get_app_config(app_name, self.log_level)
        if config:
            config["handlers"] = self.get_handlers()
        return config

    # Validation
    def validate(self) -> list[str]:
        """Validate configuration and return list of errors."""
        errors = []

        try:
            # Validate log levels
            self.log_level = ConfigValidator.validate_log_level(self.log_level)
            self.test_log_level = ConfigValidator.validate_log_level(self.test_log_level)
            self.error_email_threshold = ConfigValidator.validate_log_level(
                self.error_email_threshold
            )
        except (ValueError, TypeError) as e:
            errors.append(str(e))

        # Validate app log levels
        if self.app_log_levels:
            try:
                from typing import cast

                self.app_log_levels = ConfigValidator.validate_app_log_levels(
                    cast(dict[str, str | LogLevel], self.app_log_levels)
                )
            except (ValueError, TypeError) as e:
                errors.append(str(e))

        # Validate lists
        try:
            self.excluded_apps = ConfigValidator.validate_list_field(
                "excluded_apps", self.excluded_apps
            )
            self.included_apps = ConfigValidator.validate_list_field(
                "included_apps", self.included_apps
            )
        except TypeError as e:
            errors.append(str(e))

        # Parent validation
        parent_errors = super().validate()
        errors.extend(parent_errors)

        return errors

    # Utility methods
    def copy(self) -> "DjangoStormLoggerConfig":
        """Create a deep copy of configuration"""
        import copy

        return copy.deepcopy(self)

    def merge(self, other: "DjangoStormLoggerConfig") -> "DjangoStormLoggerConfig":
        """Merge another config into new config"""

        new_config = self.copy()

        # Merge each field
        for field_name in other.__dataclass_fields__:
            other_value = getattr(other, field_name)

            # Special handling for dicts
            if field_name == "app_log_levels" and isinstance(other_value, dict):
                current_value = getattr(new_config, field_name, {})
                merged_dict = {**current_value, **other_value}
                setattr(new_config, field_name, merged_dict)
            # Special handling for lists
            elif field_name in ["excluded_apps", "included_apps"] and isinstance(other_value, list):
                # Replace lists entirely from other
                setattr(new_config, field_name, other_value.copy())
            else:
                # Direct assignment for other fields
                setattr(new_config, field_name, other_value)

        return new_config

    def update_from_django_settings(self) -> "DjangoStormLoggerConfig":
        """Update configuration from Django settings"""
        # Create a copy
        new_config = self.copy()

        if settings is None:
            return new_config  # type: ignore[unreachable]

        if not (hasattr(settings, "configured") and settings.configured):
            return new_config

        # Update from Django settings
        if hasattr(settings, "DEBUG"):
            new_config.debug_mode = settings.DEBUG
            new_config.production_mode = not settings.DEBUG
            new_config.enable_console = settings.DEBUG

        # Extract from LOGGING config if present
        if hasattr(settings, "LOGGING") and "loggers" in settings.LOGGING:
            for logger_name, logger_config in settings.LOGGING["loggers"].items():
                if "level" in logger_config:
                    try:
                        level = LogLevel(logger_config["level"].upper())
                        new_config.add_app_config(logger_name, level)
                    except ValueError:
                        pass

        return new_config

    # Context manager support
    def __enter__(self) -> "DjangoStormLoggerConfig":
        """Enter context manager"""
        return self

    def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> Literal[False]:
        """Exit context manager"""
        return False

    # Legacy methods for backward compatibility
    def customize_for_environment(self, environment: str) -> None:
        """
        Customize configuration for specific environments.

        Deprecated: Use for_environment() factory method instead.
        """
        # Apply environment strategy
        strategy = EnvironmentStrategyFactory.create(environment)
        strategy.apply(self)

    def get_django_handlers(self) -> list[str]:
        """
        Get Django-specific handlers.

        Deprecated: Use get_handlers() instead.
        """
        return self.get_handlers()

    def create_django_log_directories(self) -> None:
        """
        Create Django-specific log directories.

        Deprecated: Use create_log_directories() instead.
        """
        self.create_log_directories()

    def get_app_logger_config(self, app_name: str) -> dict[str, Any] | None:
        """
        Get logger configuration for a specific Django app.

        Deprecated: Use get_app_config() instead.
        """
        return self.get_app_config(app_name)

    def add_custom_app_config(
        self, app_name: str, log_level: LogLevel, _handlers: list[str] | None = None
    ) -> None:
        """
        Add custom configuration for a Django app.

        Deprecated: Use add_app_config() instead.
        """
        self.add_app_config(app_name, log_level)


# Configuration manager singleton
class DjangoConfigManager:
    """Manages Django configuration instances"""

    _instance: DjangoStormLoggerConfig | None = None

    @classmethod
    def get_config(cls, config: DjangoStormLoggerConfig | None = None) -> DjangoStormLoggerConfig:
        """Get or create configuration instance"""
        if config is not None:
            cls._instance = config
        elif cls._instance is None:
            cls._instance = DjangoStormLoggerConfig.for_environment(Environment.AUTO)
        return cls._instance

    @classmethod
    def reset(cls) -> None:
        """Reset configuration instance"""
        cls._instance = None


# Public API functions
def configure_django_storm_logging(
    config: DjangoStormLoggerConfig | dict[str, Any] | None = None,
) -> dict[str, Any]:
    """
    Configure Django Storm Logger.

    Args:
        config: Configuration object, dict, or None for auto-detection

    Returns:
        Django LOGGING configuration dict
    """
    # Convert dict to config object if needed
    if isinstance(config, dict):
        config = DjangoStormLoggerConfig(**config)

    # Get or create config
    config = DjangoConfigManager.get_config(config)

    # Validate
    errors = config.validate()
    if errors:
        raise ValueError(f"Invalid configuration: {', '.join(errors)}")

    # Setup Storm logging
    setup_storm_logging(config)

    # Return Django LOGGING dict
    return config.for_django_settings()


def get_django_config(environment: str | None = None, **kwargs: Any) -> DjangoStormLoggerConfig:
    """
    Get Django configuration for environment.

    Args:
        environment: Environment name or None for auto-detection
        **kwargs: Additional configuration parameters

    Returns:
        Configured DjangoStormLoggerConfig instance
    """
    if environment is None:
        environment = Environment.AUTO.value

    return DjangoStormLoggerConfig.for_environment(environment, **kwargs)


# Backward compatibility
def configure_django_logging(config: DjangoStormLoggerConfig | None = None) -> None:
    """Configure Django logging (backward compatibility)"""
    django_config = configure_django_storm_logging(config)

    # Apply to Django logging
    import logging.config

    logging.config.dictConfig(django_config)


# Legacy global instance getter (backward compatibility)
_django_config = None


def get_django_config_old(
    config: DjangoStormLoggerConfig | None = None,
) -> DjangoStormLoggerConfig:
    """
    Get or create Django configuration (backward compatibility).

    Deprecated: Use get_django_config() or DjangoConfigManager.get_config() instead.
    """
    global _django_config  # noqa: PLW0603
    if config is not None:
        _django_config = config
    elif _django_config is None:
        _django_config = DjangoStormLoggerConfig()
    return _django_config
