"""
Storm Logger Configuration System
Modern configuration management for Storm Development logging
"""

import importlib.util
import logging
import os
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from enum import Enum
from pathlib import Path
from typing import Any


class LogLevel(str, Enum):
    """Supported log levels for Storm Logger."""

    DEBUG = "DEBUG"
    INFO = "INFO"
    WARNING = "WARNING"
    ERROR = "ERROR"
    CRITICAL = "CRITICAL"


class Framework(str, Enum):
    """Supported framework integrations."""

    DJANGO = "django"
    FLASK = "flask"
    FASTAPI = "fastapi"
    STANDALONE = "standalone"


class StormTheme(str, Enum):
    """Available Storm console themes."""

    DEFAULT = "default"
    PROFESSIONAL = "professional"
    MINIMAL = "minimal"
    ENTERPRISE = "enterprise"


# Abstraction interfaces for better testability


class EnvironmentReader(ABC):
    """Abstract interface for reading environment variables."""

    @abstractmethod
    def get(self, key: str, default: str | None = None) -> str | None:
        """Get environment variable value."""
        pass


class FrameworkDetector(ABC):
    """Abstract interface for detecting installed frameworks."""

    @abstractmethod
    def detect(self) -> Framework:
        """Detect which framework is currently available."""
        pass


class FileSystemOps(ABC):
    """Abstract interface for file system operations."""

    @abstractmethod
    def mkdir(self, path: Path, parents: bool = False, exist_ok: bool = False) -> None:
        """Create directory."""
        pass

    @abstractmethod
    def touch(self, path: Path) -> None:
        """Create empty file."""
        pass

    @abstractmethod
    def unlink(self, path: Path) -> None:
        """Delete file."""
        pass

    @abstractmethod
    def exists(self, path: Path) -> bool:
        """Check if path exists."""
        pass


# Concrete implementations


class OSEnvironmentReader(EnvironmentReader):
    """Real environment reader using os.getenv."""

    def get(self, key: str, default: str | None = None) -> str | None:
        return os.getenv(key, default)


class ImportBasedFrameworkDetector(FrameworkDetector):
    """Detects frameworks by trying to import them."""

    def detect(self) -> Framework:
        if importlib.util.find_spec("django") is not None:
            return Framework.DJANGO

        if importlib.util.find_spec("flask") is not None:
            return Framework.FLASK

        if importlib.util.find_spec("fastapi") is not None:
            return Framework.FASTAPI

        return Framework.STANDALONE


class RealFileSystemOps(FileSystemOps):
    """Real file system operations."""

    def mkdir(self, path: Path | str, parents: bool = False, exist_ok: bool = False) -> None:
        # Ensure path is a Path object
        if isinstance(path, str):
            path = Path(path)
        path.mkdir(parents=parents, exist_ok=exist_ok)

    def touch(self, path: Path) -> None:
        path.touch()

    def unlink(self, path: Path) -> None:
        path.unlink()

    def exists(self, path: Path) -> bool:
        return path.exists()


