"""Git worktree management for parallel agent execution.

Each task gets its own worktree (isolated file state) and branch,
so multiple agents can work simultaneously without conflicts.

Lifecycle:
  1. create_worktree()  - git worktree add + branch
  2. (agent runs in worktree dir)
  3. commit_worktree()   - commit changes in worktree
  4. remove_worktree()   - git worktree remove + cleanup
"""

from __future__ import annotations

import re
import shutil
import subprocess
from dataclasses import dataclass
from pathlib import Path


@dataclass
class Worktree:
    """A single worktree instance."""
    path: Path
    branch: str
    task_id: int
    prompt: str


def _git(*args: str, cwd: str | None = None) -> subprocess.CompletedProcess:
    return subprocess.run(
        ["git", *args],
        capture_output=True, text=True, timeout=30, cwd=cwd,
    )


def _slugify(text: str, max_len: int = 30) -> str:
    """Turn a prompt into a safe branch suffix."""
    slug = re.sub(r"[^a-zA-Z0-9]+", "-", text.lower()).strip("-")
    return slug[:max_len]


def get_repo_root() -> Path | None:
    result = _git("rev-parse", "--show-toplevel")
    if result.returncode == 0:
        return Path(result.stdout.strip())
    return None


def get_current_branch() -> str:
    result = _git("rev-parse", "--abbrev-ref", "HEAD")
    return result.stdout.strip() or "main"


def create_worktree(task_id: int, prompt: str, base_branch: str | None = None) -> Worktree | None:
    """Create a new worktree for a parallel task.

    Returns Worktree on success, None on failure.
    """
    repo_root = get_repo_root()
    if not repo_root:
        return None

    base = base_branch or get_current_branch()
    slug = _slugify(prompt)
    branch = f"krim/parallel-{task_id}-{slug}"
    wt_path = repo_root / ".krim-worktrees" / f"task-{task_id}"

    # ensure parent dir
    wt_path.parent.mkdir(parents=True, exist_ok=True)

    # remove stale worktree if exists
    if wt_path.exists():
        _git("worktree", "remove", "--force", str(wt_path))
        if wt_path.exists():
            shutil.rmtree(wt_path, ignore_errors=True)

    # delete branch if it exists from a previous run
    _git("branch", "-D", branch)

    # create worktree with new branch from base
    result = _git("worktree", "add", "-b", branch, str(wt_path), base)
    if result.returncode != 0:
        return None

    return Worktree(path=wt_path, branch=branch, task_id=task_id, prompt=prompt)


def commit_worktree(wt: Worktree, message: str | None = None) -> bool:
    """Commit all changes in a worktree."""
    cwd = str(wt.path)
    msg = message or f"krim: parallel task {wt.task_id} - {wt.prompt[:60]}"

    # stage all changes
    _git("add", "-A", cwd=cwd)

    # check if there's anything to commit
    status = _git("status", "--porcelain", cwd=cwd)
    if not status.stdout.strip():
        return False

    result = _git("commit", "-m", msg, cwd=cwd)
    return result.returncode == 0


def remove_worktree(wt: Worktree) -> bool:
    """Remove a worktree (keeps the branch)."""
    result = _git("worktree", "remove", "--force", str(wt.path))
    if result.returncode != 0 and wt.path.exists():
        shutil.rmtree(wt.path, ignore_errors=True)
    return True


def cleanup_worktrees() -> None:
    """Remove the .krim-worktrees directory and prune git worktree list."""
    repo_root = get_repo_root()
    if not repo_root:
        return
    wt_dir = repo_root / ".krim-worktrees"
    if wt_dir.exists():
        # remove all worktrees cleanly
        _git("worktree", "prune")
        shutil.rmtree(wt_dir, ignore_errors=True)


def merge_branch(branch: str, into: str | None = None) -> tuple[bool, str]:
    """Merge a worktree branch into the target branch.

    Returns (success, message).
    """
    target = into or get_current_branch()

    # checkout target
    result = _git("checkout", target)
    if result.returncode != 0:
        return False, f"failed to checkout {target}: {result.stderr.strip()}"

    # merge
    result = _git("merge", branch, "--no-edit",
                   "-m", f"krim: merge parallel branch {branch}")
    if result.returncode != 0:
        # conflict - abort and report
        _git("merge", "--abort")
        return False, f"merge conflict with {branch} (left as separate branch)"

    return True, f"merged {branch} into {target}"
