"""Integration test — full orchestrator lifecycle with real git repo + mocked claude."""

from __future__ import annotations

import asyncio
import json
import subprocess
import sys
from pathlib import Path
from unittest.mock import AsyncMock, patch

import pytest

from cleave.orchestrator.config import OrchestratorConfig
from cleave.orchestrator.runner import run
from cleave.orchestrator.state import load_state


def _init_git_repo(path: Path) -> None:
    """Create a minimal git repo with an initial commit."""
    subprocess.run(["git", "init", str(path)], check=True, capture_output=True)
    subprocess.run(
        ["git", "-C", str(path), "config", "user.email", "test@test.com"],
        check=True, capture_output=True,
    )
    subprocess.run(
        ["git", "-C", str(path), "config", "user.name", "Test"],
        check=True, capture_output=True,
    )
    readme = path / "README.md"
    readme.write_text("# Test repo\n")
    subprocess.run(["git", "-C", str(path), "add", "."], check=True, capture_output=True)
    subprocess.run(
        ["git", "-C", str(path), "commit", "-m", "Initial commit"],
        check=True, capture_output=True,
    )


def _mock_claude_process(stdout_text: str = "", returncode: int = 0):
    """Create a mock async subprocess that mimics claude --print."""
    proc = AsyncMock()
    proc.stdin = AsyncMock()
    proc.stdin.write = lambda x: None
    proc.stdin.drain = AsyncMock()
    proc.stdin.close = lambda: None
    proc.pid = 99999
    proc.communicate = AsyncMock(
        return_value=(stdout_text.encode("utf-8"), b"")
    )
    proc.returncode = returncode
    proc.terminate = AsyncMock()
    proc.kill = AsyncMock()
    return proc


@pytest.mark.skipif(sys.platform == "win32", reason="Orchestrator uses POSIX signal handlers")
class TestFullLifecycleDryRun:
    """Dry-run lifecycle: preflight → plan → stop."""

    def test_dry_run_with_real_git(self, tmp_path: Path) -> None:
        _init_git_repo(tmp_path)

        config = OrchestratorConfig(
            directive="Add a README section",
            repo_path=tmp_path,
            success_criteria=["README.md updated"],
            dry_run=True,
        )

        plan_data = {
            "children": [
                {"label": "update-readme", "description": "Add overview section to README"},
                {"label": "add-toc", "description": "Add table of contents"},
            ],
            "rationale": "Split README update into content and structure",
        }

        async def mock_git_exec(*args, **kwargs):
            """Mock git commands for preflight."""
            subcmd = args[1] if len(args) > 1 else ""
            if subcmd == "rev-parse":
                return _mock_claude_process(stdout_text="main")
            if subcmd == "status":
                return _mock_claude_process(stdout_text="")
            return _mock_claude_process()

        async def mock_plan_split(claude_cli, config):
            return plan_data

        with patch("shutil.which", return_value="/usr/bin/claude"):
            with patch(
                "cleave.orchestrator.runner.plan_split",
                side_effect=mock_plan_split,
            ):
                with patch(
                    "cleave.orchestrator.worktree.asyncio.create_subprocess_exec",
                    side_effect=mock_git_exec,
                ):
                    result = asyncio.run(run(config))

        assert result.success is True
        assert result.state.phase == "complete"
        assert result.state.plan is not None
        assert result.state.plan["children"][0]["label"] == "update-readme"
        assert result.workspace_path is not None
        assert result.report_path is not None
        assert result.report_path.exists()

        # Verify state was persisted
        loaded = load_state(result.workspace_path)
        assert loaded.run_id == result.state.run_id
        assert loaded.phase == "complete"


@pytest.mark.skipif(sys.platform == "win32", reason="Orchestrator uses POSIX signal handlers")
class TestFullLifecycleWithExecution:
    """Full lifecycle with mocked claude subprocess execution."""

    def test_two_child_success(self, tmp_path: Path) -> None:
        _init_git_repo(tmp_path)

        config = OrchestratorConfig(
            directive="Add greeting and farewell functions",
            repo_path=tmp_path,
            success_criteria=["python -c 'from greet import greet' works"],
            max_parallel_children=2,
            child_timeout_seconds=30,
        )

        plan_json = json.dumps({
            "children": [
                {"label": "add-greet", "description": "Add greet.py with greeting function"},
                {"label": "add-farewell", "description": "Add farewell.py with farewell function"},
            ],
            "rationale": "Two independent modules",
        })

        child_output = json.dumps({"cost_usd": 0.05, "result": "Done"})

        call_count = {"planner": 0, "child": 0, "git": 0}

        async def mock_exec(*args, **kwargs):
            cmd = args[0] if args else ""
            if cmd == "git":
                call_count["git"] += 1
                subcmd = args[1] if len(args) > 1 else ""
                if subcmd == "rev-parse":
                    return _mock_claude_process(stdout_text="main")
                if subcmd == "status":
                    return _mock_claude_process(stdout_text="")
                return _mock_claude_process()
            else:
                # Claude CLI
                if call_count["planner"] == 0:
                    call_count["planner"] += 1
                    return _mock_claude_process(stdout_text=plan_json)
                else:
                    call_count["child"] += 1
                    return _mock_claude_process(stdout_text=child_output)

        with patch("shutil.which", return_value="/usr/bin/claude"):
            with patch(
                "cleave.orchestrator.planner.asyncio.create_subprocess_exec",
                side_effect=mock_exec,
            ):
                with patch(
                    "cleave.orchestrator.worktree.asyncio.create_subprocess_exec",
                    side_effect=mock_exec,
                ):
                    with patch(
                        "cleave.orchestrator.dispatcher.asyncio.create_subprocess_exec",
                        side_effect=mock_exec,
                    ):
                        with patch(
                            "cleave.orchestrator.merge.asyncio.create_subprocess_exec",
                            side_effect=mock_exec,
                        ):
                            result = asyncio.run(run(config))

        assert result.state.plan is not None
        assert len(result.state.children) == 2
        assert result.state.children[0].label == "add-greet"
        assert result.state.children[1].label == "add-farewell"
        assert all(c.status == "completed" for c in result.state.children)
        assert result.state.total_cost_usd > 0
        assert result.report_path is not None
