# #region Test.Git.RemoteProviders [C:3] [TYPE Module] [SEMANTICS test,git,github,gitlab,remote,provider]
# @BRIEF Tests for GitServiceGithubMixin and GitServiceGitlabMixin — repository creation and merge request creation via HTTP.
# @RELATION BINDS_TO -> [GitServiceRemoteMixin]
# @TEST_EDGE: create_github_repo_success -> repo created
# @TEST_EDGE: create_github_repo_enterprise -> uses enterprise API URL
# @TEST_EDGE: create_github_repo_api_error -> HTTPException
# @TEST_EDGE: create_github_repo_network_error -> HTTPException 503
# @TEST_EDGE: create_gitlab_repo_success -> repo created with normalized URLs
# @TEST_EDGE: create_gitlab_repo_api_error -> HTTPException
# @TEST_EDGE: create_gitlab_merge_request_success -> MR created
# @TEST_EDGE: create_gitlab_merge_request_error -> HTTPException

import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent / "src"))

import pytest
from unittest.mock import AsyncMock, MagicMock, patch

from fastapi import HTTPException

from src.services.git._remote_providers import GitServiceGithubMixin, GitServiceGitlabMixin


class TestableGitGithub(GitServiceGithubMixin):
    """Concrete test class for GitHub mixin."""
    def __init__(self):
        self._http_client = AsyncMock()

    def _normalize_git_server_url(self, raw_url):
        return (raw_url or "").strip().rstrip("/")


class TestableGitGitlab(GitServiceGitlabMixin):
    """Concrete test class for GitLab mixin."""
    def __init__(self):
        self._http_client = AsyncMock()

    def _normalize_git_server_url(self, raw_url):
        return (raw_url or "").strip().rstrip("/")

    def _parse_remote_repo_identity(self, remote_url):
        from urllib.parse import urlparse
        normalized = str(remote_url or "").strip()
        path = urlparse(normalized).path.strip("/")
        if path.endswith(".git"):
            path = path[:-4]
        parts = [s for s in path.split("/") if s]
        owner, repo = parts[0], parts[-1]
        namespace = "/".join(parts[:-1])
        return {"owner": owner, "repo": repo, "namespace": namespace, "full_name": f"{namespace}/{repo}"}


# ── GitHub ──

class TestCreateGitHubRepository:
    """create_github_repository — GitHub repo creation."""

    # #region test_github_repo_success [C:2] [TYPE Function]
    @pytest.mark.asyncio
    async def test_github_repo_success(self):
        """GitHub repo created successfully."""
        svc = TestableGitGithub()
        resp = MagicMock()
        resp.status_code = 201
        resp.json.return_value = {"name": "new-repo", "clone_url": "https://github.com/user/new-repo.git"}
        svc._http_client.post.return_value = resp

        result = await svc.create_github_repository(
            "https://github.com", "pat", "new-repo",
            private=True, description="test repo",
        )
        assert result["name"] == "new-repo"
        svc._http_client.post.assert_called_once()
        call_url = svc._http_client.post.call_args[0][0]
        assert "api.github.com" in call_url
    # #endregion test_github_repo_success

    # #region test_github_repo_enterprise [C:2] [TYPE Function]
    @pytest.mark.asyncio
    async def test_github_repo_enterprise(self):
        """GitHub Enterprise URL → uses enterprise API path."""
        svc = TestableGitGithub()
        resp = MagicMock()
        resp.status_code = 201
        resp.json.return_value = {"name": "repo"}
        svc._http_client.post.return_value = resp

        await svc.create_github_repository(
            "https://github.internal.com", "pat", "repo",
        )
        call_url = svc._http_client.post.call_args[0][0]
        assert "/api/v3" in call_url
    # #endregion test_github_repo_enterprise

    # #region test_github_repo_api_error [C:2] [TYPE Function]
    @pytest.mark.asyncio
    async def test_github_repo_api_error(self):
        """API error → HTTPException with detail."""
        svc = TestableGitGithub()
        resp = MagicMock()
        resp.status_code = 422
        resp.text = "Validation Failed"
        resp.json.return_value = {"message": "Name already exists"}
        svc._http_client.post.return_value = resp

        with pytest.raises(HTTPException, match="already exists"):
            await svc.create_github_repository("https://github.com", "pat", "existing-repo")
    # #endregion test_github_repo_api_error

    # #region test_github_repo_api_error_no_json [C:2] [TYPE Function]
    @pytest.mark.asyncio
    async def test_github_repo_api_error_no_json(self):
        """API error without JSON → uses text."""
        svc = TestableGitGithub()
        resp = MagicMock()
        resp.status_code = 403
        resp.text = "rate limit exceeded"
        resp.json.side_effect = ValueError("no json")
        svc._http_client.post.return_value = resp

        with pytest.raises(HTTPException, match="rate limit"):
            await svc.create_github_repository("https://github.com", "pat", "repo")
    # #endregion test_github_repo_api_error_no_json

    # #region test_github_repo_network_error [C:2] [TYPE Function]
    @pytest.mark.asyncio
    async def test_github_repo_network_error(self):
        """Network error → HTTPException 503."""
        svc = TestableGitGithub()
        svc._http_client.post.side_effect = Exception("connection refused")

        with pytest.raises(HTTPException) as exc_info:
            await svc.create_github_repository("https://github.com", "pat", "repo")
        assert exc_info.value.status_code == 503
    # #endregion test_github_repo_network_error

    # #region test_github_repo_default_params [C:2] [TYPE Function]
    @pytest.mark.asyncio
    async def test_github_repo_default_params(self):
        """Default parameters passed correctly."""
        svc = TestableGitGithub()
        resp = MagicMock()
        resp.status_code = 201
        resp.json.return_value = {"name": "repo"}
        svc._http_client.post.return_value = resp

        await svc.create_github_repository("https://github.com", "pat", "my-repo")
        call_json = svc._http_client.post.call_args[1]["json"]
        assert call_json["name"] == "my-repo"
        assert call_json["private"] is True
        assert call_json["auto_init"] is True
        assert call_json["default_branch"] == "main"
    # #endregion test_github_repo_default_params


