"""Git subprocess wrappers for Legionnaire."""

import subprocess
from dataclasses import dataclass
from pathlib import Path


class GitError(Exception):
    """Raised when a git command fails."""

    pass


@dataclass
class WorktreeEntry:
    """Represents a git worktree from 'git worktree list --porcelain'."""

    path: Path
    head: str | None = None
    branch: str | None = None
    bare: bool = False
    detached: bool = False
    locked: bool = False
    prunable: bool = False


def _run_git(
    args: list[str],
    cwd: Path,
    check: bool = True,
    capture_output: bool = True,
) -> subprocess.CompletedProcess:
    """Run a git command and return the result.

    Args:
        args: Git subcommand and arguments.
        cwd: Working directory for the command.
        check: Whether to raise on non-zero exit.
        capture_output: Whether to capture stdout/stderr.

    Returns:
        The completed process result.

    Raises:
        GitError: If check=True and the command fails.
    """
    try:
        return subprocess.run(
            ["git"] + args,
            cwd=cwd,
            check=check,
            capture_output=capture_output,
            text=True,
        )
    except subprocess.CalledProcessError as e:
        raise GitError(f"git {' '.join(args)} failed: {e.stderr}") from e


def current_head(repo_root: Path) -> str:
    """Get the current HEAD commit SHA.

    Args:
        repo_root: The repository root path.

    Returns:
        The full SHA of HEAD.
    """
    result = _run_git(["rev-parse", "HEAD"], cwd=repo_root)
    return result.stdout.strip()


def current_branch(repo_root: Path) -> str | None:
    """Get the current branch name, or None if detached.

    Args:
        repo_root: The repository root path.

    Returns:
        The branch name, or None if HEAD is detached.
    """
    result = _run_git(
        ["symbolic-ref", "--short", "HEAD"],
        cwd=repo_root,
        check=False,
    )
    if result.returncode != 0:
        return None
    return result.stdout.strip()


def current_upstream(repo_root: Path) -> str | None:
    """Get the upstream tracking ref for the current branch.

    Args:
        repo_root: The repository root path.

    Returns:
        The upstream ref (e.g., 'origin/main'), or None if not configured.
    """
    result = _run_git(
        ["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"],
        cwd=repo_root,
        check=False,
    )
    if result.returncode != 0:
        return None
    return result.stdout.strip()


def rev_parse(repo_root: Path, ref: str) -> str:
    """Resolve a ref to its SHA.

    Args:
        repo_root: The repository root path.
        ref: The ref to resolve (branch name, tag, SHA prefix, etc.).

    Returns:
        The full SHA of the ref.
    """
    result = _run_git(["rev-parse", ref], cwd=repo_root)
    return result.stdout.strip()


def branch_exists(repo_root: Path, branch: str) -> bool:
    """Check if a branch exists.

    Args:
        repo_root: The repository root path.
        branch: The branch name to check.

    Returns:
        True if the branch exists, False otherwise.
    """
    result = _run_git(
        ["show-ref", "--verify", "--quiet", f"refs/heads/{branch}"],
        cwd=repo_root,
        check=False,
    )
    return result.returncode == 0


