"""
Hierarchical Config Merger - Deep merge multiple configuration sources

Merge order (later configs override earlier):
1. Global: `~/.groknroll/groknroll.json`
2. Environment: path from `GROKNROLL_CONFIG` env var
3. Project: `./groknroll.json`

Merge rules:
- Objects (dicts): Deep merge recursively
- Arrays (lists): Replace entirely (no merging)
- Scalars: Later value wins
- null/None: Treated as explicit value (can clear a field)

Examples:
    merger = ConfigMerger()
    result = merger.merge(global_config, env_config, project_config)

    # Or use with ConfigLoader
    merged = merge_configs(project_path=Path.cwd())
"""

from dataclasses import dataclass, field
from pathlib import Path
from typing import Any, Optional

from groknroll.config.loader import ConfigLoader


@dataclass
class MergeResult:
    """
    Result of configuration merging

    Attributes:
        config: The merged configuration dict
        sources: List of source names that contributed
        success: Whether merging succeeded
        conflicts: List of merge conflicts (informational)
    """

    config: dict[str, Any] = field(default_factory=dict)
    sources: list[str] = field(default_factory=list)
    success: bool = True
    conflicts: list[str] = field(default_factory=list)


class ConfigMerger:
    """
    Deep merge multiple configuration sources

    Merge rules:
    - Objects (dicts): Deep merge recursively
    - Arrays (lists): Replace entirely (no merging)
    - Scalars: Later value wins
    - null/None: Treated as explicit value (can clear a field)

    Example:
        merger = ConfigMerger()

        global_config = {"model": "gpt-4", "permission": {"*": "ask"}}
        project_config = {"permission": {"bash": "allow"}}

        result = merger.merge(global_config, project_config)
        # result.config = {
        #     "model": "gpt-4",
        #     "permission": {"*": "ask", "bash": "allow"}
        # }
    """

    def __init__(self, track_conflicts: bool = False):
        """
        Initialize ConfigMerger

        Args:
            track_conflicts: If True, track merge conflicts in result
        """
        self.track_conflicts = track_conflicts

    def merge(self, *configs: dict[str, Any]) -> MergeResult:
        """
        Merge multiple configuration dicts

        Later configs override earlier ones.

        Args:
            *configs: Configuration dicts to merge (in order)

        Returns:
            MergeResult with merged config
        """
        conflicts: list[str] = []
        sources: list[str] = []

        # Start with empty config
        result: dict[str, Any] = {}

        for i, config in enumerate(configs):
            if config:
                source_name = f"source_{i}"
                sources.append(source_name)

                if self.track_conflicts:
                    result, new_conflicts = self._deep_merge_with_tracking(
                        result, config, prefix=""
                    )
                    conflicts.extend(new_conflicts)
                else:
                    result = self._deep_merge(result, config)

        return MergeResult(
            config=result,
            sources=sources,
            success=True,
            conflicts=conflicts,
        )

    def merge_into(self, base: dict[str, Any], override: dict[str, Any]) -> dict[str, Any]:
        """
        Merge override into base config

        Args:
            base: Base configuration
            override: Override configuration

        Returns:
            Merged configuration dict
        """
        return self._deep_merge(base.copy(), override)

    def _deep_merge(self, base: dict[str, Any], override: dict[str, Any]) -> dict[str, Any]:
        """
        Deep merge two dicts

        Args:
            base: Base dict (will be modified)
            override: Override dict

        Returns:
            Merged dict
        """
        result = base.copy()

        for key, value in override.items():
            if key in result:
                base_value = result[key]

                # Both are dicts: deep merge
                if isinstance(base_value, dict) and isinstance(value, dict):
                    result[key] = self._deep_merge(base_value, value)
                else:
                    # Arrays, scalars, or mixed: override wins
                    result[key] = value
            else:
                # New key: add it
                result[key] = value

        return result

    def _deep_merge_with_tracking(
        self,
        base: dict[str, Any],
        override: dict[str, Any],
        prefix: str,
    ) -> tuple[dict[str, Any], list[str]]:
        """
        Deep merge with conflict tracking

        Args:
            base: Base dict
            override: Override dict
            prefix: Key prefix for conflict tracking

        Returns:
            Tuple of (merged dict, list of conflicts)
        """
        result = base.copy()
        conflicts: list[str] = []

        for key, value in override.items():
            full_key = f"{prefix}.{key}" if prefix else key

            if key in result:
                base_value = result[key]

                # Both are dicts: deep merge
                if isinstance(base_value, dict) and isinstance(value, dict):
                    merged, sub_conflicts = self._deep_merge_with_tracking(
                        base_value, value, full_key
                    )
                    result[key] = merged
                    conflicts.extend(sub_conflicts)
                else:
                    # Track the override
                    if base_value != value:
                        conflicts.append(f"{full_key}: {repr(base_value)} -> {repr(value)}")
                    result[key] = value
            else:
                result[key] = value

        return result, conflicts

    def diff(
        self,
        config1: dict[str, Any],
        config2: dict[str, Any],
        prefix: str = "",
    ) -> list[str]:
        """
        Find differences between two configs

        Args:
            config1: First config
            config2: Second config
            prefix: Key prefix for output

        Returns:
            List of difference descriptions
        """
        diffs: list[str] = []

        all_keys = set(config1.keys()) | set(config2.keys())

        for key in sorted(all_keys):
            full_key = f"{prefix}.{key}" if prefix else key
            in_1 = key in config1
            in_2 = key in config2

            if in_1 and in_2:
                val1 = config1[key]
                val2 = config2[key]

                if isinstance(val1, dict) and isinstance(val2, dict):
                    diffs.extend(self.diff(val1, val2, full_key))
                elif val1 != val2:
                    diffs.append(f"{full_key}: {repr(val1)} != {repr(val2)}")
            elif in_1:
                diffs.append(f"{full_key}: only in first ({repr(config1[key])})")
            else:
                diffs.append(f"{full_key}: only in second ({repr(config2[key])})")

        return diffs

    def flatten(self, config: dict[str, Any], prefix: str = "") -> dict[str, Any]:
        """
        Flatten nested config to dot-notation keys

        Args:
            config: Nested configuration dict
            prefix: Key prefix

        Returns:
            Flat dict with dot-notation keys

        Example:
            flatten({"a": {"b": 1}}) -> {"a.b": 1}
        """
        result: dict[str, Any] = {}

        for key, value in config.items():
            full_key = f"{prefix}.{key}" if prefix else key

            if isinstance(value, dict):
                result.update(self.flatten(value, full_key))
            else:
                result[full_key] = value

        return result

    def unflatten(self, flat: dict[str, Any]) -> dict[str, Any]:
        """
        Unflatten dot-notation keys to nested config

        Args:
            flat: Flat dict with dot-notation keys

        Returns:
            Nested configuration dict

        Example:
            unflatten({"a.b": 1}) -> {"a": {"b": 1}}
        """
        result: dict[str, Any] = {}

        for key, value in flat.items():
            parts = key.split(".")
            current = result

            for part in parts[:-1]:
                if part not in current:
                    current[part] = {}
                current = current[part]

            current[parts[-1]] = value

        return result


