"""Configuration management for MergeGuide git hooks.

This module provides configuration loading and management for MergeGuide's
git hooks (pre-commit and pre-push). Configuration is loaded from YAML
files and provides per-hook settings for policies, exclusions, and behavior.

Configuration File Locations (searched in order):
    1. .mergeguide/hooks.yaml
    2. .mergeguide/hooks.yml
    3. mergeguide-hooks.yaml
    4. mergeguide-hooks.yml

Configuration Structure:
    version: "1.0"
    pre_commit:
        enabled: true
        policies: ["security", "quality"]
        exclude_paths: ["vendor/", "*.generated.go"]
        fail_on_warning: false
    pre_push:
        enabled: true
        policies: []
        exclude_paths: []
        fail_on_warning: true
    bypass_logging:
        enabled: true
        log_file: ".mergeguide/bypass.log"

Example Usage:
    # Load configuration (searches standard locations)
    config = HooksConfig.load()

    # Load from specific file
    config = HooksConfig.load(Path(".mergeguide/hooks.yaml"))

    # Access hook-specific settings
    if config.pre_commit.enabled:
        policies = config.pre_commit.policies

    # Save configuration
    config.save(Path(".mergeguide/hooks.yaml"))
"""

from __future__ import annotations

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

import yaml


@dataclass
class HookConfig:
    """Configuration settings for a specific hook type (pre-commit or pre-push).

    Each hook type has its own independent configuration controlling which
    policies to apply, which paths to exclude, and how to handle violations.

    Attributes:
        enabled: Whether this hook is active. Disabled hooks exit immediately.
        policies: List of policy IDs to apply. Empty list means use defaults.
        exclude_paths: Glob patterns for paths to skip during validation.
            Supports standard glob syntax (e.g., "vendor/**", "*.pb.go").
        fail_on_warning: If True, warnings cause the hook to fail (exit 1).
            If False, only errors cause failure. Defaults to False.

    Example:
        >>> config = HookConfig(
        ...     enabled=True,
        ...     policies=["security-001", "quality-001"],
        ...     exclude_paths=["vendor/", "*.generated.*"],
        ...     fail_on_warning=True,
        ... )
    """

    enabled: bool = True
    policies: list[str] = field(default_factory=list)
    exclude_paths: list[str] = field(default_factory=list)
    fail_on_warning: bool = False

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> HookConfig:
        """Create a HookConfig instance from a dictionary.

        Handles missing keys gracefully by using default values.

        Args:
            data: Dictionary containing hook configuration fields.
                All fields are optional.

        Returns:
            HookConfig instance with values from the dictionary or defaults.
        """
        return cls(
            enabled=data.get("enabled", True),
            policies=data.get("policies", []),
            exclude_paths=data.get("exclude_paths", []),
            fail_on_warning=data.get("fail_on_warning", False),
        )


