"""GitHub CLI integration utilities for peon-mcp.

This module provides shared functions for interacting with GitHub via the gh CLI,
primarily for PR creation and management.
"""

import asyncio
import logging
import os
import shutil

logger = logging.getLogger(__name__)


async def create_feature_pr(feature: dict, project_path: str) -> str:
    """Auto-create a GitHub PR for a feature branch merging to main.

    Uses the GitHub CLI (gh) to create a pull request merging the feature branch
    into main. If a PR already exists for the branch, attempts to find and return
    the existing PR URL instead of failing.

    Args:
        feature: Feature dict with keys: name, description, branch
        project_path: Absolute path to the git repository

    Returns:
        PR URL (https://github.com/...) as a string

    Raises:
        ValueError: If feature has no branch configured or branch is invalid
        FileNotFoundError: If GitHub CLI (gh) is not installed or not in PATH
        RuntimeError: If PR creation fails for other reasons (auth, network, etc.)
    """
    # Validate feature has a branch
    if not feature.get("branch"):
        raise ValueError("Feature has no branch configured. Cannot create PR without a branch.")

    branch = feature["branch"]

    # Validate branch exists and is pushed to remote
    branch_check = await asyncio.create_subprocess_exec(
        "git", "rev-parse", "--verify", f"origin/{branch}",
        cwd=project_path,
        stdout=asyncio.subprocess.PIPE,
        stderr=asyncio.subprocess.PIPE,
    )
    await branch_check.communicate()

    if branch_check.returncode != 0:
        # Try local branch check
        local_check = await asyncio.create_subprocess_exec(
            "git", "rev-parse", "--verify", branch,
            cwd=project_path,
            stdout=asyncio.subprocess.PIPE,
            stderr=asyncio.subprocess.PIPE,
        )
        await local_check.communicate()

        if local_check.returncode == 0:
            raise ValueError(
                f"Branch '{branch}' exists locally but hasn't been pushed to remote. "
                f"Push the branch first: git push -u origin {branch}"
            )
        else:
            raise ValueError(
                f"Branch '{branch}' does not exist. "
                f"Create and push the branch before completing the feature."
            )

    pr_body = f"## Feature: {feature['name']}\n\n{feature.get('description', '')}"

    # Try to create PR via gh CLI
    try:
        proc = await asyncio.create_subprocess_exec(
            "gh", "pr", "create",
            "--base", "main",
            "--head", branch,
            "--title", feature["name"],
            "--body", pr_body,
            cwd=project_path,
            stdout=asyncio.subprocess.PIPE,
            stderr=asyncio.subprocess.PIPE,
        )
        stdout, stderr = await proc.communicate()

        if proc.returncode != 0:
            stderr_text = stderr.decode().strip()

            # Check if PR already exists
            if "already exists" in stderr_text.lower() or "pull request already exists" in stderr_text.lower():
                # Try to find existing PR
                list_proc = await asyncio.create_subprocess_exec(
                    "gh", "pr", "list",
                    "--head", branch,
                    "--json", "url",
                    "--jq", ".[0].url",
                    cwd=project_path,
                    stdout=asyncio.subprocess.PIPE,
                    stderr=asyncio.subprocess.PIPE,
                )
                list_stdout, list_stderr = await list_proc.communicate()

                if list_proc.returncode == 0:
                    pr_url = list_stdout.decode().strip()

                    # Defensive checks for race conditions and invalid data
                    if not pr_url or pr_url == "null" or pr_url == "":
                        # PR might have been closed, deleted, or is in an inaccessible state
                        raise RuntimeError(
                            f"A PR for branch '{branch}' was recently created but is no longer accessible. "
                            f"Please check GitHub manually and provide the PR URL, or verify the PR still exists."
                        )

                    if not pr_url.startswith("http"):
                        # Invalid URL format - could be jq parsing error or unexpected output
                        raise RuntimeError(
                            f"Found PR reference but URL format is invalid: {pr_url}. "
                            f"Please provide PR URL manually."
                        )

                    return pr_url
                else:
                    raise RuntimeError(
                        f"A PR already exists for branch '{branch}' but could not be retrieved. "
                        f"GitHub CLI error: {list_stderr.decode().strip()}"
                    )
            else:
                # Other error - raise it
                raise RuntimeError(f"Failed to create PR via GitHub CLI: {stderr_text}")
        else:
            # Success - parse PR URL from stdout
            pr_url = stdout.decode().strip()
            # gh pr create typically outputs the URL on the last line
            if "\n" in pr_url:
                pr_url = pr_url.split("\n")[-1].strip()

            if not pr_url or not pr_url.startswith("http"):
                raise RuntimeError(
                    f"PR created but could not parse URL from GitHub CLI output: {stdout.decode()}"
                )

            return pr_url

    except FileNotFoundError:
        raise FileNotFoundError(
            "GitHub CLI (gh) is not installed or not in PATH. "
            "Please install it from https://cli.github.com/"
        )


