"""Git operation tools for the gjalla-precommit CLI.

Uses GitPython for all git operations with graceful error handling.
Adapted patterns from Aider's battle-tested repo.py implementation.
"""

from __future__ import annotations

from pathlib import Path
from typing import TYPE_CHECKING

from git import InvalidGitRepositoryError, Repo

if TYPE_CHECKING:
    from .executor import ToolResult


# Comprehensive git error handling tuple (from Aider)
# Catches all git-related exceptions plus common Python errors
# that can occur during git operations
try:
    import git

    ANY_GIT_ERROR: list[type[Exception]] = [
        git.exc.ODBError,
        git.exc.GitError,
        git.exc.InvalidGitRepositoryError,
        git.exc.GitCommandNotFound,
    ]
except ImportError:
    git = None  # type: ignore
    ANY_GIT_ERROR = []

ANY_GIT_ERROR += [
    OSError,
    IndexError,
    BufferError,
    TypeError,
    ValueError,
    AttributeError,
    AssertionError,
    TimeoutError,
]
ANY_GIT_ERROR_TUPLE: tuple[type[Exception], ...] = tuple(ANY_GIT_ERROR)


def _get_tool_result() -> type:
    """Import ToolResult lazily to avoid circular imports."""
    from .executor import ToolResult
    return ToolResult


def _get_repo(repo_root: Path) -> Repo:
    """Get git repository from path.

    Args:
        repo_root: Path to the repository root.

    Returns:
        Git Repo object.

    Raises:
        InvalidGitRepositoryError: If path is not a git repository.
    """
    return Repo(repo_root, search_parent_directories=True)


async def git_status(repo_root: Path) -> "ToolResult":
    """Get staged/unstaged files status.

    Args:
        repo_root: Path to the repository root.

    Returns:
        ToolResult with success=True and data=status string on success,
        or success=False and error message on failure.
    """
    ToolResult = _get_tool_result()

    try:
        repo = _get_repo(repo_root)
        status = repo.git.status()
        return ToolResult(success=True, data=status)
    except InvalidGitRepositoryError:
        return ToolResult(success=False, error="Not a git repository")
    except ANY_GIT_ERROR_TUPLE as e:
        return ToolResult(success=False, error=str(e))


async def git_log(repo_root: Path, n: int = 5) -> "ToolResult":
    """Get recent commits.

    Args:
        repo_root: Path to the repository root.
        n: Number of commits to retrieve (default: 5).

    Returns:
        ToolResult with success=True and data=log string on success,
        or success=False and error message on failure.
    """
    ToolResult = _get_tool_result()

    try:
        repo = _get_repo(repo_root)
        log = repo.git.log(f"-{n}", "--oneline")
        return ToolResult(success=True, data=log)
    except InvalidGitRepositoryError:
        return ToolResult(success=False, error="Not a git repository")
    except ANY_GIT_ERROR_TUPLE as e:
        return ToolResult(success=False, error=str(e))


def get_remote_url(repo_root: Path) -> str | None:
    """Get origin remote URL.

    Args:
        repo_root: Path to the repository root.

    Returns:
        Origin remote URL as string, None if no origin remote,
        or error message on failure.
    """
    try:
        repo = _get_repo(repo_root)
        if "origin" in [remote.name for remote in repo.remotes]:
            return repo.remotes.origin.url
        return None
    except InvalidGitRepositoryError:
        return "Error: Not a git repository"
    except ANY_GIT_ERROR_TUPLE as e:
        return f"Error: {e}"


def is_git_repo(repo_root: Path) -> bool:
    """Check if path is a git repository.

    Args:
        repo_root: Path to check.

    Returns:
        True if path is a git repository, False otherwise.
    """
    try:
        _get_repo(repo_root)
        return True
    except InvalidGitRepositoryError:
        return False
    except ANY_GIT_ERROR_TUPLE:
        return False


def get_current_branch(repo_root: Path) -> str:
    """Get current branch name.

    Args:
        repo_root: Path to the repository root.

    Returns:
        Current branch name, or error message on failure.
    """
    try:
        repo = _get_repo(repo_root)
        return repo.active_branch.name
    except InvalidGitRepositoryError:
        return "Error: Not a git repository"
    except TypeError:
        return "Error: Detached HEAD state"
    except ANY_GIT_ERROR_TUPLE as e:
        return f"Error: {e}"


async def get_diff(repo_root: Path, base: str, head: str) -> "ToolResult":
    """Get diff between two commits.

    Args:
        repo_root: Path to the repository root.
        base: Base commit/ref for diff.
        head: Head commit/ref for diff.

    Returns:
        ToolResult with success=True and data=diff string on success,
        or success=False and error message on failure.
    """
    ToolResult = _get_tool_result()

    try:
        repo = _get_repo(repo_root)
        diff = repo.git.diff(base, head)
        return ToolResult(success=True, data=diff)
    except InvalidGitRepositoryError:
        return ToolResult(success=False, error="Not a git repository")
    except ANY_GIT_ERROR_TUPLE as e:
        return ToolResult(success=False, error=str(e))
