"""Configuration management for file-organizer.

This module handles loading, saving, and accessing configuration for the file organizer.
Configuration is stored as YAML in a platform-appropriate config directory:
- Windows: %APPDATA%\\file-organizer\\config.yaml
- Linux/macOS: ~/.config/file-organizer/config.yaml (or $XDG_CONFIG_HOME)
"""

from __future__ import annotations

import copy
import os
from pathlib import Path
from typing import Any

import yaml

from .models import OrganizeMode

# Default configuration values
# These are used when no config file exists or when merging with user config
DEFAULT_CONFIG = {
    "organize": {
        "default_mode": "type",  # Default organization mode: type, date, or size
        "date_format": "%Y-%m",  # strftime format for date-based folders (e.g., "2024-01")
        "size_thresholds": {  # MB thresholds for size-based organization
            "small_mb": 1,  # Files < 1 MB -> "Small"
            "large_mb": 100,  # Files > 100 MB -> "Large"
        },
        "create_subdirs": True,  # Create category subdirectories in source dir
    },
    "ignore": {
        "patterns": [  # Glob patterns for files to ignore
            "*.tmp",
            "*.crdownload",  # Chrome partial downloads
            "*.part",  # Firefox partial downloads
            "*.download",  # Other partial downloads
            ".DS_Store",  # macOS metadata
            "Thumbs.db",  # Windows thumbnails
            "*.lnk",  # Windows shortcuts
            "desktop.ini",  # Windows folder config
        ],
        "hidden_files": True,  # Ignore dotfiles (Unix hidden files)
        "system_files": True,  # Ignore system files (Thumbs.db, etc.)
    },
    "safety": {
        "dry_run_by_default": True,  # Default to dry-run (preview only)
        "require_confirmation": False,  # Ask before applying (when not using --yes)
        "max_file_size_mb": 1000,  # Skip files larger than this (1 GB)
        "preserve_timestamps": True,  # Preserve file modification times
    },
    "advanced": {
        "follow_symlinks": False,  # Follow symbolic links when scanning
        "ignore_errors": False,  # Continue on errors instead of stopping
        "log_level": "INFO",  # Logging level
    },
}


def get_config_dir() -> Path:
    """Get the configuration directory path.

    Uses platform-standard locations:
    - Windows: %APPDATA%\\file-organizer
    - Linux/macOS: $XDG_CONFIG_HOME/file-organizer or ~/.config/file-organizer

    Returns:
        Path to the config directory.
    """
    if os.name == "nt":  # Windows
        base = Path(os.environ.get("APPDATA", Path.home() / "AppData" / "Roaming"))
    else:  # Linux/macOS
        base = Path(os.environ.get("XDG_CONFIG_HOME", Path.home() / ".config"))
    return base / "file-organizer"


def get_config_path() -> Path:
    """Get the configuration file path.

    Returns:
        Path to config.yaml in the config directory.
    """
    return get_config_dir() / "config.yaml"


def get_undo_log_path() -> Path:
    """Get the undo log file path.

    Returns:
        Path to undo_log.json in the config directory.
    """
    return get_config_dir() / "undo_log.json"


def load_config() -> dict[str, Any]:
    """Load configuration from file, returning defaults if not found.

    Loads YAML config and deep-merges with DEFAULT_CONFIG so missing keys
    get default values while user settings are preserved.

    Returns:
        Merged configuration dictionary.
    """
    config_path = get_config_path()
    if not config_path.exists():
        return copy.deepcopy(DEFAULT_CONFIG)

    try:
        with config_path.open("r", encoding="utf-8") as f:
            user_config = yaml.safe_load(f) or {}
        # Deep merge with defaults: user config overrides defaults
        return _deep_merge(copy.deepcopy(DEFAULT_CONFIG), user_config)
    except Exception:
        # On any error (invalid YAML, permissions, etc.), return defaults
        return copy.deepcopy(DEFAULT_CONFIG)


def save_config(config: dict[str, Any]) -> None:
    """Save configuration to file.

    Creates config directory if it doesn't exist, writes YAML with
    default_flow_style=False for readability and sort_keys=False
    to preserve key order.

    Args:
        config: Configuration dictionary to save.
    """
    config_dir = get_config_dir()
    config_dir.mkdir(parents=True, exist_ok=True)
    config_path = get_config_path()

    with config_path.open("w", encoding="utf-8") as f:
        yaml.dump(config, f, default_flow_style=False, sort_keys=False)


