"""Configuration management for agr.toml."""

from dataclasses import dataclass, field
from pathlib import Path

import tomlkit
from tomlkit import TOMLDocument
from tomlkit.exceptions import TOMLKitError

from agr.exceptions import ConfigError
from agr.source import (
    DEFAULT_SOURCE_NAME,
    SourceConfig,
    SourceResolver,
    default_sources,
)
from agr.tool import DEFAULT_TOOL_NAMES, TOOLS, ToolConfig, get_tool


@dataclass
class Dependency:
    """A dependency in agr.toml.

    Examples:
        Remote: { handle = "kasperjunge/commit", type = "skill" }
        Local:  { path = "./my-skill", type = "skill" }
    """

    type: str  # Always "skill" for now
    handle: str | None = None  # Remote Git reference
    path: str | None = None  # Local path
    source: str | None = None  # Optional source name for remote handles

    def __post_init__(self) -> None:
        """Validate dependency has exactly one source."""
        if self.handle and self.path:
            raise ValueError("Dependency cannot have both handle and path")
        if not self.handle and not self.path:
            raise ValueError("Dependency must have either handle or path")
        if self.path and self.source:
            raise ValueError("Local dependency cannot specify a source")

    @property
    def is_local(self) -> bool:
        """True if this is a local path dependency."""
        return self.path is not None

    @property
    def is_remote(self) -> bool:
        """True if this is a remote GitHub dependency."""
        return self.handle is not None

    @property
    def identifier(self) -> str:
        """Unique identifier (path or handle)."""
        return self.path or self.handle or ""


