"""Configuration loading from YAML and .conf files."""

from __future__ import annotations

from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple
import yaml


class ConfigLoader:
    """Load configuration from YAML and bash-style .conf files.

    Supports an external user config directory that overlays the bundled
    defaults. Users only need to create files for the settings they want
    to override.

    Merge strategies:
        - defaults.conf: key-level override (user keys replace bundled keys)
        - distributions.yml: deep merge (user can add distros/versions)
        - base/extended-packages.yml: top-level key replacement (user's
          apt_packages list replaces the bundled one entirely)
    """

    def __init__(
        self,
        config_dir: str | Path | None = None,
        user_config_dir: str | Path | None = None,
    ):
        """Initialize config loader.

        Args:
            config_dir: Path to bundled config directory. Defaults to
                the tui/data/config/ package directory.
            user_config_dir: Path to user's external config overrides.
                Defaults to get_user_config_path(). Set to False-y value
                to disable user overrides entirely.
        """
        if config_dir is None:
            from tui.data import get_config_path

            self.config_dir = get_config_path()
        else:
            self.config_dir = Path(config_dir)

        if user_config_dir is None:
            from tui.data import get_user_config_path

            self._user_config_dir: Optional[Path] = get_user_config_path()
        elif user_config_dir:
            self._user_config_dir = Path(user_config_dir)
        else:
            self._user_config_dir = None

        self._distributions: Optional[dict] = None
        self._defaults: Optional[dict] = None
        self._base_packages: Optional[dict] = None
        self._extended_packages: Optional[dict] = None

    @property
    def user_config_dir(self) -> Optional[Path]:
        """The resolved user config override directory, or None."""
        return self._user_config_dir

    @property
    def distributions(self) -> dict:
        """Get distribution metadata, merging user overrides via deep merge."""
        if self._distributions is None:
            # Load bundled
            dist_file = self.config_dir / "distributions.yml"
            if dist_file.exists():
                with open(dist_file) as f:
                    self._distributions = yaml.safe_load(f) or {}
            else:
                self._distributions = {"distributions": {}}

            # Deep merge user overrides
            user_file = self._user_file("distributions.yml")
            if user_file:
                with open(user_file) as f:
                    user_data = yaml.safe_load(f) or {}
                self._distributions = self._deep_merge(
                    self._distributions, user_data
                )

        return self._distributions

    @property
    def defaults(self) -> dict:
        """Get default values, with user keys overriding bundled keys."""
        if self._defaults is None:
            # Load bundled
            self._defaults = self._load_conf_from(
                self.config_dir / "defaults.conf"
            )
            # Overlay user keys
            user_file = self._user_file("defaults.conf")
            if user_file:
                user_conf = self._load_conf_from(user_file)
                self._defaults.update(user_conf)
        return self._defaults

    @property
    def base_packages(self) -> dict:
        """Get base packages, with user top-level keys replacing bundled ones."""
        if self._base_packages is None:
            self._base_packages = self._load_packages("base-packages.yml")
        return self._base_packages

    @property
    def extended_packages(self) -> dict:
        """Get extended packages, with user top-level keys replacing bundled ones."""
        if self._extended_packages is None:
            self._extended_packages = self._load_packages(
                "extended-packages.yml"
            )
        return self._extended_packages

    def _user_file(self, relative_path: str) -> Optional[Path]:
        """Return the user override file path if it exists, else None."""
        if self._user_config_dir is None:
            return None
        path = self._user_config_dir / relative_path
        return path if path.is_file() else None

    def _load_packages(self, filename: str) -> dict:
        """Load a package YAML with top-level key replacement from user overrides.

        If the user provides the same top-level key (e.g. apt_packages),
        the user's list entirely replaces the bundled list. Keys not
        present in the user file keep their bundled values.
        """
        # Load bundled
        pkg_file = self.config_dir / "packages" / filename
        if pkg_file.exists():
            with open(pkg_file) as f:
                result = yaml.safe_load(f) or {}
        else:
            result = {}

        # Top-level key replacement from user
        user_file = self._user_file("packages/" + filename)
        if user_file:
            with open(user_file) as f:
                user_data = yaml.safe_load(f) or {}
            result.update(user_data)

        return result

    @staticmethod
    def _load_conf_from(conf_path: Path) -> dict[str, str]:
        """Parse a bash-style key=value config file.

        Args:
            conf_path: Absolute path to the .conf file.

        Returns:
            Dictionary of configuration values.
        """
        config: dict[str, str] = {}
        if conf_path.exists():
            with open(conf_path) as f:
                for line in f:
                    line = line.strip()
                    if line and not line.startswith("#") and "=" in line:
                        key, value = line.split("=", 1)
                        key = key.strip()
                        # Remove quotes from value
                        value = value.strip().strip('"').strip("'")
                        config[key] = value
        return config

    def _load_conf(self, filename: str) -> dict:
        """Parse bash-style key=value config files (legacy interface).

        Args:
            filename: Name of the config file (relative to config_dir).

        Returns:
            Dictionary of configuration values.
        """
        return self._load_conf_from(self.config_dir / filename)

    @staticmethod
    def _deep_merge(base: dict, override: dict) -> dict:
        """Recursively merge override dict into base dict.

        - Dict values are merged recursively.
        - All other values (lists, scalars) in override replace base.

        Args:
            base: Base dictionary (modified in place and returned).
            override: Dictionary with values to merge in.

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

    def get_distro_list(self) -> list[tuple[str, str]]:
        """Get list of distributions for menu display.

        Returns:
            List of tuples: (distro_id, display_name)
        """
        distros = self.distributions.get("distributions", {})
        return [
            (distro_id, data.get("display_name", distro_id))
            for distro_id, data in distros.items()
        ]

    def get_version_list(self, distro: str) -> list[tuple[str, str]]:
        """Get list of versions for a distribution.

        Args:
            distro: Distribution identifier (e.g., 'ubuntu', 'rocky').

        Returns:
            List of tuples: (version_id, display_name)
        """
        distros = self.distributions.get("distributions", {})
        distro_data = distros.get(distro, {})
        versions = distro_data.get("versions", {})
        return [
            (version_id, data.get("name", version_id))
            for version_id, data in versions.items()
        ]

    def get_version_info(self, distro: str, version: str) -> dict[str, Any]:
        """Get detailed info for a specific distribution version.

        Args:
            distro: Distribution identifier.
            version: Version identifier.

        Returns:
            Dictionary with version metadata.
        """
        distros = self.distributions.get("distributions", {})
        distro_data = distros.get(distro, {})
        versions = distro_data.get("versions", {})
        return versions.get(version, {})

    def get_distro_family(self, distro: str) -> str:
        """Get the family for a distribution (debian, rhel, suse).

        Args:
            distro: Distribution identifier.

        Returns:
            Family name string.
        """
        distros = self.distributions.get("distributions", {})
        distro_data = distros.get(distro, {})
        return distro_data.get("family", "unknown")

    def get_default(self, key: str, fallback: Any = None) -> Any:
        """Get a default configuration value.

        Args:
            key: Configuration key name.
            fallback: Default value if key not found.

        Returns:
            Configuration value or fallback.
        """
        return self.defaults.get(key, fallback)

    def get_default_int(self, key: str, fallback: int = 0) -> int:
        """Get a default configuration value as integer.

        Args:
            key: Configuration key name.
            fallback: Default value if key not found or not an integer.

        Returns:
            Integer configuration value or fallback.
        """
        value = self.defaults.get(key)
        if value is None:
            return fallback
        try:
            return int(value)
        except (ValueError, TypeError):
            return fallback
