"""Git operations tool."""

import logging
import subprocess
from enum import Enum
from typing import Annotated, Any

from arcade_tdk import ToolContext, tool

from cade_mcp_local.config import DEFAULT_TIMEOUT
from cade_mcp_local.errors import GitOperationError, InvalidInputError

log = logging.getLogger(__name__)


class GitAction(str, Enum):
    """Git operation to perform."""

    STATUS = "status"
    DIFF = "diff"
    LOG = "log"
    BRANCH = "branch"


class BranchAction(str, Enum):
    """Git branch sub-operation."""

    LIST = "list"
    CURRENT = "current"
    CREATE = "create"
    DELETE = "delete"
    SWITCH = "switch"


def _run_git_command(
    args: list[str],
    timeout: int = DEFAULT_TIMEOUT,
) -> tuple[str, str | None, int]:
    """Run a git command and return (stdout, stderr_if_error, exit_code).

    Runs in the project root directory (from CADE_PROJECT_ROOT or cwd).
    """
    from cade_mcp_local.config import get_project_root

    command = ["git"] + args
    cwd = str(get_project_root())

    try:
        log.debug(f"Running: {' '.join(command)} in {cwd}")
        result = subprocess.run(
            command,
            capture_output=True,
            text=True,
            timeout=timeout,
            check=False,
            cwd=cwd,
        )
        # Use rstrip to preserve leading whitespace (important for git status parsing)
        stdout = result.stdout.rstrip()
        stderr = result.stderr.strip() if result.returncode != 0 else None
        return stdout, stderr, result.returncode
    except subprocess.TimeoutExpired:
        return "", "Command timed out", 1
    except Exception as e:
        log.error(f"Git command failed: {e}")
        return "", str(e), 1


# ==============================================================================
# Helper functions (exported for use by other modules)
# ==============================================================================


def get_current_branch_name() -> tuple[str, str | None]:
    """Get the current active Git branch name.

    Returns:
        Tuple of (branch_name, error_message). error_message is None on success.
    """
    stdout, stderr, _ = _run_git_command(["rev-parse", "--abbrev-ref", "HEAD"])
    return stdout, stderr


def get_status() -> tuple[str, str | None]:
    """Get repository status using 'git status --short'.

    Returns:
        Tuple of (status_output, error_message). error_message is None on success.
    """
    stdout, stderr, _ = _run_git_command(["status", "--short"])
    return stdout, stderr


# ==============================================================================
# Consolidated Git tool
# ==============================================================================


@tool(
    name="Git",
    desc=(
        "Git operations on the local repository. "
        "action='status': working tree status. "
        "action='diff': show changes between commits/working tree. "
        "action='log': commit history. "
        "action='branch': list, create, delete, or switch branches."
    ),
)
def git_tool(
    context: ToolContext,
    action: Annotated[GitAction, "Git operation: status, diff, log, or branch."] = GitAction.STATUS,
    # status params
    short: Annotated[bool, "Use short format (status)."] = True,
    # diff params
    path: Annotated[str, "File or directory path to scope the operation (diff, log)."] = "",
    staged: Annotated[bool, "Show staged changes, --cached (diff)."] = False,
    commit: Annotated[str, "Compare with a commit ref, e.g. 'HEAD~1', 'main' (diff)."] = "",
    stat: Annotated[bool, "Show diffstat instead of full diff (diff)."] = False,
    name_only: Annotated[bool, "Show only changed file names (diff)."] = False,
    # log params
    count: Annotated[int, "Number of commits to show (log)."] = 10,
    oneline: Annotated[bool, "One line per commit (log)."] = True,
    author: Annotated[str, "Filter by author name or email (log)."] = "",
    since: Annotated[str, "Commits since date, e.g. '2 weeks ago' (log)."] = "",
    until: Annotated[str, "Commits until date (log)."] = "",
    grep: Annotated[str, "Search commit messages for this text (log)."] = "",
    # branch params
    branch_action: Annotated[
        BranchAction, "Branch sub-operation (when action=branch)."
    ] = BranchAction.LIST,
    name: Annotated[str, "Branch name (required for create/delete/switch)."] = "",
    all_branches: Annotated[bool, "Include remote branches (branch list)."] = False,
    force: Annotated[bool, "Force action (branch delete)."] = False,
) -> Annotated[dict[str, Any], "Git operation result."]:
    """Perform git operations on the local repository."""
    if action == GitAction.STATUS:
        return _git_status(short=short)
    elif action == GitAction.DIFF:
        return _git_diff(
            path=path,
            staged=staged,
            commit=commit,
            stat=stat,
            name_only=name_only,
        )
    elif action == GitAction.LOG:
        return _git_log(
            count=count,
            oneline=oneline,
            path=path,
            author=author,
            since=since,
            until=until,
            grep=grep,
        )
    elif action == GitAction.BRANCH:
        return _git_branch(
            branch_action=branch_action,
            name=name,
            all_branches=all_branches,
            force=force,
        )
    else:
        raise InvalidInputError("action", f"Unknown action: {action}")