async def merge_main_into_feature(feature: dict, project_path: str) -> dict:
    """Merge origin/main into the feature branch to resolve conflicts.

    Creates a temporary git worktree, merges origin/main into the feature branch
    in that worktree, and pushes the result if the merge is clean.

    Args:
        feature: Feature dict with keys: id, name, branch
        project_path: Absolute path to the git repository

    Returns:
        dict with keys:
            success (bool): True if merge succeeded and was pushed
            conflicts (list[str]): List of conflicting file paths (empty on success)
            message (str): Human-readable status message
            commit_sha (str | None): New HEAD commit SHA on success, None on failure

    Raises:
        ValueError: If feature has no branch configured
        RuntimeError: If git operations fail unexpectedly
    """
    branch = feature.get("branch")
    if not branch:
        raise ValueError("Feature has no branch configured. Cannot merge without a branch.")

    feature_id = feature.get("id", "tmp")
    worktree_path = os.path.join(project_path, ".peon-tmp", f"conflict-resolve-{feature_id}")

    # Ensure the temp directory parent exists
    os.makedirs(os.path.join(project_path, ".peon-tmp"), exist_ok=True)

    # Remove any leftover temp worktree from a previous failed run
    if os.path.exists(worktree_path):
        cleanup = await asyncio.create_subprocess_exec(
            "git", "worktree", "remove", "--force", worktree_path,
            cwd=project_path,
            stdout=asyncio.subprocess.PIPE,
            stderr=asyncio.subprocess.PIPE,
        )
        await cleanup.communicate()
        # If the directory still exists, remove it manually
        if os.path.exists(worktree_path):
            shutil.rmtree(worktree_path, ignore_errors=True)

    # 1. Fetch latest state from origin
    fetch_proc = await asyncio.create_subprocess_exec(
        "git", "fetch", "origin",
        cwd=project_path,
        stdout=asyncio.subprocess.PIPE,
        stderr=asyncio.subprocess.PIPE,
    )
    fetch_stdout, fetch_stderr = await fetch_proc.communicate()
    if fetch_proc.returncode != 0:
        raise RuntimeError(f"git fetch failed: {fetch_stderr.decode().strip()}")

    # 2. Verify the feature branch exists on remote
    branch_check = await asyncio.create_subprocess_exec(
        "git", "rev-parse", "--verify", f"origin/{branch}",
        cwd=project_path,
        stdout=asyncio.subprocess.PIPE,
        stderr=asyncio.subprocess.PIPE,
    )
    await branch_check.communicate()
    if branch_check.returncode != 0:
        raise ValueError(
            f"Branch '{branch}' not found on remote. Push the branch before resolving conflicts."
        )

    # 3. Create a detached-HEAD worktree at the feature branch tip
    add_proc = await asyncio.create_subprocess_exec(
        "git", "worktree", "add", "--detach", worktree_path, f"origin/{branch}",
        cwd=project_path,
        stdout=asyncio.subprocess.PIPE,
        stderr=asyncio.subprocess.PIPE,
    )
    add_stdout, add_stderr = await add_proc.communicate()
    if add_proc.returncode != 0:
        raise RuntimeError(
            f"Failed to create temporary worktree: {add_stderr.decode().strip()}"
        )

    try:
        # 4. Merge origin/main into the feature branch (no edit needed for CI-style)
        merge_proc = await asyncio.create_subprocess_exec(
            "git", "merge", "origin/main", "--no-edit",
            cwd=worktree_path,
            stdout=asyncio.subprocess.PIPE,
            stderr=asyncio.subprocess.PIPE,
        )
        merge_stdout, merge_stderr = await merge_proc.communicate()

        if merge_proc.returncode != 0:
            # Collect the conflicting files
            status_proc = await asyncio.create_subprocess_exec(
                "git", "diff", "--name-only", "--diff-filter=U",
                cwd=worktree_path,
                stdout=asyncio.subprocess.PIPE,
                stderr=asyncio.subprocess.PIPE,
            )
            status_stdout, _ = await status_proc.communicate()
            conflicts = [f for f in status_stdout.decode().strip().split("\n") if f]

            # Abort the failed merge to restore a clean state
            abort_proc = await asyncio.create_subprocess_exec(
                "git", "merge", "--abort",
                cwd=worktree_path,
                stdout=asyncio.subprocess.PIPE,
                stderr=asyncio.subprocess.PIPE,
            )
            await abort_proc.communicate()

            return {
                "success": False,
                "conflicts": conflicts,
                "message": (
                    f"Merge conflicts in {len(conflicts)} file(s): "
                    f"{', '.join(conflicts) if conflicts else 'see git output'}. "
                    "Manual resolution required."
                ),
                "commit_sha": None,
            }

        # 5. Capture the new HEAD commit SHA
        sha_proc = await asyncio.create_subprocess_exec(
            "git", "rev-parse", "HEAD",
            cwd=worktree_path,
            stdout=asyncio.subprocess.PIPE,
            stderr=asyncio.subprocess.PIPE,
        )
        sha_stdout, _ = await sha_proc.communicate()
        commit_sha = sha_stdout.decode().strip()

        # 6. Push the updated branch back to origin
        push_proc = await asyncio.create_subprocess_exec(
            "git", "push", "origin", f"HEAD:{branch}",
            cwd=worktree_path,
            stdout=asyncio.subprocess.PIPE,
            stderr=asyncio.subprocess.PIPE,
        )
        push_stdout, push_stderr = await push_proc.communicate()
        if push_proc.returncode != 0:
            raise RuntimeError(
                f"Failed to push updated branch '{branch}': {push_stderr.decode().strip()}"
            )

        return {
            "success": True,
            "conflicts": [],
            "message": f"Successfully merged origin/main into '{branch}' and pushed.",
            "commit_sha": commit_sha,
        }

    finally:
        # Always clean up the temporary worktree
        cleanup_proc = await asyncio.create_subprocess_exec(
            "git", "worktree", "remove", "--force", worktree_path,
            cwd=project_path,
            stdout=asyncio.subprocess.PIPE,
            stderr=asyncio.subprocess.PIPE,
        )
        await cleanup_proc.communicate()


