"""
Git Operations Module

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

import subprocess
from pathlib import Path
from typing import Optional, List, Dict, Any
from dataclasses import dataclass
import re


@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 []