# ── GitLab ──

class TestCreateGitLabRepository:
    """create_gitlab_repository — GitLab project creation."""

    # #region test_gitlab_repo_success [C:2] [TYPE Function]
    @pytest.mark.asyncio
    async def test_gitlab_repo_success(self):
        """GitLab project created with normalized fields."""
        svc = TestableGitGitlab()
        resp = MagicMock()
        resp.status_code = 201
        resp.json.return_value = {
            "id": 42, "name": "new-project",
            "http_url_to_repo": "https://gitlab.com/user/new-project.git",
            "web_url": "https://gitlab.com/user/new-project",
            "ssh_url_to_repo": "git@gitlab.com:user/new-project.git",
            "path_with_namespace": "user/new-project",
        }
        svc._http_client.post.return_value = resp

        result = await svc.create_gitlab_repository(
            "https://gitlab.com", "pat", "new-project",
            private=True, description="test",
        )
        assert result["name"] == "new-project"
        assert result["clone_url"] is not None
        assert result["html_url"] is not None
        assert result["ssh_url"] is not None
        assert result["full_name"] is not None
    # #endregion test_gitlab_repo_success

    # #region test_gitlab_repo_api_error [C:2] [TYPE Function]
    @pytest.mark.asyncio
    async def test_gitlab_repo_api_error(self):
        """API error → HTTPException."""
        svc = TestableGitGitlab()
        resp = MagicMock()
        resp.status_code = 400
        resp.text = "Bad Request"
        resp.json.return_value = {"message": "Name has already been taken"}
        svc._http_client.post.return_value = resp

        with pytest.raises(HTTPException, match="already been taken"):
            await svc.create_gitlab_repository("https://gitlab.com", "pat", "existing")
    # #endregion test_gitlab_repo_api_error

    # #region test_gitlab_repo_network_error [C:2] [TYPE Function]
    @pytest.mark.asyncio
    async def test_gitlab_repo_network_error(self):
        """Network error → HTTPException 503."""
        svc = TestableGitGitlab()
        svc._http_client.post.side_effect = Exception("timeout")

        with pytest.raises(HTTPException) as exc_info:
            await svc.create_gitlab_repository("https://gitlab.com", "pat", "repo")
        assert exc_info.value.status_code == 503
    # #endregion test_gitlab_repo_network_error

    # #region test_gitlab_repo_normalized_fields_fallback [C:2] [TYPE Function]
    @pytest.mark.asyncio
    async def test_gitlab_repo_normalized_fields_fallback(self):
        """Missing clone_url/html_url → uses _to_repo fields."""
        svc = TestableGitGitlab()
        resp = MagicMock()
        resp.status_code = 201
        resp.json.return_value = {"name": "project"}  # No clone_url etc.
        svc._http_client.post.return_value = resp

        result = await svc.create_gitlab_repository("https://gitlab.com", "pat", "project")
        assert result["clone_url"] is None  # No fallback data
        assert result["full_name"] == "project"
    # #endregion test_gitlab_repo_normalized_fields_fallback


class TestCreateGitLabMergeRequest:
    """create_gitlab_merge_request — GitLab MR creation."""

    # #region test_gitlab_mr_success [C:2] [TYPE Function]
    @pytest.mark.asyncio
    async def test_gitlab_mr_success(self):
        """MR created successfully."""
        svc = TestableGitGitlab()
        resp = MagicMock()
        resp.status_code = 201
        resp.json.return_value = {
            "iid": 7, "web_url": "https://gitlab.com/org/repo/-/merge_requests/7",
            "state": "opened",
        }
        svc._http_client.post.return_value = resp

        result = await svc.create_gitlab_merge_request(
            "https://gitlab.com", "pat",
            "https://gitlab.com/org/repo.git",
            "feature", "main", "Add feature", description="desc",
        )
        assert result["id"] == 7
        assert result["status"] == "opened"
        assert "merge_requests" in result["url"]
    # #endregion test_gitlab_mr_success

    # #region test_gitlab_mr_api_error [C:2] [TYPE Function]
    @pytest.mark.asyncio
    async def test_gitlab_mr_api_error(self):
        """API error → HTTPException."""
        svc = TestableGitGitlab()
        resp = MagicMock()
        resp.status_code = 400
        resp.text = "Bad Request"
        resp.json.return_value = {"message": "Source branch not found"}
        svc._http_client.post.return_value = resp

        with pytest.raises(HTTPException, match="not found"):
            await svc.create_gitlab_merge_request(
                "https://gitlab.com", "pat",
                "https://gitlab.com/org/repo.git",
                "bad-branch", "main", "Title",
            )
    # #endregion test_gitlab_mr_api_error

    # #region test_gitlab_mr_network_error [C:2] [TYPE Function]
    @pytest.mark.asyncio
    async def test_gitlab_mr_network_error(self):
        """Network error → HTTPException 503."""
        svc = TestableGitGitlab()
        svc._http_client.post.side_effect = Exception("timeout")

        with pytest.raises(HTTPException) as exc_info:
            await svc.create_gitlab_merge_request(
                "https://gitlab.com", "pat",
                "https://gitlab.com/org/repo.git",
                "feature", "main", "Title",
            )
        assert exc_info.value.status_code == 503
    # #endregion test_gitlab_mr_network_error