@dataclass
class StormLoggerConfig:
    """
    Comprehensive configuration for Storm Logger.

    This class provides a modern, type-safe configuration system that supports
    environment variables, default values, and validation.
    """

    # Core logging settings
    log_level: LogLevel = LogLevel.INFO
    log_directory: Path | None = None
    max_file_size: int = 10 * 1024 * 1024  # 10MB
    backup_count: int = 5

    # Console settings
    enable_console: bool = True
    console_colors: bool = True
    storm_theme: StormTheme = StormTheme.DEFAULT

    # Business intelligence features
    enable_business_metrics: bool = True
    enable_context_detection: bool = True
    enable_professional_formatting: bool = True
    enable_roi_analysis: bool = True

    # Framework integration
    framework: Framework | None = None
    framework_config: dict[str, Any] = field(default_factory=dict)

    # Security and privacy
    sanitize_logs: bool = True
    detect_pii: bool = True
    encrypt_logs: bool = False

    # Performance settings
    enable_async_logging: bool = False
    batch_size: int = 100
    flush_interval: float = 5.0

    # File handling
    enable_file_logging: bool = True
    log_rotation: bool = True
    compression: bool = False
    archive_directory: Path | None = None

    # Custom handlers and formatters
    custom_handlers: list[str] = field(default_factory=list)
    custom_formatters: list[str] = field(default_factory=list)

    # Environment-specific settings
    debug_mode: bool = False
    production_mode: bool = False

    # Dependency injection fields (not part of public API)
    _env_reader: EnvironmentReader | None = field(default=None, init=False, repr=False)
    _framework_detector: FrameworkDetector | None = field(default=None, init=False, repr=False)
    _fs_ops: FileSystemOps | None = field(default=None, init=False, repr=False)

    def __post_init__(self) -> None:
        """Validate and normalize configuration after initialization."""
        # Initialize dependencies if not injected
        if self._env_reader is None:
            self._env_reader = OSEnvironmentReader()
        if self._framework_detector is None:
            self._framework_detector = ImportBasedFrameworkDetector()
        if self._fs_ops is None:
            self._fs_ops = RealFileSystemOps()

        # Perform initialization steps
        self._initialize_debug_mode()
        self._initialize_log_directory()
        self._normalize_paths()
        self._validate_log_level()
        self._detect_framework_if_needed()

    def _initialize_debug_mode(self) -> None:
        """Initialize debug mode from environment if not explicitly set."""
        if not self.production_mode and not self.debug_mode:
            debug_val = self._env_reader.get("DEBUG") if self._env_reader else None
            self.debug_mode = (debug_val or "false").lower() == "true"
            self.production_mode = not self.debug_mode

    def _initialize_log_directory(self) -> None:
        """Set default log directory if not specified."""
        if self.log_directory is None:
            self.log_directory = Path.cwd() / "logs"

    def _normalize_paths(self) -> None:
        """Convert string paths to Path objects."""
        # Convert log_directory if it's a string
        if isinstance(self.log_directory, str):
            self.log_directory = Path(self.log_directory)  # type: ignore[unreachable]

        # Convert archive_directory if it's a string
        if isinstance(self.archive_directory, str):
            self.archive_directory = Path(self.archive_directory)  # type: ignore[unreachable]

    def _validate_log_level(self) -> None:
        """Validate and normalize log level."""
        if isinstance(self.log_level, str):
            self.log_level = LogLevel(self.log_level.upper())

    def _detect_framework_if_needed(self) -> None:
        """Auto-detect framework if not specified."""
        if self.framework is None and self._framework_detector is not None:
            self.framework = self._framework_detector.detect()

    @classmethod
    def from_env(cls, env_reader: EnvironmentReader | None = None) -> "StormLoggerConfig":
        """Create configuration from environment variables."""
        if env_reader is None:
            env_reader = OSEnvironmentReader()

        log_dir_str = env_reader.get("STORM_LOG_DIR")

        config = cls(
            log_level=LogLevel((env_reader.get("STORM_LOG_LEVEL") or "INFO").upper()),
            log_directory=Path(log_dir_str) if log_dir_str else None,
            max_file_size=int(env_reader.get("STORM_MAX_FILE_SIZE") or "10485760"),  # 10MB
            backup_count=int(env_reader.get("STORM_BACKUP_COUNT") or "5"),
            enable_console=(env_reader.get("STORM_ENABLE_CONSOLE") or "true").lower() == "true",
            console_colors=(env_reader.get("STORM_CONSOLE_COLORS") or "true").lower() == "true",
            storm_theme=StormTheme(env_reader.get("STORM_THEME") or "default"),
            enable_business_metrics=(env_reader.get("STORM_BUSINESS_METRICS") or "true").lower()
            == "true",
            enable_context_detection=(env_reader.get("STORM_CONTEXT_DETECTION") or "true").lower()
            == "true",
            enable_professional_formatting=(
                env_reader.get("STORM_PROFESSIONAL_FORMAT") or "true"
            ).lower()
            == "true",
            sanitize_logs=(env_reader.get("STORM_SANITIZE_LOGS") or "true").lower() == "true",
            detect_pii=(env_reader.get("STORM_DETECT_PII") or "true").lower() == "true",
            encrypt_logs=(env_reader.get("STORM_ENCRYPT_LOGS") or "false").lower() == "true",
            enable_async_logging=(env_reader.get("STORM_ASYNC_LOGGING") or "false").lower()
            == "true",
            debug_mode=(env_reader.get("DEBUG") or "false").lower() == "true",
            production_mode=(env_reader.get("STORM_PRODUCTION") or "false").lower() == "true",
        )

        # Set the env_reader dependency
        config._env_reader = env_reader
        return config

    @classmethod
    def from_dict(cls, config_dict: dict[str, Any]) -> "StormLoggerConfig":
        """Create configuration from dictionary."""
        # Filter out keys that don't exist in the dataclass
        valid_keys = {field.name for field in cls.__dataclass_fields__.values()}
        filtered_config = {k: v for k, v in config_dict.items() if k in valid_keys}

        return cls(**filtered_config)

    def to_dict(self) -> dict[str, Any]:
        """Convert configuration to dictionary."""
        result: dict[str, Any] = {}
        for field_name, _field_info in self.__dataclass_fields__.items():
            value = getattr(self, field_name)

            # Convert Path objects to strings
            if isinstance(value, Path):
                result[field_name] = str(value)
            # Convert Enum values to their string representation
            elif isinstance(value, Enum):
                result[field_name] = value.value
            else:
                result[field_name] = value

        return result

    def get_log_handlers(self) -> list[str]:
        """Get list of enabled log handlers based on configuration."""
        handlers: list[str] = []

        if self.enable_console:
            handlers.append("console")

        # Only add file handlers if file logging is enabled AND we have a directory
        if self.enable_file_logging and self.log_directory:
            handlers.extend(["file", "error_file"])

            if self.debug_mode:
                handlers.append("debug_file")

        if self.production_mode and self.framework == Framework.DJANGO:
            handlers.extend(["mail_admins", "syslog"])

        return handlers

    def get_logging_level(self) -> int:
        """Get Python logging level from configuration."""
        level_map = {
            LogLevel.DEBUG: logging.DEBUG,
            LogLevel.INFO: logging.INFO,
            LogLevel.WARNING: logging.WARNING,
            LogLevel.ERROR: logging.ERROR,
            LogLevel.CRITICAL: logging.CRITICAL,
        }
        return level_map[self.log_level]

    def create_log_directories(self) -> None:
        """Create necessary log directories."""
        if self.log_directory and self._fs_ops is not None:
            try:
                self._fs_ops.mkdir(self.log_directory, parents=True, exist_ok=True)

                # Create subdirectories
                self._fs_ops.mkdir(self.log_directory / "app", exist_ok=True)
                self._fs_ops.mkdir(self.log_directory / "errors", exist_ok=True)

                if self.framework == Framework.DJANGO:
                    self._fs_ops.mkdir(self.log_directory / "django", exist_ok=True)

                if self.archive_directory:
                    self._fs_ops.mkdir(self.archive_directory, parents=True, exist_ok=True)
            except (OSError, PermissionError) as e:
                # If directory creation fails, disable file logging
                import warnings

                warnings.warn(
                    f"Cannot create log directory {self.log_directory}: {e}. "
                    "Falling back to console-only logging.",
                    UserWarning,
                    stacklevel=2,
                )
                self.enable_file_logging = False

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

        # Validate log directory is writable
        if self.log_directory and self._fs_ops is not None:
            try:
                self._fs_ops.mkdir(self.log_directory, parents=True, exist_ok=True)
                test_file = self.log_directory / "test_write.tmp"
                self._fs_ops.touch(test_file)
                self._fs_ops.unlink(test_file)
            except (OSError, PermissionError) as e:
                errors.append(f"Log directory not writable: {e}")

        # Validate file size limits
        if self.max_file_size <= 0:
            errors.append("max_file_size must be greater than 0")

        if self.backup_count < 0:
            errors.append("backup_count must be non-negative")

        # Validate batch settings for async logging
        if self.enable_async_logging:
            if self.batch_size <= 0:
                errors.append("batch_size must be greater than 0 for async logging")
            if self.flush_interval <= 0:
                errors.append("flush_interval must be greater than 0 for async logging")

        return errors


