"""
Git Operations Module

Provides git operations with smart commit messages and PR creation.
"""

import subprocess
from dataclasses import dataclass
from pathlib import Path
from typing import Optional


@dataclass
class GitStatus:
    """Git repository status"""

    branch: str
    staged: list[str]
    unstaged: list[str]
    untracked: list[str]
    is_clean: bool
    ahead: int
    behind: int


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

    success: bool
    message: str
    output: Optional[str] = None


class GitOperations:
    """
    Git operations for groknroll agent

    Features:
    - Git status and info
    - Stage, commit, push
    - Smart commit message generation
    - PR creation (via gh CLI)
    - Branch management
    - Git history
    """

    def __init__(self, project_path: Path):
        """
        Initialize git operations

        Args:
            project_path: Git repository root
        """
        self.project_path = project_path.resolve()

        # Verify git is available
        if not self._is_git_available():
            raise RuntimeError("Git is not installed or not in PATH")

        # Verify this is a git repo
        if not self._is_git_repo():
            raise RuntimeError(f"Not a git repository: {project_path}")

    def status(self) -> GitStatus:
        """
        Get git status

        Returns:
            GitStatus with current repository state
        """
        # Get current branch
        branch_result = self._run_git(["branch", "--show-current"])
        branch = branch_result.strip() if branch_result else "unknown"

        # Get status
        status_result = self._run_git(["status", "--porcelain"])
        staged = []
        unstaged = []
        untracked = []

        for line in status_result.splitlines():
            if not line:
                continue

            status_code = line[:2]
            filepath = line[3:]

            if status_code[0] in ("M", "A", "D", "R", "C"):
                staged.append(filepath)
            if status_code[1] in ("M", "D"):
                unstaged.append(filepath)
            if status_code == "??":
                untracked.append(filepath)

        # Get ahead/behind status
        ahead, behind = self._get_ahead_behind()

        return GitStatus(
            branch=branch,
            staged=staged,
            unstaged=unstaged,
            untracked=untracked,
            is_clean=not (staged or unstaged or untracked),
            ahead=ahead,
            behind=behind,
        )

    def add(self, files: Optional[list[Path]] = None, all_files: bool = False) -> GitResult:
        """
        Stage files

        Args:
            files: List of files to stage (or None with all_files=True)
            all_files: Stage all changes

        Returns:
            GitResult
        """
        try:
            if all_files:
                self._run_git(["add", "-A"])
                return GitResult(success=True, message="All changes staged")

            if not files:
                return GitResult(success=False, message="No files specified and all_files=False")

            # Stage specific files
            file_paths = [str(f) for f in files]
            self._run_git(["add"] + file_paths)

            return GitResult(success=True, message=f"Staged {len(files)} file(s)")

        except Exception as e:
            return GitResult(success=False, message=f"Error staging files: {e}")

    def commit(
        self, message: str, files: Optional[list[Path]] = None, amend: bool = False
    ) -> GitResult:
        """
        Create commit

        Args:
            message: Commit message
            files: Files to commit (or all staged if None)
            amend: Amend previous commit

        Returns:
            GitResult
        """
        try:
            # Stage files if provided
            if files:
                self.add(files)

            # Build commit command
            cmd = ["commit", "-m", message]
            if amend:
                cmd.append("--amend")

            output = self._run_git(cmd)

            return GitResult(
                success=True, message=f"Commit created: {message[:50]}...", output=output
            )

        except Exception as e:
            return GitResult(success=False, message=f"Error creating commit: {e}")

    def push(
        self,
        remote: str = "origin",
        branch: Optional[str] = None,
        set_upstream: bool = False,
        force: bool = False,
    ) -> GitResult:
        """
        Push to remote

        Args:
            remote: Remote name
            branch: Branch name (current branch if None)
            set_upstream: Set upstream tracking
            force: Force push (use with caution!)

        Returns:
            GitResult
        """
        try:
            # Get current branch if not specified
            if branch is None:
                status = self.status()
                branch = status.branch

            # Build push command
            cmd = ["push"]
            if set_upstream:
                cmd.extend(["-u", remote, branch])
            else:
                cmd.extend([remote, branch])

            if force:
                cmd.append("--force")

            output = self._run_git(cmd)

            return GitResult(success=True, message=f"Pushed to {remote}/{branch}", output=output)

        except Exception as e:
            return GitResult(success=False, message=f"Error pushing: {e}")

    def pull(
        self, remote: str = "origin", branch: Optional[str] = None, rebase: bool = False
    ) -> GitResult:
        """
        Pull from remote

        Args:
            remote: Remote name
            branch: Branch name
            rebase: Use rebase instead of merge

        Returns:
            GitResult
        """
        try:
            cmd = ["pull"]
            if rebase:
                cmd.append("--rebase")
            cmd.append(remote)
            if branch:
                cmd.append(branch)

            output = self._run_git(cmd)

            return GitResult(success=True, message=f"Pulled from {remote}", output=output)

        except Exception as e:
            return GitResult(success=False, message=f"Error pulling: {e}")

    def create_branch(self, branch_name: str, checkout: bool = True) -> GitResult:
        """
        Create new branch

        Args:
            branch_name: Branch name
            checkout: Checkout after creating

        Returns:
            GitResult
        """
        try:
            if checkout:
                self._run_git(["checkout", "-b", branch_name])
                message = f"Created and checked out branch: {branch_name}"
            else:
                self._run_git(["branch", branch_name])
                message = f"Created branch: {branch_name}"

            return GitResult(success=True, message=message)

        except Exception as e:
            return GitResult(success=False, message=f"Error creating branch: {e}")

    def checkout(self, branch_name: str, create: bool = False) -> GitResult:
        """
        Checkout branch

        Args:
            branch_name: Branch name
            create: Create if doesn't exist

        Returns:
            GitResult
        """
        try:
            if create:
                self._run_git(["checkout", "-b", branch_name])
            else:
                self._run_git(["checkout", branch_name])

            return GitResult(success=True, message=f"Checked out: {branch_name}")

        except Exception as e:
            return GitResult(success=False, message=f"Error checking out branch: {e}")

    def generate_commit_message(self, context: Optional[str] = None) -> str:
        """
        Generate smart commit message based on diff

        Args:
            context: Additional context for commit message

        Returns:
            Generated commit message
        """
        try:
            # Get diff
            diff = self._run_git(["diff", "--cached"])

            if not diff:
                return "chore: update files"

            # Analyze diff to generate message
            message = self._analyze_diff_for_message(diff, context)

            return message

        except Exception as e:
            return f"chore: update files ({e})"

    def create_pr(
        self, title: str, body: Optional[str] = None, base: str = "main", draft: bool = False
    ) -> GitResult:
        """
        Create pull request using GitHub CLI

        Args:
            title: PR title
            body: PR description
            base: Base branch
            draft: Create as draft

        Returns:
            GitResult with PR URL
        """
        try:
            # Check if gh CLI is available
            if not self._is_gh_available():
                return GitResult(
                    success=False,
                    message="GitHub CLI (gh) not installed. Install with: brew install gh",
                )

            # Build gh command
            cmd = ["gh", "pr", "create", "--title", title, "--base", base]

            if body:
                cmd.extend(["--body", body])

            if draft:
                cmd.append("--draft")

            output = subprocess.run(
                cmd, cwd=str(self.project_path), capture_output=True, text=True, check=True
            )

            # Extract PR URL from output
            pr_url = output.stdout.strip()

            return GitResult(success=True, message=f"PR created: {pr_url}", output=pr_url)

        except subprocess.CalledProcessError as e:
            return GitResult(success=False, message=f"Error creating PR: {e.stderr}")
        except Exception as e:
            return GitResult(success=False, message=f"Error creating PR: {e}")

    def log(self, limit: int = 10, oneline: bool = False) -> GitResult:
        """
        Get git log

        Args:
            limit: Number of commits
            oneline: One line per commit

        Returns:
            GitResult with log output
        """
        try:
            cmd = ["log", f"-{limit}"]
            if oneline:
                cmd.append("--oneline")

            output = self._run_git(cmd)

            return GitResult(success=True, message="Git log retrieved", output=output)

        except Exception as e:
            return GitResult(success=False, message=f"Error getting log: {e}")

    def diff(self, cached: bool = False, files: Optional[list[Path]] = None) -> GitResult:
        """
        Get git diff

        Args:
            cached: Show staged changes
            files: Specific files to diff

        Returns:
            GitResult with diff output
        """
        try:
            cmd = ["diff"]
            if cached:
                cmd.append("--cached")

            if files:
                cmd.extend([str(f) for f in files])

            output = self._run_git(cmd)

            return GitResult(success=True, message="Diff retrieved", output=output)

        except Exception as e:
            return GitResult(success=False, message=f"Error getting diff: {e}")

    # =========================================================================
    # Helper Methods
    # =========================================================================

    def _run_git(self, args: list[str]) -> str:
        """Run git command and return output"""
        cmd = ["git"] + args
        result = subprocess.run(
            cmd, cwd=str(self.project_path), capture_output=True, text=True, check=True
        )
        return result.stdout

    def _is_git_available(self) -> bool:
        """Check if git is installed"""
        try:
            subprocess.run(["git", "--version"], capture_output=True, check=True)
            return True
        except (subprocess.CalledProcessError, FileNotFoundError):
            return False

    def _is_gh_available(self) -> bool:
        """Check if GitHub CLI is installed"""
        try:
            subprocess.run(["gh", "--version"], capture_output=True, check=True)
            return True
        except (subprocess.CalledProcessError, FileNotFoundError):
            return False

    def _is_git_repo(self) -> bool:
        """Check if directory is a git repository"""
        try:
            self._run_git(["rev-parse", "--git-dir"])
            return True
        except subprocess.CalledProcessError:
            return False

    def _get_ahead_behind(self) -> tuple[int, int]:
        """Get ahead/behind commit count"""
        try:
            output = self._run_git(["rev-list", "--left-right", "--count", "@{upstream}...HEAD"])
            behind, ahead = output.strip().split()
            return int(ahead), int(behind)
        except (subprocess.CalledProcessError, ValueError):
            return 0, 0

    def _analyze_diff_for_message(self, diff: str, context: Optional[str] = None) -> str:
        """
        Analyze diff to generate commit message

        Args:
            diff: Git diff output
            context: Additional context

        Returns:
            Generated commit message
        """
        # Count changes
        additions = diff.count("\n+") - diff.count("\n+++")
        deletions = diff.count("\n-") - diff.count("\n---")

        # Detect change type
        if "def " in diff or "class " in diff:
            change_type = "feat"
        elif "import " in diff:
            change_type = "refactor"
        elif "test_" in diff or "Test" in diff:
            change_type = "test"
        elif "README" in diff or "# " in diff:
            change_type = "docs"
        elif "fix" in diff.lower() or "bug" in diff.lower():
            change_type = "fix"
        else:
            change_type = "chore"

        # Generate summary
        if additions > deletions * 2:
            summary = "add new functionality"
        elif deletions > additions * 2:
            summary = "remove deprecated code"
        else:
            summary = "update implementation"

        # Add context if provided
        if context:
            message = f"{change_type}: {context}\n\n+{additions} -{deletions}"
        else:
            message = f"{change_type}: {summary}\n\n+{additions} -{deletions}"

        return message

    def get_remote_url(self, remote: str = "origin") -> Optional[str]:
        """Get remote URL"""
        try:
            output = self._run_git(["remote", "get-url", remote])
            return output.strip()
        except subprocess.CalledProcessError:
            return None

    def get_current_branch(self) -> Optional[str]:
        """Get current branch name"""
        try:
            output = self._run_git(["branch", "--show-current"])
            return output.strip()
        except subprocess.CalledProcessError:
            return None

    def list_branches(self, all_branches: bool = False) -> list[str]:
        """List branches"""
        try:
            cmd = ["branch"]
            if all_branches:
                cmd.append("-a")

            output = self._run_git(cmd)
            branches = [
                line.strip().lstrip("* ").strip() for line in output.splitlines() if line.strip()
            ]
            return branches
        except subprocess.CalledProcessError:
            return []
