"""Git worktree management for isolated agent execution."""

import shutil
import subprocess
import time
import uuid
from contextlib import contextmanager
from dataclasses import dataclass, field
from pathlib import Path
from typing import Iterator


@dataclass
class Worktree:
    """Represents a git worktree."""

    name: str
    path: Path
    ref: str
    created_at: float = field(default_factory=time.time)


class WorktreeManager:
    """Manages git worktrees for isolated agent execution.

    Each agent gets its own worktree to prevent conflicts when
    multiple agents are working on the same codebase.
    """

    def __init__(
        self,
        repo_root: Path,
        worktree_root: Path | None = None,
    ):
        """Initialize the worktree manager.

        Args:
            repo_root: Path to the main git repository.
            worktree_root: Path where worktrees will be created.
                          Defaults to <repo_root>/.deliberate-worktrees
        """
        self.repo_root = repo_root.resolve()
        self.worktree_root = (
            worktree_root or repo_root / ".deliberate-worktrees"
        ).resolve()
        self._active: dict[str, Worktree] = {}

    def create(self, name: str | None = None, ref: str = "HEAD") -> Worktree:
        """Create a new worktree.

        Args:
            name: Name for the worktree. Auto-generated if not provided.
            ref: Git ref to base the worktree on.

        Returns:
            Worktree object representing the created worktree.

        Raises:
            ValueError: If a worktree with the given name already exists.
            RuntimeError: If worktree creation fails.
        """
        name = name or f"jury-{uuid.uuid4().hex[:8]}"
        wt_path = self.worktree_root / name

        if wt_path.exists():
            raise ValueError(f"Worktree {name} already exists at {wt_path}")

        # Ensure worktree root exists
        self.worktree_root.mkdir(parents=True, exist_ok=True)

        # Create detached worktree
        result = subprocess.run(
            ["git", "worktree", "add", "--detach", str(wt_path), ref],
            cwd=self.repo_root,
            capture_output=True,
            text=True,
        )

        if result.returncode != 0:
            raise RuntimeError(f"Failed to create worktree: {result.stderr}")

        worktree = Worktree(name=name, path=wt_path, ref=ref)
        self._active[name] = worktree
        return worktree

    def remove(self, name: str, force: bool = False) -> None:
        """Remove a worktree.

        Args:
            name: Name of the worktree to remove.
            force: If True, force removal even if there are uncommitted changes.
        """
        wt_path = self.worktree_root / name

        if not wt_path.exists():
            self._active.pop(name, None)
            return

        cmd = ["git", "worktree", "remove"]
        if force:
            cmd.append("--force")
        cmd.append(str(wt_path))

        result = subprocess.run(
            cmd,
            cwd=self.repo_root,
            capture_output=True,
            text=True,
        )

        # If removal failed and force is requested, try manual cleanup
        if result.returncode != 0 and force:
            shutil.rmtree(wt_path, ignore_errors=True)
            subprocess.run(
                ["git", "worktree", "prune"],
                cwd=self.repo_root,
                capture_output=True,
            )

        self._active.pop(name, None)

    def get_diff(self, worktree: Worktree) -> str:
        """Get the diff of changes in a worktree.

        Args:
            worktree: The worktree to get the diff for.

        Returns:
            The git diff as a string.
        """
        result = subprocess.run(
            ["git", "diff", "HEAD"],
            cwd=worktree.path,
            capture_output=True,
            text=True,
        )
        return result.stdout

    def get_status(self, worktree: Worktree) -> str:
        """Get the status of a worktree.

        Args:
            worktree: The worktree to get status for.

        Returns:
            The git status output.
        """
        result = subprocess.run(
            ["git", "status", "--porcelain"],
            cwd=worktree.path,
            capture_output=True,
            text=True,
        )
        return result.stdout

    def commit_changes(
        self,
        worktree: Worktree,
        message: str,
        add_all: bool = True,
    ) -> str | None:
        """Commit changes in a worktree.

        Args:
            worktree: The worktree to commit in.
            message: Commit message.
            add_all: If True, add all changes before committing.

        Returns:
            The commit hash, or None if there were no changes.
        """
        if add_all:
            subprocess.run(
                ["git", "add", "-A"],
                cwd=worktree.path,
                capture_output=True,
            )

        # Check if there are changes to commit
        status = self.get_status(worktree)
        if not status.strip():
            return None

        result = subprocess.run(
            ["git", "commit", "-m", message],
            cwd=worktree.path,
            capture_output=True,
            text=True,
        )

        if result.returncode != 0:
            return None

        # Get the commit hash
        hash_result = subprocess.run(
            ["git", "rev-parse", "HEAD"],
            cwd=worktree.path,
            capture_output=True,
            text=True,
        )
        return hash_result.stdout.strip()

    @contextmanager
    def temporary(self, name: str | None = None, ref: str = "HEAD") -> Iterator[Worktree]:
        """Create a temporary worktree that is cleaned up automatically.

        Args:
            name: Name for the worktree. Auto-generated if not provided.
            ref: Git ref to base the worktree on.

        Yields:
            The created worktree.
        """
        worktree = self.create(name=name, ref=ref)
        try:
            yield worktree
        finally:
            self.remove(worktree.name, force=True)

    def cleanup_all(self) -> None:
        """Remove all active worktrees managed by this instance."""
        for name in list(self._active.keys()):
            self.remove(name, force=True)

        # Prune any stale worktree references
        subprocess.run(
            ["git", "worktree", "prune"],
            cwd=self.repo_root,
            capture_output=True,
        )

    def list_worktrees(self) -> list[Worktree]:
        """List all active worktrees.

        Returns:
            List of active Worktree objects.
        """
        return list(self._active.values())

    @property
    def active_count(self) -> int:
        """Get the number of active worktrees."""
        return len(self._active)
