"""Abstract base class for forge providers (GitLab, GitHub, etc.)."""

from __future__ import annotations

from abc import ABC, abstractmethod
from typing import ClassVar


class AuthenticationError(Exception):
    """Raised when a forge API returns 401 Unauthorized."""


# pylint: disable=too-many-public-methods
class ForgeProvider(ABC):
    """Common interface for forge integrations."""

    supported_features: ClassVar[frozenset[str]] = frozenset({
        "mrs", "issues", "pipelines", "vulnerabilities",
    })

    def __init__(self, instance: str, token: str) -> None:
        self.instance = instance
        self._token = token

    # ------------------------------------------------------------------
    # Abstract read methods — subclasses must implement these
    # ------------------------------------------------------------------

    @abstractmethod
    def get_username(self) -> str | None:
        """Return the authenticated username, or None if unavailable."""

    @abstractmethod
    def list_merge_requests(self, project_path: str, state: str = "opened") -> list:
        """Return merge requests for *project_path* filtered by *state*."""

    @abstractmethod
    def list_issues(self, project_path: str, state: str = "opened") -> list:
        """Return issues for *project_path* filtered by *state*."""

    @abstractmethod
    def get_mrs_by_iids(self, project_path: str, iids: list[int]) -> list:
        """Return merge requests matching the given internal IDs."""

    @abstractmethod
    def get_issues_by_iids(self, project_path: str, iids: list[int]) -> list:
        """Return issues matching the given internal IDs."""

    @abstractmethod
    def list_pipelines(
        self, project_path: str, ref: str | None = None, per_page: int = 20
    ) -> list:
        """Return pipelines for *project_path*, optionally filtered by *ref*."""

    @abstractmethod
    def get_pipeline_jobs(self, project_path: str, pipeline_id: int | str) -> list:
        """Return jobs belonging to *pipeline_id*."""

    @abstractmethod
    def get_job_log(self, project_path: str, job_id: int | str) -> str:
        """Return the raw log for *job_id*."""

    @abstractmethod
    def get_vulnerabilities(
        self, project_path: str, severity: str | None = None
    ) -> list:
        """Return vulnerability findings for *project_path*."""

    @abstractmethod
    def get_pipeline_findings(
        self,
        project_path: str,
        pipeline_id: int | str | None,
        ref: str | None = None,
    ) -> tuple[set[str], list]:
        """Return (identifier_names, findings) from a pipeline's security scan."""

    @abstractmethod
    def get_default_branch(self, project_path: str) -> str | None:
        """Return the default branch name for *project_path*."""

    @abstractmethod
    def get_project_info(self, project_path: str) -> dict:
        """Return raw project metadata for *project_path*."""

    # ------------------------------------------------------------------
    # Concrete write methods — not implemented in V1; raise explicitly
    # ------------------------------------------------------------------

    def approve_mr(self, project_path: str, iid: int) -> None:
        raise NotImplementedError(f"{type(self).__name__} does not support approve_mr")

    def merge_mr(
        self, project_path: str, iid: int, merge_options: dict | None = None
    ) -> None:
        raise NotImplementedError(f"{type(self).__name__} does not support merge_mr")

    def comment_mr(self, project_path: str, iid: int, body: str) -> None:
        raise NotImplementedError(f"{type(self).__name__} does not support comment_mr")

    def create_mr(
        self,
        project_path: str,
        source_branch: str,
        target_branch: str,
        title: str,
        description: str = "",
    ) -> None:
        raise NotImplementedError(f"{type(self).__name__} does not support create_mr")

    def list_mr_discussions(self, project_path: str, iid: int) -> list:
        raise NotImplementedError(
            f"{type(self).__name__} does not support list_mr_discussions"
        )

    def reply_to_discussion(
        self, project_path: str, iid: int, discussion_id: str, body: str
    ) -> None:
        raise NotImplementedError(
            f"{type(self).__name__} does not support reply_to_discussion"
        )

    def create_mr_review(
        self, project_path: str, iid: int, comments: list, body: str = ""
    ) -> None:
        raise NotImplementedError(
            f"{type(self).__name__} does not support create_mr_review"
        )

    def comment_issue(self, project_path: str, iid: int, body: str) -> None:
        raise NotImplementedError(
            f"{type(self).__name__} does not support comment_issue"
        )

    def close_issue(self, project_path: str, iid: int) -> None:
        raise NotImplementedError(f"{type(self).__name__} does not support close_issue")

    def reopen_issue(self, project_path: str, iid: int) -> None:
        raise NotImplementedError(
            f"{type(self).__name__} does not support reopen_issue"
        )

    def create_issue(
        self,
        project_path: str,
        title: str,
        description: str = "",
        labels: list[str] | None = None,
        assignees: list[str] | None = None,
    ) -> None:
        raise NotImplementedError(
            f"{type(self).__name__} does not support create_issue"
        )

    def retry_pipeline(self, project_path: str, pipeline_id: int) -> None:
        raise NotImplementedError(
            f"{type(self).__name__} does not support retry_pipeline"
        )

    def cancel_pipeline(self, project_path: str, pipeline_id: int) -> None:
        raise NotImplementedError(
            f"{type(self).__name__} does not support cancel_pipeline"
        )
