"""
PR Automation - Automatically create PRs from groknroll changes

Provides automation for creating pull requests:
- Create feature branch from main
- Commit changes
- Push branch
- Create PR with title and description
- Link PR to original issue

Examples:
    # Create PR automation handler
    handler = PRAutomation(github_token="...")

    # Create PR from changes
    result = await handler.create_pr(
        repo="owner/repo",
        title="Fix auth bug",
        changes=["src/auth.py"],
        issue_number=42,
    )
"""

import re
from dataclasses import dataclass, field
from enum import Enum, auto
from typing import Any, Optional

import httpx


class PRStatus(Enum):
    """Status of PR creation"""

    PENDING = auto()
    BRANCH_CREATED = auto()
    COMMITTED = auto()
    PUSHED = auto()
    PR_CREATED = auto()
    FAILED = auto()


@dataclass
class PRResult:
    """Result of PR creation"""

    status: PRStatus
    pr_url: Optional[str] = None
    pr_number: Optional[int] = None
    branch_name: Optional[str] = None
    error: Optional[str] = None
    metadata: dict[str, Any] = field(default_factory=dict)

    @property
    def success(self) -> bool:
        """Check if PR was created successfully"""
        return self.status == PRStatus.PR_CREATED

    @property
    def failed(self) -> bool:
        """Check if PR creation failed"""
        return self.status == PRStatus.FAILED


@dataclass
class PRConfig:
    """Configuration for PR creation"""

    base_branch: str = "main"
    branch_prefix: str = "groknroll/"
    auto_merge: bool = False
    draft: bool = False
    labels: list[str] = field(default_factory=list)
    assignees: list[str] = field(default_factory=list)
    reviewers: list[str] = field(default_factory=list)


@dataclass
class CommitInfo:
    """Information about a commit"""

    sha: str
    message: str
    author: str
    files_changed: list[str] = field(default_factory=list)