class ConfigManager:
    """Manages global configuration with better testability."""

    def __init__(self) -> None:
        self._config: StormLoggerConfig | None = None
        self._env_reader: EnvironmentReader | None = None
        self._framework_detector: FrameworkDetector | None = None
        self._fs_ops: FileSystemOps | None = None

    def get_config(self) -> StormLoggerConfig:
        """Get the current configuration, creating from environment if needed."""
        if self._config is None:
            self._config = StormLoggerConfig.from_env(self._env_reader)
            # Apply any custom dependencies
            if self._framework_detector is not None:
                self._config._framework_detector = self._framework_detector
            if self._fs_ops is not None:
                self._config._fs_ops = self._fs_ops
        return self._config

    def set_config(self, config: StormLoggerConfig) -> None:
        """Set a new configuration."""
        self._config = config

    def reset(self) -> None:
        """Reset configuration to None."""
        self._config = None

    def with_env_reader(self, env_reader: EnvironmentReader) -> "ConfigManager":
        """Set custom environment reader for testing."""
        self._env_reader = env_reader
        return self

    def with_framework_detector(self, detector: FrameworkDetector) -> "ConfigManager":
        """Set custom framework detector for testing."""
        self._framework_detector = detector
        return self

    def with_fs_ops(self, fs_ops: FileSystemOps) -> "ConfigManager":
        """Set custom file system operations for testing."""
        self._fs_ops = fs_ops
        return self


# Global configuration manager instance
_config_manager = ConfigManager()


def get_config() -> StormLoggerConfig:
    """Get the global Storm Logger configuration."""
    return _config_manager.get_config()


def set_config(config: StormLoggerConfig) -> None:
    """Set the global Storm Logger configuration."""
    _config_manager.set_config(config)


def reset_config() -> None:
    """Reset the global configuration to default."""
    _config_manager.reset()


def get_config_manager() -> ConfigManager:
    """Get the config manager instance for advanced testing scenarios."""
    return _config_manager
