"""
Base provider interface for git hosting platforms.

Defines the abstract interface that all git providers must implement.
"""

from abc import ABC, abstractmethod
from typing import Any

from models import (
    CreatePRResult,
    GetPRResult,
    ListPRsResult,
    MergePRResult,
    MergeStrategy,
    PRState,
    UpdatePRResult,
)


class BaseProvider(ABC):
    """
    Abstract base class for git hosting providers.

    All providers (Gitea, GitHub, GitLab) must implement this interface.
    """

    @property
    @abstractmethod
    def name(self) -> str:
        """Return the provider name (e.g., 'gitea', 'github')."""
        ...

    @abstractmethod
    async def is_connected(self) -> bool:
        """Check if the provider is connected and authenticated."""
        ...

    @abstractmethod
    async def get_authenticated_user(self) -> str | None:
        """Get the username of the authenticated user."""
        ...

    # Repository operations

    @abstractmethod
    async def get_repo_info(self, owner: str, repo: str) -> dict[str, Any]:
        """
        Get repository information.

        Args:
            owner: Repository owner/organization
            repo: Repository name

        Returns:
            Repository info dict
        """
        ...

    @abstractmethod
    async def get_default_branch(self, owner: str, repo: str) -> str:
        """
        Get the default branch for a repository.

        Args:
            owner: Repository owner/organization
            repo: Repository name

        Returns:
            Default branch name
        """
        ...

    # Pull Request operations

    @abstractmethod
    async def create_pr(
        self,
        owner: str,
        repo: str,
        title: str,
        body: str,
        source_branch: str,
        target_branch: str,
        draft: bool = False,
        labels: list[str] | None = None,
        assignees: list[str] | None = None,
        reviewers: list[str] | None = None,
    ) -> CreatePRResult:
        """
        Create a pull request.

        Args:
            owner: Repository owner
            repo: Repository name
            title: PR title
            body: PR description
            source_branch: Source branch name
            target_branch: Target branch name
            draft: Whether to create as draft
            labels: Labels to add
            assignees: Users to assign
            reviewers: Users to request review from

        Returns:
            CreatePRResult with PR number and URL
        """
        ...

    @abstractmethod
    async def get_pr(self, owner: str, repo: str, pr_number: int) -> GetPRResult:
        """
        Get a pull request by number.

        Args:
            owner: Repository owner
            repo: Repository name
            pr_number: Pull request number

        Returns:
            GetPRResult with PR details
        """
        ...

    @abstractmethod
    async def list_prs(
        self,
        owner: str,
        repo: str,
        state: PRState | None = None,
        author: str | None = None,
        limit: int = 20,
    ) -> ListPRsResult:
        """
        List pull requests.

        Args:
            owner: Repository owner
            repo: Repository name
            state: Filter by state (open, closed, merged)
            author: Filter by author
            limit: Maximum PRs to return

        Returns:
            ListPRsResult with list of PRs
        """
        ...

    @abstractmethod
    async def merge_pr(
        self,
        owner: str,
        repo: str,
        pr_number: int,
        merge_strategy: MergeStrategy = MergeStrategy.MERGE,
        commit_message: str | None = None,
        delete_branch: bool = True,
    ) -> MergePRResult:
        """
        Merge a pull request.

        Args:
            owner: Repository owner
            repo: Repository name
            pr_number: Pull request number
            merge_strategy: Merge strategy to use
            commit_message: Custom merge commit message
            delete_branch: Whether to delete source branch

        Returns:
            MergePRResult with merge status
        """
        ...

    @abstractmethod
    async def update_pr(
        self,
        owner: str,
        repo: str,
        pr_number: int,
        title: str | None = None,
        body: str | None = None,
        state: PRState | None = None,
        labels: list[str] | None = None,
        assignees: list[str] | None = None,
    ) -> UpdatePRResult:
        """
        Update a pull request.

        Args:
            owner: Repository owner
            repo: Repository name
            pr_number: Pull request number
            title: New title
            body: New description
            state: New state (open, closed)
            labels: Replace labels
            assignees: Replace assignees

        Returns:
            UpdatePRResult with updated PR info
        """
        ...

    @abstractmethod
    async def close_pr(self, owner: str, repo: str, pr_number: int) -> UpdatePRResult:
        """
        Close a pull request without merging.

        Args:
            owner: Repository owner
            repo: Repository name
            pr_number: Pull request number

        Returns:
            UpdatePRResult with updated PR info
        """
        ...

    # Branch operations via API (for operations that need to bypass local git)

    @abstractmethod
    async def delete_remote_branch(self, owner: str, repo: str, branch: str) -> bool:
        """
        Delete a remote branch via API.

        Args:
            owner: Repository owner
            repo: Repository name
            branch: Branch name to delete

        Returns:
            True if deleted, False otherwise
        """
        ...

    @abstractmethod
    async def get_branch(
        self, owner: str, repo: str, branch: str
    ) -> dict[str, Any] | None:
        """
        Get branch information via API.

        Args:
            owner: Repository owner
            repo: Repository name
            branch: Branch name

        Returns:
            Branch info dict or None if not found
        """
        ...

    # Comment operations

    @abstractmethod
    async def add_pr_comment(
        self, owner: str, repo: str, pr_number: int, body: str
    ) -> dict[str, Any]:
        """
        Add a comment to a pull request.

        Args:
            owner: Repository owner
            repo: Repository name
            pr_number: Pull request number
            body: Comment body

        Returns:
            Created comment info
        """
        ...

    @abstractmethod
    async def list_pr_comments(
        self, owner: str, repo: str, pr_number: int
    ) -> list[dict[str, Any]]:
        """
        List comments on a pull request.

        Args:
            owner: Repository owner
            repo: Repository name
            pr_number: Pull request number

        Returns:
            List of comments
        """
        ...

    # Label operations

    @abstractmethod
    async def add_labels(
        self, owner: str, repo: str, pr_number: int, labels: list[str]
    ) -> list[str]:
        """
        Add labels to a pull request.

        Args:
            owner: Repository owner
            repo: Repository name
            pr_number: Pull request number
            labels: Labels to add

        Returns:
            Updated list of labels
        """
        ...

    @abstractmethod
    async def remove_label(
        self, owner: str, repo: str, pr_number: int, label: str
    ) -> list[str]:
        """
        Remove a label from a pull request.

        Args:
            owner: Repository owner
            repo: Repository name
            pr_number: Pull request number
            label: Label to remove

        Returns:
            Updated list of labels
        """
        ...

    # Reviewer operations

    @abstractmethod
    async def request_review(
        self, owner: str, repo: str, pr_number: int, reviewers: list[str]
    ) -> list[str]:
        """
        Request review from users.

        Args:
            owner: Repository owner
            repo: Repository name
            pr_number: Pull request number
            reviewers: Usernames to request review from

        Returns:
            List of reviewers requested
        """
        ...

    # Utility methods

    def parse_repo_url(self, repo_url: str) -> tuple[str, str]:
        """
        Parse repository URL to extract owner and repo name.

        Args:
            repo_url: Repository URL (HTTPS or SSH)

        Returns:
            Tuple of (owner, repo)

        Raises:
            ValueError: If URL cannot be parsed
        """
        import re

        # Handle SSH URLs: git@host:owner/repo.git
        ssh_match = re.match(r"git@[^:]+:([^/]+)/([^/]+?)(?:\.git)?$", repo_url)
        if ssh_match:
            return ssh_match.group(1), ssh_match.group(2)

        # Handle HTTPS URLs: https://host/owner/repo.git
        https_match = re.match(r"https?://[^/]+/([^/]+)/([^/]+?)(?:\.git)?$", repo_url)
        if https_match:
            return https_match.group(1), https_match.group(2)

        raise ValueError(f"Unable to parse repository URL: {repo_url}")
