# #region Test.Git.Branch.Coverage [C:3] [TYPE Module] [SEMANTICS test, git, branch, coverage, error, edge]
# @BRIEF Additional edge coverage for GitServiceBranchMixin — origin fetch failure, push failure, ref exception, create_head failure, stderr parsing.
# @RELATION BINDS_TO -> [GitServiceBranchMixin]

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

from src.services.git._branch import GitServiceBranchMixin


class TestableBranch(GitServiceBranchMixin):
    """Minimal test wrapper matching test_git_branch.py pattern."""
    def __init__(self, mock_repo=None):
        self._mock_repo = mock_repo or MagicMock()

    async def get_repo(self, dashboard_id):
        return self._mock_repo

    def _locked(self, dashboard_id):
        @contextlib.contextmanager
        def _lock():
            yield
        return _lock()


class TestEnsureGitflowBranchesCoverage:
    """Cover origin.fetch failure and push failure paths."""

    def test_fetch_failure_logged(self):
        """origin.fetch() raises → fetch error logged, push still attempted."""
        repo = MagicMock()
        main_head = MagicMock()
        main_head.name = "main"
        main_head.commit = MagicMock()
        repo.heads = [main_head]
        repo.head.commit = MagicMock()
        origin = MagicMock()
        origin.fetch.side_effect = Exception("fetch failed")
        repo.remote.return_value = origin
        svc = TestableBranch()
        svc._ensure_gitflow_branches(repo, 1)
        # Should have pushed branches (dev, preprod)
        assert origin.push.call_count >= 2

    def test_push_failure_raises_500(self):
        """Branch push to origin fails → HTTPException 500."""
        repo = MagicMock()
        main_head = MagicMock()
        main_head.name = "main"
        main_head.commit = MagicMock()
        repo.heads = [main_head]
        repo.head.commit = MagicMock()
        origin = MagicMock()
        origin.push.side_effect = Exception("push denied")
        repo.remote.return_value = origin
        svc = TestableBranch()
        with pytest.raises(HTTPException, match="Failed to create default branch"):
            svc._ensure_gitflow_branches(repo, 1)


class TestListBranchesCoverage:
    """Cover ref processing exception and active-branch-not-listed paths."""

    @pytest.mark.asyncio
    async def test_ref_processing_exception(self):
        """Ref processing raises → skipped with log."""
        repo = MagicMock()
        bad_ref = MagicMock()
        bad_ref.name = "refs/heads/dev"
        type(bad_ref).commit = PropertyMock(side_effect=Exception("no commit"))
        repo.refs = [bad_ref]
        type(repo.active_branch).name = PropertyMock(return_value="dev")
        svc = TestableBranch(repo)
        result = await svc.list_branches(1)
        # 'dev' should appear from active branch fallback
        assert any(b["name"] == "dev" for b in result)

    @pytest.mark.asyncio
    async def test_active_branch_not_in_list(self):
        """Active branch not in refs list → explicitly added."""
        repo = MagicMock()
        repo.refs = []
        type(repo.active_branch).name = PropertyMock(return_value="main")
        svc = TestableBranch(repo)
        result = await svc.list_branches(1)
        assert any(b["name"] == "main" for b in result)


class TestCreateBranchCoverage:
    """Cover create_head failure path."""

    @pytest.mark.asyncio
    async def test_create_head_failure(self):
        """create_head raises → exception propagates."""
        repo = MagicMock()
        repo.heads = [MagicMock()]
        repo.remotes = [MagicMock()]
        repo.commit.return_value = MagicMock()
        repo.create_head.side_effect = Exception("name conflict")
        svc = TestableBranch(repo)
        with pytest.raises(Exception, match="name conflict"):
            await svc.create_branch(1, "feature", "main")


class TestCheckoutBranchCoverage:
    """Cover stderr file-parsing path."""

    @pytest.mark.asyncio
    async def test_checkout_stderr_parses_files(self):
        """stderr with file paths → GIT_CHECKOUT_LOCAL_CHANGES raised."""
        repo = MagicMock()
        error = GitCommandError("checkout", "error")
        error.stderr = (
            "error: Your local changes to the following files would be overwritten by checkout:\n"
            "\tconfig.yaml\n"
            "\tsrc/app.py\n"
        )
        repo.git.checkout.side_effect = error
        svc = TestableBranch(repo)
        with pytest.raises(HTTPException) as exc_info:
            await svc.checkout_branch(1, "other")
        detail = exc_info.value.detail
        assert detail["error_code"] == "GIT_CHECKOUT_LOCAL_CHANGES"
# #endregion Test.Git.Branch.Coverage
