"""Git operations -- real subprocess-based git commands for Hive Builder.

All code changes go through git branches + PRs. Never direct to main.
Safety: every operation validates state before executing.
"""

from __future__ import annotations

import logging
import subprocess
from dataclasses import dataclass
from typing import TYPE_CHECKING, Any

if TYPE_CHECKING:
    from pathlib import Path

logger = logging.getLogger("hive.git")


@dataclass
class GitResult:
    """Result of a git operation."""

    success: bool
    stdout: str
    stderr: str
    returncode: int


class GitOperations:
    """Safe git operations for the Hive Builder agent.

    All commands run via subprocess with timeouts and error capture.
    Never force-pushes. Never pushes to main/develop directly.
    """

    def __init__(self, workspace_root: Path, timeout: int = 60) -> None:
        self._root = workspace_root
        self._timeout = timeout

    def _run(self, *args: str, timeout: int | None = None) -> GitResult:
        """Execute a git command safely."""
        cmd = ["git", *args]
        logger.debug("git %s", " ".join(args))
        try:
            result = subprocess.run(
                cmd,
                cwd=self._root,
                capture_output=True,
                text=True,
                timeout=timeout or self._timeout,
                check=False,
            )
            if result.returncode != 0:
                logger.warning("git %s failed (rc=%d): %s", args[0], result.returncode, result.stderr.strip())
            return GitResult(
                success=result.returncode == 0,
                stdout=result.stdout.strip(),
                stderr=result.stderr.strip(),
                returncode=result.returncode,
            )
        except subprocess.TimeoutExpired:
            logger.error("git %s timed out after %ds", args[0], timeout or self._timeout)
            return GitResult(success=False, stdout="", stderr="timeout", returncode=-1)
        except FileNotFoundError:
            logger.error("git not found in PATH")
            return GitResult(success=False, stdout="", stderr="git not found", returncode=-1)

    # ------------------------------------------------------------------
    # Query operations (read-only)
    # ------------------------------------------------------------------

    def current_branch(self) -> str | None:
        """Get the current branch name."""
        result = self._run("rev-parse", "--abbrev-ref", "HEAD")
        return result.stdout if result.success else None

    def is_clean(self) -> bool:
        """Check if the working directory is clean (no uncommitted changes)."""
        result = self._run("status", "--porcelain")
        return result.success and result.stdout == ""

    def has_remote(self, name: str = "origin") -> bool:
        """Check if a remote exists."""
        result = self._run("remote", "get-url", name)
        return result.success

    def log(self, count: int = 10, format_str: str = "%H|%s|%an|%ai") -> list[dict[str, str]]:
        """Get recent commit log."""
        result = self._run("log", f"-{count}", f"--format={format_str}")
        if not result.success:
            return []
        entries = []
        for line in result.stdout.splitlines():
            parts = line.split("|", 3)
            if len(parts) == 4:
                entries.append(
                    {
                        "hash": parts[0],
                        "subject": parts[1],
                        "author": parts[2],
                        "date": parts[3],
                    }
                )
        return entries

    def diff_stat(self, base: str = "main") -> str:
        """Get diff stat against a base branch."""
        result = self._run("diff", "--stat", base)
        return result.stdout if result.success else ""

    def diff_files(self, base: str = "main") -> list[str]:
        """Get list of changed files against a base branch."""
        result = self._run("diff", "--name-only", base)
        return result.stdout.splitlines() if result.success else []

    def last_green_commit(self) -> str | None:
        """Find the last commit where tests passed (using git notes or tags)."""
        result = self._run("tag", "-l", "hive/green-*", "--sort=-creatordate")
        if result.success and result.stdout:
            tag = result.stdout.splitlines()[0]
            hash_result = self._run("rev-parse", tag)
            return hash_result.stdout if hash_result.success else None
        return None

    # ------------------------------------------------------------------
    # Branch operations
    # ------------------------------------------------------------------

    def create_branch(self, branch_name: str, base: str = "main") -> GitResult:
        """Create and checkout a new branch from base.

        Safety: refuses to create branches that don't start with 'hive/'.
        """
        if not branch_name.startswith("hive/"):
            return GitResult(
                success=False,
                stdout="",
                stderr="Hive branches must start with 'hive/'",
                returncode=1,
            )

        # Fetch latest from remote first
        self._run("fetch", "origin", base)

        # Create branch from origin/base (or local base if no remote)
        result = self._run("checkout", "-b", branch_name, f"origin/{base}")
        if not result.success:
            # Fallback: try local base branch
            result = self._run("checkout", "-b", branch_name, base)
        return result

    def checkout(self, branch: str) -> GitResult:
        """Switch to an existing branch."""
        return self._run("checkout", branch)

    def delete_branch(self, branch: str) -> GitResult:
        """Delete a local branch. Safety: only Hive branches."""
        if not branch.startswith("hive/"):
            return GitResult(success=False, stdout="", stderr="Can only delete hive/ branches", returncode=1)
        return self._run("branch", "-D", branch)

    # ------------------------------------------------------------------
    # Commit operations
    # ------------------------------------------------------------------

    def stage_files(self, files: list[str]) -> GitResult:
        """Stage specific files for commit."""
        if not files:
            return GitResult(success=False, stdout="", stderr="No files to stage", returncode=1)
        return self._run("add", *files)

    def stage_all(self) -> GitResult:
        """Stage all changes."""
        return self._run("add", "-A")

    def commit(self, message: str) -> GitResult:
        """Create a commit with a message.

        Hive commits follow convention: hive(HIVE-NNN): description
        """
        # Configure committer identity for Hive
        self._run("config", "user.name", "Hive.CEO Agent")
        self._run("config", "user.email", "hive@localhost")
        return self._run("commit", "-m", message)

    # ------------------------------------------------------------------
    # Push operations
    # ------------------------------------------------------------------

    def push(self, branch: str) -> GitResult:
        """Push a branch to origin.

        Safety: refuses to push to main or develop.
        """
        protected = {"main", "master", "develop", "production"}
        if branch in protected:
            return GitResult(
                success=False,
                stdout="",
                stderr=f"Cannot push directly to protected branch: {branch}",
                returncode=1,
            )
        return self._run("push", "-u", "origin", branch, timeout=120)

    # ------------------------------------------------------------------
    # PR operations (via gh CLI)
    # ------------------------------------------------------------------

    def create_pr(
        self,
        title: str,
        body: str,
        head: str,
        base: str = "main",
        labels: list[str] | None = None,
    ) -> GitResult:
        """Create a GitHub PR using gh CLI.

        Returns GitResult where stdout contains the PR URL on success.
        """
        cmd = [
            "gh",
            "pr",
            "create",
            "--title",
            title,
            "--body",
            body,
            "--head",
            head,
            "--base",
            base,
        ]
        if labels:
            for label in labels:
                cmd.extend(["--label", label])

        logger.info("Creating PR: %s (%s → %s)", title, head, base)
        try:
            result = subprocess.run(
                cmd,
                cwd=self._root,
                capture_output=True,
                text=True,
                timeout=30,
                check=False,
            )
            return GitResult(
                success=result.returncode == 0,
                stdout=result.stdout.strip(),
                stderr=result.stderr.strip(),
                returncode=result.returncode,
            )
        except (subprocess.TimeoutExpired, FileNotFoundError) as e:
            return GitResult(success=False, stdout="", stderr=str(e), returncode=-1)

    def get_pr_status(self, branch: str) -> dict[str, Any]:
        """Get PR status for a branch using gh CLI."""
        try:
            result = subprocess.run(
                ["gh", "pr", "view", branch, "--json", "number,state,url,statusCheckRollup"],
                cwd=self._root,
                capture_output=True,
                text=True,
                timeout=15,
                check=False,
            )
            if result.returncode == 0:
                import json

                data: dict[str, Any] = json.loads(result.stdout)
                return data
        except Exception:
            pass
        return {"state": "unknown"}

    # ------------------------------------------------------------------
    # Composite operations (used by Builder)
    # ------------------------------------------------------------------

    def prepare_change(
        self,
        item_id: str,
        description: str,
        base: str = "main",
    ) -> GitResult:
        """Create a branch for a Hive improvement and checkout.

        Returns the branch creation result.
        Branch name: hive/{item_id}-{sanitized_description}
        """
        safe_desc = description.lower().replace(" ", "-")[:40]
        # Remove non-alphanumeric characters except hyphens
        safe_desc = "".join(c for c in safe_desc if c.isalnum() or c == "-")
        branch_name = f"hive/{item_id}-{safe_desc}"
        return self.create_branch(branch_name, base)

    def submit_change(
        self,
        item_id: str,
        title: str,
        body: str,
        files: list[str],
        base: str = "main",
    ) -> GitResult:
        """Stage, commit, push, and create PR -- the full Builder workflow.

        Returns the PR creation result (stdout = PR URL).
        """
        branch = self.current_branch()
        if not branch or not branch.startswith("hive/"):
            return GitResult(success=False, stdout="", stderr="Not on a hive/ branch", returncode=1)

        # Stage
        stage_result = self.stage_files(files) if files else self.stage_all()
        if not stage_result.success:
            return stage_result

        # Commit
        commit_msg = f"hive({item_id}): {title}"
        commit_result = self.commit(commit_msg)
        if not commit_result.success:
            return commit_result

        # Push
        push_result = self.push(branch)
        if not push_result.success:
            return push_result

        # Create PR
        return self.create_pr(
            title=f"hive({item_id}): {title}",
            body=body,
            head=branch,
            base=base,
            labels=["hive", "automated"],
        )
