"""Tests for git branch merging."""

from __future__ import annotations

import asyncio
from pathlib import Path
from unittest.mock import AsyncMock, patch

import pytest

from cleave.orchestrator.merge import merge_all_branches, merge_branch, MergeResult


def _mock_subprocess(returncode: int = 0, stdout: str = "", stderr: str = ""):
    proc = AsyncMock()
    proc.communicate = AsyncMock(return_value=(stdout.encode(), stderr.encode()))
    proc.returncode = returncode
    return proc


class TestMergeBranch:
    def test_successful_merge(self, tmp_path: Path) -> None:
        call_args: list = []

        async def mock_exec(*args, **kwargs):
            call_args.append(args)
            return _mock_subprocess()

        # Create .git dir so _ensure_repo_clean can check for MERGE_HEAD
        (tmp_path / ".git").mkdir(exist_ok=True)

        with patch("cleave.orchestrator.merge.asyncio.create_subprocess_exec", side_effect=mock_exec):
            result = asyncio.run(merge_branch(tmp_path, "cleave/auth", "main"))

        assert result.success is True
        assert result.branch == "cleave/auth"
        assert result.conflict_files == []
        # Should have: _ensure_repo_clean(rev-parse, checkout) + merge
        assert len(call_args) == 3

    def test_merge_conflict(self, tmp_path: Path) -> None:
        call_count = 0

        async def mock_exec(*args, **kwargs):
            nonlocal call_count
            call_count += 1
            if "merge" in args and "--no-ff" in args:
                return _mock_subprocess(returncode=1, stderr="CONFLICT")
            if "diff" in args:
                return _mock_subprocess(stdout="src/auth.py\nsrc/login.py")
            return _mock_subprocess()

        with patch("cleave.orchestrator.merge.asyncio.create_subprocess_exec", side_effect=mock_exec):
            result = asyncio.run(merge_branch(tmp_path, "cleave/auth", "main"))

        assert result.success is False
        assert "src/auth.py" in result.conflict_files
        assert "src/login.py" in result.conflict_files


class TestMergeAllBranches:
    def test_all_succeed(self, tmp_path: Path) -> None:
        with patch("cleave.orchestrator.merge.asyncio.create_subprocess_exec") as mock_exec:
            mock_exec.return_value = _mock_subprocess()
            report = asyncio.run(
                merge_all_branches(tmp_path, ["cleave/a", "cleave/b"], "main")
            )

        assert report.all_succeeded is True
        assert len(report.results) == 2

    def test_continues_past_conflict(self, tmp_path: Path) -> None:
        """Merge continues past conflicts so independent branches still merge."""
        merge_count = 0

        # Create .git dir for _ensure_repo_clean
        (tmp_path / ".git").mkdir(exist_ok=True)

        async def mock_exec(*args, **kwargs):
            nonlocal merge_count
            if "merge" in args and "--no-ff" in args:
                merge_count += 1
                if merge_count == 1:
                    return _mock_subprocess(returncode=1, stderr="CONFLICT")
            if "diff" in args:
                return _mock_subprocess(stdout="conflicted.py")
            return _mock_subprocess()

        with patch("cleave.orchestrator.merge.asyncio.create_subprocess_exec", side_effect=mock_exec):
            report = asyncio.run(
                merge_all_branches(tmp_path, ["cleave/a", "cleave/b", "cleave/c"], "main")
            )

        assert report.all_succeeded is False
        assert len(report.results) == 3  # continues past conflict
        assert report.results[0].success is False  # first had conflict
        assert report.results[1].success is True
        assert report.results[2].success is True