@dataclass
class AgrConfig:
    """Configuration loaded from agr.toml.

    Format:
        default_source = "github"

        [[source]]
        name = "github"
        type = "git"
        url = "https://github.com/{owner}/{repo}.git"

        tools = ["claude", "cursor"]  # Optional, defaults to ["claude"]
        dependencies = [
            { handle = "kasperjunge/commit", type = "skill" },
            { path = "./my-skill", type = "skill" },
        ]
    """

    dependencies: list[Dependency] = field(default_factory=list)
    tools: list[str] = field(default_factory=lambda: list(DEFAULT_TOOL_NAMES))
    sources: list[SourceConfig] = field(default_factory=default_sources)
    default_source: str = DEFAULT_SOURCE_NAME
    _path: Path | None = field(default=None, repr=False)

    def get_tools(self) -> list[ToolConfig]:
        """Get ToolConfig instances for configured tools.

        Returns:
            List of ToolConfig instances

        Raises:
            AgrError: If any tool name is not recognized
        """
        return [get_tool(t) for t in self.tools]

    def get_source_resolver(self) -> SourceResolver:
        """Get a SourceResolver for this config."""
        return SourceResolver(self.sources, self.default_source)

    @classmethod
    def load(cls, path: Path) -> "AgrConfig":
        """Load configuration from agr.toml.

        Args:
            path: Path to agr.toml

        Returns:
            AgrConfig instance

        Raises:
            ConfigError: If the file contains invalid TOML
        """
        if not path.exists():
            config = cls()
            config._path = path
            return config

        try:
            content = path.read_text()
            doc = tomlkit.parse(content)
        except TOMLKitError as e:
            raise ConfigError(f"Invalid TOML in {path}: {e}")

        config = cls()
        config._path = path

        # Parse tools list (defaults to DEFAULT_TOOL_NAMES)
        tools_list = doc.get("tools", list(DEFAULT_TOOL_NAMES))
        if isinstance(tools_list, list):
            config.tools = [str(t) for t in tools_list]
        else:
            config.tools = list(DEFAULT_TOOL_NAMES)

        # Validate tool names
        for tool_name in config.tools:
            if tool_name not in TOOLS:
                available = ", ".join(TOOLS.keys())
                raise ConfigError(
                    f"Unknown tool '{tool_name}' in agr.toml. Available: {available}"
                )

        # Parse sources list
        sources_list = doc.get("source")
        sources: list[SourceConfig] = []
        if sources_list is None:
            sources = default_sources()
        else:
            if not isinstance(sources_list, list):
                raise ConfigError("Invalid [[source]] format in agr.toml")
            for item in sources_list:
                if not isinstance(item, dict):
                    raise ConfigError("Invalid [[source]] entry in agr.toml")
                name = str(item.get("name", "")).strip()
                source_type = str(item.get("type", "git")).strip()
                url = item.get("url")
                if not name:
                    raise ConfigError("Source entry missing name")
                if source_type != "git":
                    raise ConfigError(
                        f"Unsupported source type '{source_type}' for '{name}'"
                    )
                if not url:
                    raise ConfigError(f"Source '{name}' missing url")
                sources.append(SourceConfig(name=name, type=source_type, url=str(url)))

        default_source = doc.get("default_source")
        if default_source:
            default_source = str(default_source)
        elif sources:
            default_source = sources[0].name
        else:
            default_source = DEFAULT_SOURCE_NAME

        if not any(source.name == default_source for source in sources):
            raise ConfigError(
                f"default_source '{default_source}' not found in [[source]] list"
            )

        config.sources = sources
        config.default_source = default_source

        # Parse dependencies list (must appear before [[source]] in TOML)
        deps_list = doc.get("dependencies", [])
        if "dependencies" not in doc and sources_list:
            for item in sources_list:
                if isinstance(item, dict) and "dependencies" in item:
                    raise ConfigError(
                        "dependencies must be declared before [[source]] blocks"
                    )
        for item in deps_list:
            if not isinstance(item, dict):
                continue
            dep_type = item.get("type", "skill")
            handle = item.get("handle")
            path_val = item.get("path")
            source = item.get("source")
            if source is not None:
                source = str(source)

            if handle:
                config.dependencies.append(
                    Dependency(handle=handle, type=dep_type, source=source)
                )
            elif path_val:
                if source is not None:
                    raise ConfigError("Local dependencies cannot specify a source")
                config.dependencies.append(Dependency(path=path_val, type=dep_type))

        source_names = {source.name for source in sources}
        for dep in config.dependencies:
            if dep.source and dep.source not in source_names:
                raise ConfigError(
                    f"Unknown source '{dep.source}' in dependency '{dep.identifier}'"
                )

        return config

    def save(self, path: Path | None = None) -> None:
        """Save configuration to agr.toml.

        Args:
            path: Path to save to (uses original path if not specified)

        Raises:
            ValueError: If no path specified and no original path
        """
        save_path = path or self._path
        if save_path is None:
            raise ValueError("No path specified for saving config")

        doc: TOMLDocument = tomlkit.document()

        # Always write default source and sources for clarity
        default_source = self.default_source or DEFAULT_SOURCE_NAME
        sources = self.sources or default_sources()
        if not any(source.name == default_source for source in sources):
            raise ValueError(
                f"default_source '{default_source}' not found in sources list"
            )
        doc["default_source"] = default_source

        # Save tools array if not default
        if self.tools != DEFAULT_TOOL_NAMES:
            tools_array = tomlkit.array()
            for tool in self.tools:
                tools_array.append(tool)
            doc["tools"] = tools_array

        # Build dependencies array
        deps_array = tomlkit.array()
        deps_array.multiline(True)

        for dep in self.dependencies:
            item = tomlkit.inline_table()
            if dep.handle:
                item["handle"] = dep.handle
            if dep.path:
                item["path"] = dep.path
            if dep.source:
                item["source"] = dep.source
            item["type"] = dep.type
            deps_array.append(item)

        doc["dependencies"] = deps_array
        sources_array = tomlkit.aot()
        for source in sources:
            table = tomlkit.table()
            table["name"] = source.name
            table["type"] = source.type
            table["url"] = source.url
            sources_array.append(table)
        doc["source"] = sources_array
        save_path.write_text(tomlkit.dumps(doc))
        self._path = save_path

    def add_dependency(self, dep: Dependency) -> None:
        """Add or update a dependency.

        If a dependency with the same identifier exists, it's replaced.
        """
        self.dependencies = [
            d for d in self.dependencies if d.identifier != dep.identifier
        ]
        self.dependencies.append(dep)

    def remove_dependency(self, identifier: str) -> bool:
        """Remove a dependency by identifier (handle or path).

        Returns:
            True if removed, False if not found
        """
        original_len = len(self.dependencies)
        self.dependencies = [d for d in self.dependencies if d.identifier != identifier]
        return len(self.dependencies) < original_len

    def get_by_identifier(self, identifier: str) -> Dependency | None:
        """Find a dependency by handle or path."""
        for dep in self.dependencies:
            if dep.identifier == identifier:
                return dep
        return None


def find_config(start_path: Path | None = None) -> Path | None:
    """Find agr.toml by walking up from start path.

    Stops at git root or filesystem root.

    Args:
        start_path: Directory to start searching from (defaults to cwd)

    Returns:
        Path to agr.toml if found, None otherwise
    """
    current = start_path or Path.cwd()

    while True:
        config_path = current / "agr.toml"
        if config_path.exists():
            return config_path

        # Stop at git root
        if (current / ".git").exists():
            return None

        parent = current.parent
        if parent == current:
            return None
        current = parent


def find_repo_root(start_path: Path | None = None) -> Path | None:
    """Find the git repository root.

    Args:
        start_path: Directory to start searching from (defaults to cwd)

    Returns:
        Path to repo root if found, None otherwise
    """
    current = start_path or Path.cwd()

    while True:
        if (current / ".git").exists():
            return current

        parent = current.parent
        if parent == current:
            return None
        current = parent


def get_or_create_config(start_path: Path | None = None) -> tuple[Path, AgrConfig]:
    """Get existing config or create new one.

    Args:
        start_path: Directory to start searching from (defaults to cwd)

    Returns:
        Tuple of (path to config, AgrConfig instance)
    """
    existing = find_config(start_path)
    if existing:
        return existing, AgrConfig.load(existing)

    # Create new config in cwd
    cwd = start_path or Path.cwd()
    config_path = cwd / "agr.toml"

    config = AgrConfig()
    config.save(config_path)

    return config_path, config
