"""GitHub integration utilities using GitHub CLI (gh)."""

import json
import subprocess
from datetime import datetime
from pathlib import Path
from typing import Dict, Any, List, Optional, Tuple

from rich.console import Console
from rich.progress import Progress, SpinnerColumn, TextColumn

console = Console()


class GitHubError(Exception):
    """Exception raised for GitHub-related errors."""

    pass


class GitHubCLI:
    """GitHub CLI wrapper for issue and PR management."""

    def __init__(self, repo: str):
        """Initialize GitHub CLI wrapper.

        Args:
            repo: Repository in format "owner/repo"
        """
        self.repo = repo

        # Verify gh is available
        if not self._check_gh_available():
            raise GitHubError("GitHub CLI (gh) is not installed or not in PATH")

        # Verify authentication
        if not self._check_gh_auth():
            raise GitHubError("GitHub CLI is not authenticated. Run 'gh auth login'")

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

    def _check_gh_auth(self) -> bool:
        """Check if gh is authenticated."""
        try:
            result = subprocess.run(
                ["gh", "auth", "status"], capture_output=True, text=True
            )
            return result.returncode == 0
        except subprocess.CalledProcessError:
            return False

    def _run_gh_command(self, args: List[str]) -> str:
        """Run a gh command and return output."""
        try:
            result = subprocess.run(
                ["gh"] + args, capture_output=True, text=True, check=True
            )
            return result.stdout
        except subprocess.CalledProcessError as e:
            raise GitHubError(f"GitHub CLI error: {e.stderr}")

    def create_issue(
        self,
        title: str,
        body: str,
        labels: Optional[List[str]] = None,
        assignees: Optional[List[str]] = None,
    ) -> Dict[str, Any]:
        """Create a GitHub issue.

        Args:
            title: Issue title
            body: Issue body/description
            labels: List of labels to apply
            assignees: List of assignees (GitHub usernames)

        Returns:
            Dict containing issue details (number, url, etc.)
        """
        args = [
            "issue",
            "create",
            "--repo",
            self.repo,
            "--title",
            title,
            "--body",
            body,
        ]

        if labels:
            args.extend(["--label", ",".join(labels)])

        if assignees:
            args.extend(["--assignee", ",".join(assignees)])

        # Add JSON output
        args.append("--json")
        args.append("number,title,url,state,createdAt,id")

        output = self._run_gh_command(args)
        return json.loads(output)

    def list_issues(
        self, state: str = "open", labels: Optional[List[str]] = None, limit: int = 100
    ) -> List[Dict[str, Any]]:
        """List GitHub issues.

        Args:
            state: Issue state (open, closed, all)
            labels: Filter by labels
            limit: Maximum number of issues to return

        Returns:
            List of issue dictionaries
        """
        args = [
            "issue",
            "list",
            "--repo",
            self.repo,
            "--state",
            state,
            "--limit",
            str(limit),
            "--json",
            "number,title,body,state,labels,assignees,createdAt,updatedAt,id,url",
        ]

        if labels:
            args.extend(["--label", ",".join(labels)])

        output = self._run_gh_command(args)
        return json.loads(output)

    def get_issue(self, number: int) -> Dict[str, Any]:
        """Get a specific issue by number.

        Args:
            number: Issue number

        Returns:
            Issue dictionary
        """
        args = [
            "issue",
            "view",
            str(number),
            "--repo",
            self.repo,
            "--json",
            "number,title,body,state,labels,assignees,createdAt,updatedAt,id,url",
        ]

        output = self._run_gh_command(args)
        return json.loads(output)

    def update_issue(
        self,
        number: int,
        title: Optional[str] = None,
        body: Optional[str] = None,
        state: Optional[str] = None,
        labels: Optional[List[str]] = None,
        assignees: Optional[List[str]] = None,
    ) -> Dict[str, Any]:
        """Update a GitHub issue.

        Args:
            number: Issue number
            title: New title (optional)
            body: New body (optional)
            state: New state (open/closed) (optional)
            labels: New labels (optional)
            assignees: New assignees (optional)

        Returns:
            Updated issue dictionary
        """
        args = ["issue", "edit", str(number), "--repo", self.repo]

        if title:
            args.extend(["--title", title])

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

        if state:
            args.extend(["--state", state])

        if labels is not None:
            args.extend(["--add-label", ",".join(labels)])

        if assignees is not None:
            args.extend(["--add-assignee", ",".join(assignees)])

        self._run_gh_command(args)

        # Get updated issue
        return self.get_issue(number)

    def close_issue(self, number: int) -> Dict[str, Any]:
        """Close a GitHub issue.

        Args:
            number: Issue number

        Returns:
            Updated issue dictionary
        """
        return self.update_issue(number, state="closed")

    def create_pr(
        self,
        title: str,
        body: str,
        base: str = "main",
        head: Optional[str] = None,
        draft: bool = False,
        labels: Optional[List[str]] = None,
        assignees: Optional[List[str]] = None,
    ) -> Dict[str, Any]:
        """Create a GitHub pull request.

        Args:
            title: PR title
            body: PR body/description
            base: Base branch (default: main)
            head: Head branch (default: current branch)
            draft: Create as draft PR
            labels: List of labels to apply
            assignees: List of assignees

        Returns:
            Dict containing PR details
        """
        args = [
            "pr",
            "create",
            "--repo",
            self.repo,
            "--title",
            title,
            "--body",
            body,
            "--base",
            base,
        ]

        if head:
            args.extend(["--head", head])

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

        if labels:
            args.extend(["--label", ",".join(labels)])

        if assignees:
            args.extend(["--assignee", ",".join(assignees)])

        # Add JSON output
        args.append("--json")
        args.append("number,title,url,state,createdAt,id")

        output = self._run_gh_command(args)
        return json.loads(output)

    def list_prs(
        self, state: str = "open", labels: Optional[List[str]] = None, limit: int = 100
    ) -> List[Dict[str, Any]]:
        """List GitHub pull requests.

        Args:
            state: PR state (open, closed, merged, all)
            labels: Filter by labels
            limit: Maximum number of PRs to return

        Returns:
            List of PR dictionaries
        """
        args = [
            "pr",
            "list",
            "--repo",
            self.repo,
            "--state",
            state,
            "--limit",
            str(limit),
            "--json",
            "number,title,body,state,labels,assignees,createdAt,updatedAt,id,url,headRefName,baseRefName",
        ]

        if labels:
            args.extend(["--label", ",".join(labels)])

        output = self._run_gh_command(args)
        return json.loads(output)

    def sync_issue_to_github(
        self, task: Any, dry_run: bool = False
    ) -> Optional[Dict[str, Any]]:
        """Sync a local task to GitHub as an issue.

        Args:
            task: Task object with id, title, description, etc.
            dry_run: If True, don't actually create the issue

        Returns:
            Created issue dictionary or None if dry run
        """
        if dry_run:
            console.print(f"[yellow]DRY RUN:[/yellow] Would create issue: {task.title}")
            return None

        # Prepare labels - filter out 'issue' tag
        labels = [tag for tag in task.tags if tag != "issue"]

        # Create issue
        issue = self.create_issue(
            title=task.title,
            body=task.description or "No description provided.",
            labels=labels,
            assignees=task.assignees if task.assignees else None,
        )

        return issue

    def sync_pr_to_github(
        self, task: Any, dry_run: bool = False
    ) -> Optional[Dict[str, Any]]:
        """Sync a local task to GitHub as a pull request.

        Args:
            task: Task object with id, title, description, etc.
            dry_run: If True, don't actually create the PR

        Returns:
            Created PR dictionary or None if dry run
        """
        if dry_run:
            console.print(f"[yellow]DRY RUN:[/yellow] Would create PR: {task.title}")
            return None

        # For PRs, we need to determine branches
        # This is a simplified version - in reality you'd want more logic here
        base = task.metadata.get("base_branch", "main")
        head = task.metadata.get("head_branch")

        if not head:
            raise GitHubError(
                f"Task {task.id} is marked as PR but has no head_branch in metadata"
            )

        # Prepare labels - filter out 'pull-request' tag
        labels = [tag for tag in task.tags if tag != "pull-request"]

        # Create PR
        pr = self.create_pr(
            title=task.title,
            body=task.description or "No description provided.",
            base=base,
            head=head,
            labels=labels,
            assignees=task.assignees if task.assignees else None,
        )

        return pr

    def pull_issues_from_github(
        self, task_manager: Any, dry_run: bool = False
    ) -> Tuple[int, int]:
        """Pull issues from GitHub and create/update local tasks.

        Args:
            task_manager: TaskManager instance
            dry_run: If True, don't actually create/update tasks

        Returns:
            Tuple of (created_count, updated_count)
        """
        created = 0
        updated = 0

        with Progress(
            SpinnerColumn(),
            TextColumn("[progress.description]{task.description}"),
            console=console,
        ) as progress:
            task = progress.add_task("Fetching issues from GitHub...", total=None)

            # Get all open issues
            issues = self.list_issues(state="open")

            progress.update(task, description=f"Processing {len(issues)} issues...")

            for issue in issues:
                # Check if we already have this issue locally
                existing_tasks = task_manager.list_tasks()
                existing_task = None

                for task_obj in existing_tasks:
                    if task_obj.metadata.get("github_id") == issue["id"]:
                        existing_task = task_obj
                        break

                if existing_task:
                    # Update existing task
                    if not dry_run:
                        task_manager.update_task(
                            existing_task.id,
                            title=issue["title"],
                            description=issue.get("body", ""),
                            status="open" if issue["state"] == "open" else "closed",
                            tags=["issue"]
                            + [label["name"] for label in issue.get("labels", [])],
                            assignees=[a["login"] for a in issue.get("assignees", [])],
                            metadata={
                                **existing_task.metadata,
                                "github_number": issue["number"],
                                "github_url": issue["url"],
                                "github_updated": issue["updatedAt"],
                            },
                        )
                    updated += 1
                else:
                    # Create new task
                    if not dry_run:
                        task_manager.create_task(
                            title=issue["title"],
                            description=issue.get("body", ""),
                            status="open" if issue["state"] == "open" else "closed",
                            tags=["issue"]
                            + [label["name"] for label in issue.get("labels", [])],
                            assignees=[a["login"] for a in issue.get("assignees", [])],
                            metadata={
                                "github_id": issue["id"],
                                "github_number": issue["number"],
                                "github_url": issue["url"],
                                "github_created": issue["createdAt"],
                                "github_updated": issue["updatedAt"],
                                "imported_from": "github",
                            },
                        )
                    created += 1

        return created, updated