def worktree_list_porcelain(repo_root: Path) -> list[WorktreeEntry]:
    """List all worktrees in porcelain format.

    Args:
        repo_root: The repository root path.

    Returns:
        A list of WorktreeEntry objects.
    """
    result = _run_git(["worktree", "list", "--porcelain"], cwd=repo_root)

    entries: list[WorktreeEntry] = []
    current_entry: dict = {}

    for line in result.stdout.splitlines():
        line = line.strip()
        if not line:
            if current_entry:
                entries.append(
                    WorktreeEntry(
                        path=Path(current_entry["worktree"]),
                        head=current_entry.get("HEAD"),
                        branch=current_entry.get("branch"),
                        bare=current_entry.get("bare", False),
                        detached=current_entry.get("detached", False),
                        locked=current_entry.get("locked", False),
                        prunable=current_entry.get("prunable", False),
                    )
                )
                current_entry = {}
            continue

        if line.startswith("worktree "):
            current_entry["worktree"] = line[9:]
        elif line.startswith("HEAD "):
            current_entry["HEAD"] = line[5:]
        elif line.startswith("branch "):
            # branch refs/heads/foo -> foo
            branch_ref = line[7:]
            if branch_ref.startswith("refs/heads/"):
                current_entry["branch"] = branch_ref[11:]
            else:
                current_entry["branch"] = branch_ref
        elif line == "bare":
            current_entry["bare"] = True
        elif line == "detached":
            current_entry["detached"] = True
        elif line.startswith("locked"):
            current_entry["locked"] = True
        elif line.startswith("prunable"):
            current_entry["prunable"] = True

    # Handle last entry if no trailing blank line
    if current_entry:
        entries.append(
            WorktreeEntry(
                path=Path(current_entry["worktree"]),
                head=current_entry.get("HEAD"),
                branch=current_entry.get("branch"),
                bare=current_entry.get("bare", False),
                detached=current_entry.get("detached", False),
                locked=current_entry.get("locked", False),
                prunable=current_entry.get("prunable", False),
            )
        )

    return entries


def ensure_worktree_add(
    repo_root: Path,
    worktree_path: Path,
    branch: str,
    base_ref: str,
) -> bool:
    """Add a worktree with a new branch if it doesn't exist.

    Args:
        repo_root: The repository root path.
        worktree_path: The path for the new worktree.
        branch: The name for the new branch.
        base_ref: The ref to base the new branch on.

    Returns:
        True if the worktree was created, False if it already existed.
    """
    # Check if worktree already exists
    existing = worktree_list_porcelain(repo_root)
    for entry in existing:
        if entry.path == worktree_path:
            return False

    # Check if branch already exists
    if branch_exists(repo_root, branch):
        # Branch exists, checkout without -b
        _run_git(
            ["worktree", "add", str(worktree_path), branch],
            cwd=repo_root,
        )
    else:
        # Create new branch
        _run_git(
            ["worktree", "add", "-b", branch, str(worktree_path), base_ref],
            cwd=repo_root,
        )

    return True


def set_branch_upstream(repo_root: Path, branch: str, upstream: str) -> None:
    """Set the upstream tracking ref for a branch.

    Args:
        repo_root: The repository root path.
        branch: The branch to configure.
        upstream: The upstream ref to track.
    """
    _run_git(
        ["branch", "--set-upstream-to", upstream, branch],
        cwd=repo_root,
    )


def worktree_remove(repo_root: Path, worktree_path: Path, force: bool = False) -> None:
    """Remove a worktree.

    Args:
        repo_root: The repository root path.
        worktree_path: The path to the worktree to remove.
        force: If True, remove even if dirty.
    """
    args = ["worktree", "remove"]
    if force:
        args.append("--force")
    args.append(str(worktree_path))
    _run_git(args, cwd=repo_root)


def branch_delete(repo_root: Path, branch: str, force: bool = False) -> None:
    """Delete a branch.

    Args:
        repo_root: The repository root path.
        branch: The branch name to delete.
        force: If True, use -D (force delete even if not merged).
    """
    flag = "-D" if force else "-d"
    _run_git(["branch", flag, branch], cwd=repo_root)


def is_worktree_dirty(worktree_path: Path) -> bool:
    """Check if a worktree has uncommitted changes.

    Args:
        worktree_path: The path to the worktree.

    Returns:
        True if there are uncommitted changes, False otherwise.
    """
    result = _run_git(
        ["status", "--porcelain"],
        cwd=worktree_path,
        check=False,
    )
    return bool(result.stdout.strip())


def list_branches_with_prefix(repo_root: Path, prefix: str) -> list[str]:
    """List all branches that start with a given prefix.

    Args:
        repo_root: The repository root path.
        prefix: The prefix to match (e.g., 'feature-x/').

    Returns:
        A list of branch names matching the prefix.
    """
    result = _run_git(
        ["for-each-ref", "--format=%(refname:short)", "refs/heads/"],
        cwd=repo_root,
    )
    branches = []
    for line in result.stdout.splitlines():
        line = line.strip()
        if line.startswith(prefix):
            branches.append(line)
    return branches