# Convenience functions


def merge_configs(
    project_path: Optional[Path] = None,
    include_global: bool = True,
    include_env: bool = True,
) -> dict[str, Any]:
    """
    Load and merge configs from all sources (convenience function)

    Uses deep merging instead of shallow update.

    Args:
        project_path: Project root directory
        include_global: Include global config
        include_env: Include env var config

    Returns:
        Merged configuration dict
    """
    loader = ConfigLoader(project_path=project_path)
    merger = ConfigMerger()

    configs: list[dict[str, Any]] = []

    # Global config
    if include_global and loader.global_config_path.exists():
        try:
            configs.append(loader.load_file(loader.global_config_path))
        except (ValueError, FileNotFoundError):
            pass

    # Env var config
    if include_env and loader.env_config_path and loader.env_config_path.exists():
        try:
            configs.append(loader.load_file(loader.env_config_path))
        except (ValueError, FileNotFoundError):
            pass

    # Project config
    if loader.project_config_path.exists():
        try:
            configs.append(loader.load_file(loader.project_config_path))
        except (ValueError, FileNotFoundError):
            pass

    if not configs:
        return {}

    result = merger.merge(*configs)
    return result.config


def merge_configs_with_result(
    project_path: Optional[Path] = None,
) -> MergeResult:
    """
    Load and merge configs with detailed result (convenience function)

    Args:
        project_path: Project root directory

    Returns:
        MergeResult with config and metadata
    """
    loader = ConfigLoader(project_path=project_path)
    merger = ConfigMerger(track_conflicts=True)

    configs: list[dict[str, Any]] = []

    # Collect all configs
    if loader.global_config_path.exists():
        try:
            configs.append(loader.load_file(loader.global_config_path))
        except (ValueError, FileNotFoundError):
            pass

    if loader.env_config_path and loader.env_config_path.exists():
        try:
            configs.append(loader.load_file(loader.env_config_path))
        except (ValueError, FileNotFoundError):
            pass

    if loader.project_config_path.exists():
        try:
            configs.append(loader.load_file(loader.project_config_path))
        except (ValueError, FileNotFoundError):
            pass

    return merger.merge(*configs)


def deep_merge(base: dict[str, Any], override: dict[str, Any]) -> dict[str, Any]:
    """
    Deep merge two dicts (convenience function)

    Args:
        base: Base configuration
        override: Override configuration

    Returns:
        Merged configuration dict
    """
    merger = ConfigMerger()
    return merger.merge_into(base, override)
