"""Git worktree management for parallel task execution."""
from __future__ import annotations

import asyncio
import logging
import os
import shutil

logger = logging.getLogger(__name__)


async def _run(cmd: list[str], cwd: str) -> tuple[int, str, str]:
    """Run a git command and return (returncode, stdout, stderr)."""
    proc = await asyncio.create_subprocess_exec(
        *cmd,
        stdout=asyncio.subprocess.PIPE,
        stderr=asyncio.subprocess.PIPE,
        cwd=cwd,
    )
    stdout, stderr = await proc.communicate()
    return proc.returncode or 0, stdout.decode(errors="replace"), stderr.decode(errors="replace")


async def create_worktree(repo_path: str, task_id: str) -> str | None:
    """Create a git worktree for a task. Returns the worktree path, or None on failure."""
    branch = f"dobby/{task_id}"
    worktree_dir = os.path.join(repo_path, ".dobby-worktrees", task_id)

    # Ensure parent dir exists
    os.makedirs(os.path.dirname(worktree_dir), exist_ok=True)

    # Create worktree with new branch from HEAD
    rc, out, err = await _run(
        ["git", "worktree", "add", "-b", branch, worktree_dir, "HEAD"],
        cwd=repo_path,
    )
    if rc != 0:
        logger.warning("Failed to create worktree for %s: %s", task_id, err.strip())
        return None

    logger.info("Created worktree %s at %s", branch, worktree_dir)
    return worktree_dir


async def merge_worktree(repo_path: str, task_id: str) -> tuple[bool, str]:
    """Merge a task's worktree branch back to main. Returns (success, message)."""
    branch = f"dobby/{task_id}"
    worktree_dir = os.path.join(repo_path, ".dobby-worktrees", task_id)

    # Check if worktree has any changes
    rc, diff_out, _ = await _run(["git", "diff", "--stat", "HEAD"], cwd=worktree_dir)
    rc2, staged_out, _ = await _run(["git", "diff", "--staged", "--stat"], cwd=worktree_dir)

    has_changes = bool(diff_out.strip() or staged_out.strip())

    if has_changes:
        # Stage and commit any uncommitted changes in the worktree
        await _run(["git", "add", "-A"], cwd=worktree_dir)
        await _run(
            ["git", "commit", "-m", f"Dobby task {task_id}: auto-commit remaining changes"],
            cwd=worktree_dir,
        )

    # Check if the branch has commits ahead of main
    rc, log_out, _ = await _run(
        ["git", "log", f"HEAD..{branch}", "--oneline"],
        cwd=repo_path,
    )
    if not log_out.strip():
        # No commits to merge
        await cleanup_worktree(repo_path, task_id)
        return True, "No changes to merge"

    # Try to merge the branch into main
    rc, out, err = await _run(
        ["git", "merge", branch, "--no-edit", "-m", f"Merge dobby/{task_id}"],
        cwd=repo_path,
    )

    if rc == 0:
        await cleanup_worktree(repo_path, task_id)
        return True, f"Merged {branch} successfully"

    # Merge conflict — try to resolve
    logger.warning("Merge conflict for %s, attempting auto-resolve", task_id)

    # Try accepting all changes from the branch (theirs)
    rc2, _, _ = await _run(
        ["git", "checkout", "--theirs", "."],
        cwd=repo_path,
    )
    await _run(["git", "add", "-A"], cwd=repo_path)
    rc3, _, err3 = await _run(
        ["git", "commit", "--no-edit", "-m", f"Merge dobby/{task_id} (auto-resolved conflicts)"],
        cwd=repo_path,
    )

    if rc3 == 0:
        await cleanup_worktree(repo_path, task_id)
        return True, f"Merged {branch} with auto-resolved conflicts"

    # Give up — abort the merge
    await _run(["git", "merge", "--abort"], cwd=repo_path)
    await cleanup_worktree(repo_path, task_id)
    return False, f"Could not merge {branch}: conflicts remain"


async def cleanup_worktree(repo_path: str, task_id: str) -> None:
    """Remove worktree and branch for a completed task."""
    branch = f"dobby/{task_id}"
    worktree_dir = os.path.join(repo_path, ".dobby-worktrees", task_id)

    # Remove worktree
    await _run(["git", "worktree", "remove", "--force", worktree_dir], cwd=repo_path)

    # Delete branch
    await _run(["git", "branch", "-D", branch], cwd=repo_path)

    # Clean up directory if still exists
    if os.path.exists(worktree_dir):
        shutil.rmtree(worktree_dir, ignore_errors=True)

    logger.info("Cleaned up worktree for %s", task_id)
