# #region Test.Git.Sync.Coverage [C:3] [TYPE Module] [SEMANTICS test, git, sync, coverage, push, pull, error]
# @BRIEF Additional edge- and error-path coverage for GitServiceSyncMixin — push/pull diagnostic and origin-url exception branches.
# @RELATION BINDS_TO -> [GitServiceSyncMixin]

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

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

import pytest
from fastapi import HTTPException
from git.exc import GitCommandError


@pytest.fixture
def mixin():
    from src.services.git._sync import GitServiceSyncMixin
    m = GitServiceSyncMixin()
    m._locked = lambda x: contextlib.nullcontext()
    m._align_origin_host_with_config = MagicMock(return_value=None)
    return m


class TestPushChangesCoverage:
    """Cover remaining push_changes branches."""

    @pytest.mark.asyncio
    async def test_origin_urls_exception(self, mixin):
        """Getting origin URLs raises → empty list used."""
        repo = MagicMock()
        repo.heads = [MagicMock()]
        branch = MagicMock()
        branch.name = "dev"
        branch.tracking_branch.return_value = MagicMock()
        repo.active_branch = branch
        origin = MagicMock()
        origin.urls = ["https://origin.com/repo.git"]
        type(origin).urls = PropertyMock(side_effect=Exception("url error"))
        repo.remote.return_value = origin
        origin.push.return_value = []
        with patch.object(mixin, 'get_repo', new_callable=AsyncMock, create=True, return_value=repo), \
             patch("src.services.git._sync.SessionLocal") as mock_sl:
            mock_db = MagicMock()
            mock_sl.return_value = mock_db
            mock_db.query.return_value.filter.return_value.first.return_value = None
            await mixin.push_changes(1)
        origin.push.assert_called_once()

    @pytest.mark.asyncio
    async def test_db_diag_error(self, mixin):
        """DB diagnostics fails during push → error logged, push proceeds."""
        repo = MagicMock()
        repo.heads = [MagicMock()]
        branch = MagicMock()
        branch.name = "dev"
        branch.tracking_branch.return_value = MagicMock()
        repo.active_branch = branch
        origin = MagicMock()
        origin.urls = ["https://origin.com/repo.git"]
        repo.remote.return_value = origin
        origin.push.return_value = []
        with patch.object(mixin, 'get_repo', new_callable=AsyncMock, create=True, return_value=repo), \
             patch("src.services.git._sync.SessionLocal", side_effect=Exception("DB down")):
            await mixin.push_changes(1)
        origin.push.assert_called_once()

    @pytest.mark.asyncio
    async def test_second_origin_urls_exception(self, mixin):
        """Second origin.urls read after realignment raises → empty list used."""
        repo = MagicMock()
        repo.heads = [MagicMock()]
        branch = MagicMock()
        branch.name = "dev"
        branch.tracking_branch.return_value = MagicMock()
        repo.active_branch = branch
        origin = MagicMock()
        repo.remote.return_value = origin
        origin.push.return_value = []
        # First read succeeds, second fails
        origin.urls = ["https://origin.com/repo.git"]
        with patch.object(mixin, 'get_repo', new_callable=AsyncMock, create=True, return_value=repo), \
             patch("src.services.git._sync.SessionLocal") as mock_sl, \
             patch.object(origin, 'urls', PropertyMock(side_effect=[["https://origin.com/repo.git"], Exception("urls error")])):
            mock_db = MagicMock()
            mock_sl.return_value = mock_db
            mock_db.query.return_value.filter.return_value.first.return_value = None
            await mixin.push_changes(1)
        origin.push.assert_called_once()

    @pytest.mark.asyncio
    async def test_tracking_branch_exception(self, mixin):
        """tracking_branch() raises Exception → treated as no tracking → set-upstream."""
        repo = MagicMock()
        repo.heads = [MagicMock()]
        branch = MagicMock()
        branch.name = "feature"
        branch.tracking_branch.side_effect = Exception("no tracking")
        repo.active_branch = branch
        origin = MagicMock()
        origin.urls = ["https://origin.com/repo.git"]
        repo.remote.return_value = origin
        with patch.object(mixin, 'get_repo', new_callable=AsyncMock, create=True, return_value=repo), \
             patch("src.services.git._sync.SessionLocal") as mock_sl:
            mock_db = MagicMock()
            mock_sl.return_value = mock_db
            mock_db.query.return_value.filter.return_value.first.return_value = None
            await mixin.push_changes(1)
        repo.git.push.assert_called_with("--set-upstream", "origin", "feature:feature")


class TestPullChangesCoverage:
    """Cover remaining pull_changes branches."""

    @pytest.mark.asyncio
    async def test_origin_urls_exception(self, mixin):
        """Getting origin.urls raises during pull → empty list."""
        repo = MagicMock()
        repo.git_dir = "/tmp/repo/.git"
        repo.active_branch.name = "dev"
        origin = MagicMock()
        type(origin).urls = PropertyMock(side_effect=Exception("urls error"))
        repo.remote.return_value = origin
        remote_ref = MagicMock()
        remote_ref.name = "origin/dev"
        repo.refs = [remote_ref]
        with patch.object(mixin, 'get_repo', new_callable=AsyncMock, create=True, return_value=repo), \
             patch("os.path.exists", return_value=False):
            await mixin.pull_changes(1)
        origin.fetch.assert_called_once_with(prune=True)

    @pytest.mark.asyncio
    async def test_git_command_error_non_conflict(self, mixin):
        """GitCommandError during pull that is NOT conflict-related → HTTP 500."""
        repo = MagicMock()
        repo.git_dir = "/tmp/repo/.git"
        repo.active_branch.name = "dev"
        origin = MagicMock()
        origin.urls = ["https://origin.com/repo.git"]
        repo.remote.return_value = origin
        remote_ref = MagicMock()
        remote_ref.name = "origin/dev"
        repo.refs = [remote_ref]
        repo.git.pull.side_effect = GitCommandError("pull", "fatal: Could not read from remote repository.")
        with patch.object(mixin, 'get_repo', new_callable=AsyncMock, create=True, return_value=repo), \
             patch("os.path.exists", return_value=False):
            with pytest.raises(HTTPException) as exc_info:
                await mixin.pull_changes(1)
            assert exc_info.value.status_code == 500
# #endregion Test.Git.Sync.Coverage