def _deep_merge(base: dict[str, Any], override: dict[str, Any]) -> dict[str, Any]:
    """Deep merge two dictionaries recursively.

    Modifies base in place and returns it. Nested dicts are merged,
    other values are replaced.

    Args:
        base: Base dictionary (will be modified).
        override: Dictionary with values to override/merge.

    Returns:
        The merged base dictionary.
    """
    for key, value in override.items():
        if key in base and isinstance(base[key], dict) and isinstance(value, dict):
            base[key] = _deep_merge(base[key], value)
        else:
            base[key] = value
    return base


class Config:
    """Configuration manager with attribute-style access.

    Provides convenient access to nested config values via attributes
    (e.g., config.organize.default_mode) and dict-like access via keys
    (e.g., config["organize"]["default_mode"]).

    Also provides helper properties for commonly used settings.
    """

    def __init__(self, data: dict[str, Any] | None = None):
        """Initialize config with provided data or load from file.

        Args:
            data: Optional pre-loaded config dict. If None, loads from file.
        """
        self._data = copy.deepcopy(data) if data is not None else load_config()

    def __getattr__(self, name: str) -> Any:
        """Allow attribute access to config sections.

        If the value is a dict, returns a nested Config object for
        chained attribute access (e.g., config.organize.date_format).

        Args:
            name: Attribute name to access.

        Returns:
            Config value or nested Config object.

        Raises:
            AttributeError: If key doesn't exist.
        """
        # Check if it's a top-level section
        if name in self._data:
            value = self._data[name]
            if isinstance(value, dict):
                return Config(value)
            return value
        raise AttributeError(f"'Config' object has no attribute '{name}'")

    def __getitem__(self, key: str) -> Any:
        """Allow dict-style access to top-level keys.

        Args:
            key: Top-level config key.

        Returns:
            Config value.
        """
        return self._data[key]

    def get(self, key: str, default: Any = None) -> Any:
        """Get nested config value using dot notation.

        Args:
            key: Dot-separated path (e.g., "organize.default_mode").
            default: Default value if key not found.

        Returns:
            Config value or default.
        """
        keys = key.split(".")
        value = self._data
        for k in keys:
            if isinstance(value, dict) and k in value:
                value = value[k]
            else:
                return default
        return value

    def set(self, key: str, value: Any) -> None:
        """Set nested config value using dot notation and persist to file.

        Creates intermediate dicts as needed.

        Args:
            key: Dot-separated path (e.g., "organize.default_mode").
            value: Value to set.
        """
        keys = key.split(".")
        target = self._data
        for k in keys[:-1]:
            if k not in target:
                target[k] = {}
            target = target[k]
        target[keys[-1]] = value
        save_config(self._data)

    def to_dict(self) -> dict[str, Any]:
        """Return a shallow copy of the internal config dict.

        Returns:
            Copy of config data.
        """
        return self._data.copy()

    @property
    def default_mode(self) -> OrganizeMode:
        """Get the default organization mode as OrganizeMode enum.

        Handles both nested (organize.default_mode) and flat (default_mode)
        config structures for backward compatibility.

        Returns:
            OrganizeMode enum (TYPE, DATE, or SIZE).
        """
        # Handle both top-level and nested config
        if "organize" in self._data:
            return OrganizeMode(self._data["organize"]["default_mode"])
        elif "default_mode" in self._data:
            return OrganizeMode(self._data["default_mode"])
        return OrganizeMode.TYPE

    @property
    def ignore_patterns(self) -> list[str]:
        """Get list of ignore patterns from config.

        Handles both nested (ignore.patterns) and flat (patterns)
        config structures for backward compatibility.

        Returns:
            List of glob patterns to ignore.
        """
        if "ignore" in self._data:
            return list(self._data["ignore"]["patterns"])
        elif "patterns" in self._data:
            return list(self._data["patterns"])
        return []

    @property
    def dry_run_by_default(self) -> bool:
        """Get the dry_run_by_default setting as bool.

        Handles both nested (safety.dry_run_by_default) and flat
        (dry_run_by_default) config structures.

        Returns:
            True if dry-run should be default, False otherwise.
        """
        if "safety" in self._data:
            return bool(self._data["safety"]["dry_run_by_default"])
        elif "dry_run_by_default" in self._data:
            return bool(self._data["dry_run_by_default"])
        return True