@dataclass
class HooksConfig:
    """Complete configuration for all MergeGuide git hooks.

    This is the top-level configuration class that combines settings for
    all hook types and global options like bypass logging. Configuration
    is typically loaded from a YAML file.

    Attributes:
        version: Configuration schema version for compatibility checking.
        pre_commit: Configuration for the pre-commit hook.
        pre_push: Configuration for the pre-push hook.
        bypass_logging_enabled: Whether to log when hooks are bypassed
            via --no-verify. Useful for audit trails.
        bypass_log_file: Path to the bypass log file, relative to repo root.

    Example:
        >>> config = HooksConfig.load()
        >>> if config.pre_commit.enabled:
        ...     print("Pre-commit hook is active")
        >>> config.save(Path(".mergeguide/hooks.yaml"))
    """

    version: str = "1.0"
    pre_commit: HookConfig = field(default_factory=HookConfig)
    pre_push: HookConfig = field(default_factory=HookConfig)
    bypass_logging_enabled: bool = True
    bypass_log_file: str = ".mergeguide/bypass.log"

    @classmethod
    def load(cls, path: Path | None = None) -> HooksConfig:
        """Load configuration from file or return defaults.

        If a specific path is provided, loads from that file. Otherwise,
        searches standard configuration file locations and loads the first
        one found. Returns default configuration if no file is found.

        Args:
            path: Explicit path to a hooks configuration file. If provided
                but the file doesn't exist, returns defaults. If None,
                searches standard locations.

        Returns:
            HooksConfig instance loaded from file or with default values.

        Raises:
            ConfigError: If a configuration file exists but contains
                invalid YAML syntax.

        Example:
            >>> # Auto-detect config file location
            >>> config = HooksConfig.load()
            >>> # Load from specific file
            >>> config = HooksConfig.load(Path("custom-hooks.yaml"))
        """
        if path is not None:
            if path.exists():
                return cls._load_from_file(path)
            return cls()

        # Search standard locations in priority order
        search_paths = [
            Path(".mergeguide/hooks.yaml"),
            Path(".mergeguide/hooks.yml"),
            Path("mergeguide-hooks.yaml"),
            Path("mergeguide-hooks.yml"),
        ]

        for search_path in search_paths:
            if search_path.exists():
                return cls._load_from_file(search_path)

        # No config file found - return defaults
        return cls()

    @classmethod
    def _load_from_file(cls, path: Path) -> HooksConfig:
        """Load configuration from a YAML file.

        Args:
            path: Path to the YAML configuration file.

        Returns:
            HooksConfig instance populated from the file.

        Raises:
            ConfigError: If the file contains invalid YAML.
        """
        try:
            content = path.read_text()
            # yaml.safe_load returns None for empty files
            data = yaml.safe_load(content) or {}
            return cls.from_dict(data)
        except yaml.YAMLError as e:
            raise ConfigError(f"Invalid YAML in {path}: {e}") from e

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> HooksConfig:
        """Create a HooksConfig instance from a dictionary.

        Handles missing keys gracefully by using default values for
        all fields and nested configurations.

        Args:
            data: Dictionary containing configuration data. All fields
                are optional.

        Returns:
            HooksConfig instance with values from the dictionary or defaults.
        """
        pre_commit_data = data.get("pre_commit", {})
        pre_push_data = data.get("pre_push", {})
        bypass_data = data.get("bypass_logging", {})

        return cls(
            version=data.get("version", "1.0"),
            pre_commit=HookConfig.from_dict(pre_commit_data),
            pre_push=HookConfig.from_dict(pre_push_data),
            bypass_logging_enabled=bypass_data.get("enabled", True),
            bypass_log_file=bypass_data.get("log_file", ".mergeguide/bypass.log"),
        )

    def to_dict(self) -> dict[str, Any]:
        """Convert the configuration to a dictionary.

        Produces a dictionary structure suitable for YAML serialization.
        All nested HookConfig objects are converted to dictionaries.

        Returns:
            Dictionary representation of the complete configuration.
        """
        return {
            "version": self.version,
            "pre_commit": {
                "enabled": self.pre_commit.enabled,
                "policies": self.pre_commit.policies,
                "exclude_paths": self.pre_commit.exclude_paths,
                "fail_on_warning": self.pre_commit.fail_on_warning,
            },
            "pre_push": {
                "enabled": self.pre_push.enabled,
                "policies": self.pre_push.policies,
                "exclude_paths": self.pre_push.exclude_paths,
                "fail_on_warning": self.pre_push.fail_on_warning,
            },
            "bypass_logging": {
                "enabled": self.bypass_logging_enabled,
                "log_file": self.bypass_log_file,
            },
        }

    def save(self, path: Path) -> None:
        """Save the configuration to a YAML file.

        Creates parent directories if they don't exist. The output is
        formatted for readability (block style, no sorted keys).

        Args:
            path: Path where the configuration file should be written.
                Parent directories are created automatically.

        Example:
            >>> config = HooksConfig()
            >>> config.pre_commit.fail_on_warning = True
            >>> config.save(Path(".mergeguide/hooks.yaml"))
        """
        path.parent.mkdir(parents=True, exist_ok=True)
        content = yaml.dump(self.to_dict(), default_flow_style=False, sort_keys=False)
        path.write_text(content)


class ConfigError(Exception):
    """Exception raised when configuration loading or parsing fails.

    This is typically raised when a configuration file contains invalid
    YAML syntax. The error message includes the file path and details
    about the YAML parsing error.

    Attributes:
        message: Human-readable description of the configuration error.
    """

    pass