def _git_status(*, short: bool) -> dict[str, Any]:
    args = ["status"]
    if short:
        args.append("--short")

    stdout, stderr, exit_code = _run_git_command(args)

    if stderr:
        raise GitOperationError("status", stderr)

    files = []
    for line in stdout.split("\n"):
        if not line.strip():
            continue
        if short and len(line) >= 3:
            status = line[:2]
            filename = line[3:].strip()
            files.append({"status": status, "file": filename})
        else:
            files.append({"line": line})

    branch, _ = get_current_branch_name()

    return {
        "branch": branch or "unknown",
        "files": files,
        "clean": len(files) == 0,
        "raw_output": stdout,
    }


def _git_diff(
    *,
    path: str,
    staged: bool,
    commit: str,
    stat: bool,
    name_only: bool,
) -> dict[str, Any]:
    args = ["diff"]

    if staged:
        args.append("--cached")
    if commit:
        args.append(commit)
    if stat:
        args.append("--stat")
    if name_only:
        args.append("--name-only")
    if path:
        args.extend(["--", path])

    stdout, stderr, exit_code = _run_git_command(args, timeout=60)

    if stderr and exit_code != 0:
        raise GitOperationError("diff", stderr)

    if name_only:
        files = [f.strip() for f in stdout.split("\n") if f.strip()]
        return {"files": files, "count": len(files)}

    return {
        "diff": stdout or "(no changes)",
        "has_changes": bool(stdout.strip()),
    }


def _git_log(
    *,
    count: int,
    oneline: bool,
    path: str,
    author: str,
    since: str,
    until: str,
    grep: str,
) -> dict[str, Any]:
    count = max(1, count)
    args = ["log", f"-{count}"]

    if oneline:
        args.append("--oneline")
    else:
        args.extend(["--format=%H|%an|%ae|%ad|%s", "--date=short"])

    if author:
        args.extend(["--author", author])
    if since:
        args.extend(["--since", since])
    if until:
        args.extend(["--until", until])
    if grep:
        args.extend(["--grep", grep])
    if path:
        args.extend(["--", path])

    stdout, stderr, exit_code = _run_git_command(args)

    if stderr and exit_code != 0:
        raise GitOperationError("log", stderr)

    commits = []
    for line in stdout.split("\n"):
        if not line.strip():
            continue

        if oneline:
            parts = line.split(" ", 1)
            commits.append(
                {
                    "hash": parts[0],
                    "message": parts[1] if len(parts) > 1 else "",
                }
            )
        else:
            parts = line.split("|", 4)
            if len(parts) >= 5:
                commits.append(
                    {
                        "hash": parts[0],
                        "author": parts[1],
                        "email": parts[2],
                        "date": parts[3],
                        "message": parts[4],
                    }
                )

    return {"commits": commits, "count": len(commits)}


def _git_branch(
    *,
    branch_action: BranchAction,
    name: str,
    all_branches: bool,
    force: bool,
) -> dict[str, Any]:
    if branch_action == BranchAction.LIST:
        args = ["branch", "-a"] if all_branches else ["branch"]
        stdout, stderr, exit_code = _run_git_command(args)

        if stderr and exit_code != 0:
            raise GitOperationError("branch list", stderr)

        branches = []
        current = None
        for line in stdout.split("\n"):
            if not line.strip():
                continue
            is_current = line.startswith("*")
            branch_name = line.lstrip("* ").strip()
            if is_current:
                current = branch_name
            branches.append({"name": branch_name, "current": is_current})

        return {"branches": branches, "current": current, "count": len(branches)}

    elif branch_action == BranchAction.CURRENT:
        branch, error = get_current_branch_name()
        if error:
            raise GitOperationError("get current branch", error)
        return {"current": branch}

    elif branch_action == BranchAction.CREATE:
        if not name:
            raise InvalidInputError("name", "required for create action")

        stdout, stderr, exit_code = _run_git_command(["checkout", "-b", name])
        if stderr and exit_code != 0:
            raise GitOperationError("create branch", stderr)

        return {
            "success": True,
            "message": f"Created and switched to branch '{name}'",
            "branch": name,
        }

    elif branch_action == BranchAction.DELETE:
        if not name:
            raise InvalidInputError("name", "required for delete action")

        flag = "-D" if force else "-d"
        stdout, stderr, exit_code = _run_git_command(["branch", flag, name])
        if stderr and exit_code != 0:
            raise GitOperationError("delete branch", stderr)

        return {"success": True, "message": f"Deleted branch '{name}'"}

    elif branch_action == BranchAction.SWITCH:
        if not name:
            raise InvalidInputError("name", "required for switch action")

        stdout, stderr, exit_code = _run_git_command(["checkout", name])
        if stderr and exit_code != 0:
            raise GitOperationError("switch branch", stderr)

        return {
            "success": True,
            "message": f"Switched to branch '{name}'",
            "branch": name,
        }

    else:
        raise InvalidInputError(
            "branch_action",
            f"'{branch_action}' is not valid. Use: list, current, create, delete, switch",
        )