async def merge_feature_pr(pr_url: str, project_path: str) -> bool:
    """Merge a feature PR using gh CLI.

    Respects the PEON_AUTO_MERGE_ENABLED and PEON_AUTO_MERGE_METHOD env vars.
    Returns True on success, False on failure (never raises).
    """
    if os.environ.get("PEON_AUTO_MERGE_ENABLED", "").lower() not in ("1", "true", "yes"):
        logger.info("merge_feature_pr: auto-merge disabled, skipping merge of %s", pr_url)
        return False

    method = os.environ.get("PEON_AUTO_MERGE_METHOD", "merge").lower()
    if method not in ("merge", "squash", "rebase"):
        method = "merge"

    try:
        proc = await asyncio.create_subprocess_exec(
            "gh", "pr", "merge", pr_url,
            f"--{method}",
            "--delete-branch",
            "--admin",
            cwd=project_path,
            stdout=asyncio.subprocess.PIPE,
            stderr=asyncio.subprocess.PIPE,
        )
        stdout, stderr = await proc.communicate()

        if proc.returncode != 0:
            logger.error(
                "merge_feature_pr: failed to merge %s: %s",
                pr_url, stderr.decode().strip(),
            )
            return False

        logger.info("merge_feature_pr: %s merged successfully (%s)", pr_url, method)
        return True

    except FileNotFoundError:
        logger.error("merge_feature_pr: gh CLI not found — cannot merge %s", pr_url)
        return False
    except Exception:
        logger.exception("merge_feature_pr: unexpected error merging %s", pr_url)
        return False
