"""Tests for ForgeProvider ABC and AuthenticationError."""

from __future__ import annotations

import pytest

from project_hub.forge import AuthenticationError, ForgeProvider


class _MinimalProvider(ForgeProvider):
    """Minimal concrete subclass implementing all abstract methods."""

    def get_username(self) -> str | None:
        return "testuser"

    def list_merge_requests(self, project_path: str, state: str = "opened") -> list:
        return []

    def list_issues(self, project_path: str, state: str = "opened") -> list:
        return []

    def get_mrs_by_iids(self, project_path: str, iids: list[int]) -> list:
        return []

    def get_issues_by_iids(self, project_path: str, iids: list[int]) -> list:
        return []

    def list_pipelines(
        self, project_path: str, ref: str | None = None, per_page: int = 20
    ) -> list:
        return []

    def get_pipeline_jobs(self, project_path: str, pipeline_id: int) -> list:
        return []

    def get_job_log(self, project_path: str, job_id: int) -> str:
        return ""

    def get_vulnerabilities(
        self, project_path: str, severity: str | None = None
    ) -> list:
        return []

    def get_pipeline_findings(
        self,
        project_path: str,
        pipeline_id: int | None,
        ref: str | None = None,
    ) -> set[str]:
        return set()

    def get_default_branch(self, project_path: str) -> str | None:
        return "main"

    def get_project_info(self, project_path: str) -> dict:
        return {}


class TestAuthenticationError:
    def test_is_raisable(self):
        with pytest.raises(AuthenticationError):
            raise AuthenticationError("token expired")

    def test_is_exception(self):
        assert issubclass(AuthenticationError, Exception)


class TestForgeProviderABC:
    def test_cannot_instantiate_directly(self):
        with pytest.raises(TypeError):
            ForgeProvider("gitlab.com", "token123")  # type: ignore[abstract]

    def test_concrete_subclass_can_be_instantiated(self):
        provider = _MinimalProvider("gitlab.com", "token123")
        assert provider.instance == "gitlab.com"

    def test_token_stored_as_private(self):
        provider = _MinimalProvider("gitlab.example.com", "secret")
        assert provider._token == "secret"

    def test_read_methods_return_values(self):
        provider = _MinimalProvider("gitlab.com", "tok")
        assert provider.get_username() == "testuser"
        assert provider.list_merge_requests("group/repo") == []
        assert provider.list_issues("group/repo") == []
        assert provider.get_mrs_by_iids("group/repo", [1, 2]) == []
        assert provider.get_issues_by_iids("group/repo", [1]) == []
        assert provider.list_pipelines("group/repo") == []
        assert provider.get_pipeline_jobs("group/repo", 42) == []
        assert provider.get_job_log("group/repo", 7) == ""
        assert provider.get_vulnerabilities("group/repo") == []
        assert provider.get_pipeline_findings("group/repo", None) == set()
        assert provider.get_default_branch("group/repo") == "main"
        assert provider.get_project_info("group/repo") == {}


class TestForgeProviderWriteMethods:
    """All write methods must raise NotImplementedError."""

    @pytest.fixture
    def provider(self):
        return _MinimalProvider("gitlab.com", "tok")

    def test_approve_mr(self, provider):
        with pytest.raises(NotImplementedError):
            provider.approve_mr("g/r", 1)

    def test_merge_mr(self, provider):
        with pytest.raises(NotImplementedError):
            provider.merge_mr("g/r", 1)

    def test_comment_mr(self, provider):
        with pytest.raises(NotImplementedError):
            provider.comment_mr("g/r", 1, "looks good")

    def test_create_mr(self, provider):
        with pytest.raises(NotImplementedError):
            provider.create_mr("g/r", "feature", "main", "My MR")

    def test_list_mr_discussions(self, provider):
        with pytest.raises(NotImplementedError):
            provider.list_mr_discussions("g/r", 1)

    def test_reply_to_discussion(self, provider):
        with pytest.raises(NotImplementedError):
            provider.reply_to_discussion("g/r", 1, "disc-id", "reply")

    def test_create_mr_review(self, provider):
        with pytest.raises(NotImplementedError):
            provider.create_mr_review("g/r", 1, [])

    def test_comment_issue(self, provider):
        with pytest.raises(NotImplementedError):
            provider.comment_issue("g/r", 1, "comment")

    def test_close_issue(self, provider):
        with pytest.raises(NotImplementedError):
            provider.close_issue("g/r", 1)

    def test_reopen_issue(self, provider):
        with pytest.raises(NotImplementedError):
            provider.reopen_issue("g/r", 1)

    def test_create_issue(self, provider):
        with pytest.raises(NotImplementedError):
            provider.create_issue("g/r", "Bug: something broke")

    def test_retry_pipeline(self, provider):
        with pytest.raises(NotImplementedError):
            provider.retry_pipeline("g/r", 99)

    def test_cancel_pipeline(self, provider):
        with pytest.raises(NotImplementedError):
            provider.cancel_pipeline("g/r", 99)

    def test_error_message_names_class(self, provider):
        """NotImplementedError message should identify the provider class."""
        try:
            provider.approve_mr("g/r", 1)
        except NotImplementedError as exc:
            assert "_MinimalProvider" in str(exc)