class PRAutomation:
    """
    Automate PR creation from groknroll changes

    Handles the full flow of creating a PR:
    1. Create feature branch
    2. Create commit with changes
    3. Push branch
    4. Open PR
    5. Link to issue

    Example:
        handler = PRAutomation(github_token="...")
        result = await handler.create_pr(
            repo="owner/repo",
            title="Fix bug",
            changes=["file.py"],
            issue_number=123,
        )
    """

    GITHUB_API_URL = "https://api.github.com"

    def __init__(
        self,
        github_token: str,
        config: Optional[PRConfig] = None,
    ):
        """
        Initialize PR automation

        Args:
            github_token: GitHub API token
            config: Optional PR configuration
        """
        self.github_token = github_token
        self.config = config or PRConfig()

    def generate_branch_name(self, title: str) -> str:
        """
        Generate branch name from title

        Args:
            title: PR title

        Returns:
            Branch name
        """
        # Sanitize title for branch name
        sanitized = re.sub(r"[^a-zA-Z0-9\s-]", "", title.lower())
        sanitized = re.sub(r"\s+", "-", sanitized)
        sanitized = sanitized[:50]  # Limit length

        return f"{self.config.branch_prefix}{sanitized}"

    def format_pr_body(
        self,
        description: str,
        issue_number: Optional[int] = None,
        changes: Optional[list[str]] = None,
    ) -> str:
        """
        Format PR body with standard template

        Args:
            description: PR description
            issue_number: Related issue number
            changes: List of changed files

        Returns:
            Formatted PR body
        """
        lines = ["## Summary", "", description, ""]

        if issue_number:
            lines.extend(
                [
                    "## Related Issue",
                    "",
                    f"Closes #{issue_number}",
                    "",
                ]
            )

        if changes:
            lines.extend(
                [
                    "## Changes",
                    "",
                    *[f"- `{f}`" for f in changes],
                    "",
                ]
            )

        lines.extend(
            [
                "---",
                "*This PR was automatically created by groknroll.*",
            ]
        )

        return "\n".join(lines)

    async def get_default_branch(
        self,
        repo: str,
        client: Optional[httpx.AsyncClient] = None,
    ) -> str:
        """
        Get default branch for repository

        Args:
            repo: Repository in "owner/repo" format
            client: Optional HTTP client

        Returns:
            Default branch name
        """
        close_client = client is None
        if client is None:
            client = httpx.AsyncClient()

        try:
            url = f"{self.GITHUB_API_URL}/repos/{repo}"
            headers = self._get_headers()

            response = await client.get(url, headers=headers)

            if response.status_code == 200:
                data = response.json()
                return data.get("default_branch", self.config.base_branch)

            return self.config.base_branch

        finally:
            if close_client:
                await client.aclose()

    async def get_branch_sha(
        self,
        repo: str,
        branch: str,
        client: Optional[httpx.AsyncClient] = None,
    ) -> Optional[str]:
        """
        Get SHA of branch HEAD

        Args:
            repo: Repository
            branch: Branch name
            client: Optional HTTP client

        Returns:
            SHA or None
        """
        close_client = client is None
        if client is None:
            client = httpx.AsyncClient()

        try:
            url = f"{self.GITHUB_API_URL}/repos/{repo}/git/refs/heads/{branch}"
            headers = self._get_headers()

            response = await client.get(url, headers=headers)

            if response.status_code == 200:
                data = response.json()
                return data.get("object", {}).get("sha")

            return None

        finally:
            if close_client:
                await client.aclose()

    async def create_branch(
        self,
        repo: str,
        branch_name: str,
        base_sha: str,
        client: Optional[httpx.AsyncClient] = None,
    ) -> bool:
        """
        Create a new branch

        Args:
            repo: Repository
            branch_name: New branch name
            base_sha: SHA to branch from
            client: Optional HTTP client

        Returns:
            True if created
        """
        close_client = client is None
        if client is None:
            client = httpx.AsyncClient()

        try:
            url = f"{self.GITHUB_API_URL}/repos/{repo}/git/refs"
            headers = self._get_headers()

            response = await client.post(
                url,
                headers=headers,
                json={
                    "ref": f"refs/heads/{branch_name}",
                    "sha": base_sha,
                },
            )

            return response.status_code == 201

        finally:
            if close_client:
                await client.aclose()

    async def create_pull_request(
        self,
        repo: str,
        title: str,
        body: str,
        head: str,
        base: str,
        draft: bool = False,
        client: Optional[httpx.AsyncClient] = None,
    ) -> Optional[dict[str, Any]]:
        """
        Create a pull request

        Args:
            repo: Repository
            title: PR title
            body: PR body
            head: Head branch
            base: Base branch
            draft: Create as draft
            client: Optional HTTP client

        Returns:
            PR data or None
        """
        close_client = client is None
        if client is None:
            client = httpx.AsyncClient()

        try:
            url = f"{self.GITHUB_API_URL}/repos/{repo}/pulls"
            headers = self._get_headers()

            response = await client.post(
                url,
                headers=headers,
                json={
                    "title": title,
                    "body": body,
                    "head": head,
                    "base": base,
                    "draft": draft,
                },
            )

            if response.status_code == 201:
                return response.json()

            return None

        finally:
            if close_client:
                await client.aclose()

    async def add_labels(
        self,
        repo: str,
        issue_number: int,
        labels: list[str],
        client: Optional[httpx.AsyncClient] = None,
    ) -> bool:
        """
        Add labels to issue/PR

        Args:
            repo: Repository
            issue_number: Issue/PR number
            labels: Labels to add
            client: Optional HTTP client

        Returns:
            True if added
        """
        if not labels:
            return True

        close_client = client is None
        if client is None:
            client = httpx.AsyncClient()

        try:
            url = f"{self.GITHUB_API_URL}/repos/{repo}/issues/{issue_number}/labels"
            headers = self._get_headers()

            response = await client.post(
                url,
                headers=headers,
                json={"labels": labels},
            )

            return response.status_code == 200

        finally:
            if close_client:
                await client.aclose()

    async def add_assignees(
        self,
        repo: str,
        issue_number: int,
        assignees: list[str],
        client: Optional[httpx.AsyncClient] = None,
    ) -> bool:
        """
        Add assignees to issue/PR

        Args:
            repo: Repository
            issue_number: Issue/PR number
            assignees: Assignees to add
            client: Optional HTTP client

        Returns:
            True if added
        """
        if not assignees:
            return True

        close_client = client is None
        if client is None:
            client = httpx.AsyncClient()

        try:
            url = f"{self.GITHUB_API_URL}/repos/{repo}/issues/{issue_number}/assignees"
            headers = self._get_headers()

            response = await client.post(
                url,
                headers=headers,
                json={"assignees": assignees},
            )

            return response.status_code == 201

        finally:
            if close_client:
                await client.aclose()

    async def request_reviewers(
        self,
        repo: str,
        pr_number: int,
        reviewers: list[str],
        client: Optional[httpx.AsyncClient] = None,
    ) -> bool:
        """
        Request reviewers for PR

        Args:
            repo: Repository
            pr_number: PR number
            reviewers: Reviewers to request
            client: Optional HTTP client

        Returns:
            True if requested
        """
        if not reviewers:
            return True

        close_client = client is None
        if client is None:
            client = httpx.AsyncClient()

        try:
            url = f"{self.GITHUB_API_URL}/repos/{repo}/pulls/{pr_number}/requested_reviewers"
            headers = self._get_headers()

            response = await client.post(
                url,
                headers=headers,
                json={"reviewers": reviewers},
            )

            return response.status_code == 201

        finally:
            if close_client:
                await client.aclose()

    async def create_pr(
        self,
        repo: str,
        title: str,
        description: str = "",
        changes: Optional[list[str]] = None,
        issue_number: Optional[int] = None,
        branch_name: Optional[str] = None,
        client: Optional[httpx.AsyncClient] = None,
    ) -> PRResult:
        """
        Create a complete PR

        This is the main entry point that orchestrates:
        1. Getting base branch SHA
        2. Creating feature branch
        3. Creating the PR
        4. Adding labels/assignees/reviewers

        Args:
            repo: Repository in "owner/repo" format
            title: PR title
            description: PR description
            changes: List of changed files
            issue_number: Related issue number
            branch_name: Custom branch name
            client: Optional HTTP client

        Returns:
            PRResult with status and details
        """
        close_client = client is None
        if client is None:
            client = httpx.AsyncClient()

        try:
            # Generate branch name if not provided
            if not branch_name:
                branch_name = self.generate_branch_name(title)

            # Get default branch
            base_branch = await self.get_default_branch(repo, client)

            # Get base branch SHA
            base_sha = await self.get_branch_sha(repo, base_branch, client)
            if not base_sha:
                return PRResult(
                    status=PRStatus.FAILED,
                    branch_name=branch_name,
                    error=f"Could not get SHA for {base_branch}",
                )

            # Create branch
            branch_created = await self.create_branch(repo, branch_name, base_sha, client)
            if not branch_created:
                return PRResult(
                    status=PRStatus.FAILED,
                    branch_name=branch_name,
                    error="Could not create branch",
                )

            # Format PR body
            body = self.format_pr_body(description, issue_number, changes)

            # Create PR
            pr_data = await self.create_pull_request(
                repo=repo,
                title=title,
                body=body,
                head=branch_name,
                base=base_branch,
                draft=self.config.draft,
                client=client,
            )

            if not pr_data:
                return PRResult(
                    status=PRStatus.FAILED,
                    branch_name=branch_name,
                    error="Could not create PR",
                )

            pr_number = pr_data.get("number")
            pr_url = pr_data.get("html_url")

            # Add labels
            if self.config.labels:
                await self.add_labels(repo, pr_number, self.config.labels, client)

            # Add assignees
            if self.config.assignees:
                await self.add_assignees(repo, pr_number, self.config.assignees, client)

            # Request reviewers
            if self.config.reviewers:
                await self.request_reviewers(repo, pr_number, self.config.reviewers, client)

            return PRResult(
                status=PRStatus.PR_CREATED,
                pr_url=pr_url,
                pr_number=pr_number,
                branch_name=branch_name,
                metadata={
                    "base_branch": base_branch,
                    "issue_number": issue_number,
                },
            )

        except Exception as e:
            return PRResult(
                status=PRStatus.FAILED,
                branch_name=branch_name,
                error=str(e),
            )

        finally:
            if close_client:
                await client.aclose()

    def _get_headers(self) -> dict[str, str]:
        """Get API headers"""
        return {
            "Authorization": f"token {self.github_token}",
            "Accept": "application/vnd.github+json",
            "X-GitHub-Api-Version": "2022-11-28",
        }


def generate_pr_title(task: str, issue_number: Optional[int] = None) -> str:
    """
    Generate PR title from task

    Args:
        task: Task description
        issue_number: Related issue number

    Returns:
        PR title
    """
    # Capitalize first letter
    title = task[0].upper() + task[1:] if task else ""

    # Remove trailing punctuation
    title = title.rstrip(".")

    # Add issue reference
    if issue_number:
        title = f"{title} (#{issue_number})"

    return title


def generate_branch_name(task: str, prefix: str = "groknroll/") -> str:
    """
    Generate branch name from task

    Args:
        task: Task description
        prefix: Branch prefix

    Returns:
        Branch name
    """
    # Sanitize for branch name
    sanitized = re.sub(r"[^a-zA-Z0-9\s-]", "", task.lower())
    sanitized = re.sub(r"\s+", "-", sanitized)
    sanitized = sanitized[:50]

    return f"{prefix}{sanitized}"
