"""Tests for the `materialize` (inject) command and CLI wiring.

This module focuses on ensuring that `rhiza materialize` delegates to the
underlying inject logic and that basic paths and options are handled.
"""

import subprocess
from unittest.mock import Mock, patch

import pytest
import yaml
from typer.testing import CliRunner

from rhiza import cli
from rhiza.commands.materialize import materialize


class TestInjectCommand:
    """Tests for the inject/materialize command."""

    @patch("rhiza.commands.materialize.subprocess.run")
    @patch("rhiza.commands.materialize.shutil.rmtree")
    @patch("rhiza.commands.materialize.shutil.copy2")
    @patch("rhiza.commands.materialize.tempfile.mkdtemp")
    def test_inject_creates_default_template_yml(
        self, mock_mkdtemp, mock_copy2, mock_rmtree, mock_subprocess, tmp_path
    ):
        """Test that inject creates a default template.yml when it doesn't exist."""
        # Setup git repo
        git_dir = tmp_path / ".git"
        git_dir.mkdir()

        # Mock tempfile to return a controlled temp directory
        temp_dir = tmp_path / "temp"
        temp_dir.mkdir()
        mock_mkdtemp.return_value = str(temp_dir)

        # Mock subprocess to succeed
        mock_subprocess.return_value = Mock(returncode=0)

        # Run inject
        materialize(tmp_path, "main", None, False)

        # Verify template.yml was created
        template_file = tmp_path / ".github" / "template.yml"
        assert template_file.exists()

        # Verify it contains expected content

        with open(template_file) as f:
            config = yaml.safe_load(f)

        assert config["template-repository"] == "jebel-quant/rhiza"
        assert config["template-branch"] == "main"
        assert ".github" in config["include"]

    @patch("rhiza.commands.materialize.subprocess.run")
    @patch("rhiza.commands.materialize.shutil.rmtree")
    @patch("rhiza.commands.materialize.shutil.copy2")
    @patch("rhiza.commands.materialize.tempfile.mkdtemp")
    def test_inject_uses_existing_template_yml(self, mock_mkdtemp, mock_copy2, mock_rmtree, mock_subprocess, tmp_path):
        """Test that inject uses an existing template.yml."""
        # Setup git repo
        git_dir = tmp_path / ".git"
        git_dir.mkdir()

        # Create existing template.yml
        github_dir = tmp_path / ".github"
        github_dir.mkdir()
        template_file = github_dir / "template.yml"

        with open(template_file, "w") as f:
            yaml.dump(
                {"template-repository": "custom/repo", "template-branch": "custom-branch", "include": [".github"]}, f
            )

        # Mock tempfile
        temp_dir = tmp_path / "temp"
        temp_dir.mkdir()
        mock_mkdtemp.return_value = str(temp_dir)

        # Mock subprocess to succeed
        mock_subprocess.return_value = Mock(returncode=0)

        # Run inject
        materialize(tmp_path, "main", None, False)

        # Verify the git clone command used the custom repo
        clone_call = mock_subprocess.call_args_list[0]
        assert "custom/repo.git" in str(clone_call)

    @patch("rhiza.commands.materialize.subprocess.run")
    @patch("rhiza.commands.materialize.shutil.rmtree")
    @patch("rhiza.commands.materialize.shutil.copy2")
    @patch("rhiza.commands.materialize.tempfile.mkdtemp")
    def test_inject_fails_with_no_include_paths(self, mock_mkdtemp, mock_copy2, mock_rmtree, mock_subprocess, tmp_path):
        """Test that inject fails when template.yml has no include paths."""
        # Setup git repo
        git_dir = tmp_path / ".git"
        git_dir.mkdir()

        # Create template.yml with empty include
        github_dir = tmp_path / ".github"
        github_dir.mkdir()
        template_file = github_dir / "template.yml"

        with open(template_file, "w") as f:
            yaml.dump({"template-repository": "jebel-quant/rhiza", "template-branch": "main", "include": []}, f)

        # Run inject and expect it to fail
        with pytest.raises(SystemExit):
            materialize(tmp_path, "main", None, False)

    @patch("rhiza.commands.materialize.subprocess.run")
    @patch("rhiza.commands.materialize.shutil.rmtree")
    @patch("rhiza.commands.materialize.shutil.copy2")
    @patch("rhiza.commands.materialize.tempfile.mkdtemp")
    def test_inject_copies_files(self, mock_mkdtemp, mock_copy2, mock_rmtree, mock_subprocess, tmp_path):
        """Test that inject copies files from template to target."""
        # Setup git repo
        git_dir = tmp_path / ".git"
        git_dir.mkdir()

        # Create template.yml
        github_dir = tmp_path / ".github"
        github_dir.mkdir()
        template_file = github_dir / "template.yml"

        with open(template_file, "w") as f:
            yaml.dump(
                {"template-repository": "jebel-quant/rhiza", "template-branch": "main", "include": ["test.txt"]}, f
            )

        # Mock tempfile with actual directory containing a file
        temp_dir = tmp_path / "temp"
        temp_dir.mkdir()
        test_file = temp_dir / "test.txt"
        test_file.write_text("content")
        mock_mkdtemp.return_value = str(temp_dir)

        # Mock subprocess to succeed
        mock_subprocess.return_value = Mock(returncode=0)

        # Run inject
        materialize(tmp_path, "main", None, False)

        # Verify copy2 was called
        assert mock_copy2.called

    @patch("rhiza.commands.materialize.subprocess.run")
    @patch("rhiza.commands.materialize.shutil.rmtree")
    @patch("rhiza.commands.materialize.shutil.copy2")
    @patch("rhiza.commands.materialize.tempfile.mkdtemp")
    def test_inject_skips_existing_files_without_force(
        self, mock_mkdtemp, mock_copy2, mock_rmtree, mock_subprocess, tmp_path
    ):
        """Test that inject skips existing files when force=False."""
        # Setup git repo
        git_dir = tmp_path / ".git"
        git_dir.mkdir()

        # Create existing file in target
        existing_file = tmp_path / "test.txt"
        existing_file.write_text("existing")

        # Create template.yml
        github_dir = tmp_path / ".github"
        github_dir.mkdir()
        template_file = github_dir / "template.yml"

        with open(template_file, "w") as f:
            yaml.dump(
                {"template-repository": "jebel-quant/rhiza", "template-branch": "main", "include": ["test.txt"]}, f
            )

        # Mock tempfile with file to copy
        temp_dir = tmp_path / "temp"
        temp_dir.mkdir()
        src_file = temp_dir / "test.txt"
        src_file.write_text("new content")
        mock_mkdtemp.return_value = str(temp_dir)

        # Mock subprocess to succeed
        mock_subprocess.return_value = Mock(returncode=0)

        # Run inject without force
        materialize(tmp_path, "main", None, False)

        # Verify existing file was not overwritten
        assert existing_file.read_text() == "existing"
        # copy2 should not have been called for this file
        # (it might be called 0 times or for other files, depending on implementation)

    @patch("rhiza.commands.materialize.subprocess.run")
    @patch("rhiza.commands.materialize.shutil.rmtree")
    @patch("rhiza.commands.materialize.shutil.copy2")
    @patch("rhiza.commands.materialize.tempfile.mkdtemp")
    def test_inject_overwrites_with_force(self, mock_mkdtemp, mock_copy2, mock_rmtree, mock_subprocess, tmp_path):
        """Test that inject overwrites existing files when force=True."""
        # Setup git repo
        git_dir = tmp_path / ".git"
        git_dir.mkdir()

        # Create existing file in target
        existing_file = tmp_path / "test.txt"
        existing_file.write_text("existing")

        # Create template.yml
        github_dir = tmp_path / ".github"
        github_dir.mkdir()
        template_file = github_dir / "template.yml"

        with open(template_file, "w") as f:
            yaml.dump(
                {"template-repository": "jebel-quant/rhiza", "template-branch": "main", "include": ["test.txt"]}, f
            )

        # Mock tempfile with file to copy
        temp_dir = tmp_path / "temp"
        temp_dir.mkdir()
        src_file = temp_dir / "test.txt"
        src_file.write_text("new content")
        mock_mkdtemp.return_value = str(temp_dir)

        # Mock subprocess to succeed
        mock_subprocess.return_value = Mock(returncode=0)

        # Run inject with force
        materialize(tmp_path, "main", None, True)

        # Verify copy2 was called (force should allow overwrite)
        assert mock_copy2.called

    @patch("rhiza.commands.materialize.subprocess.run")
    @patch("rhiza.commands.materialize.shutil.rmtree")
    @patch("rhiza.commands.materialize.shutil.copy2")
    @patch("rhiza.commands.materialize.tempfile.mkdtemp")
    def test_inject_excludes_paths(self, mock_mkdtemp, mock_copy2, mock_rmtree, mock_subprocess, tmp_path):
        """Test that inject excludes specified paths."""
        # Setup git repo
        git_dir = tmp_path / ".git"
        git_dir.mkdir()

        # Create template.yml with exclude
        github_dir = tmp_path / ".github"
        github_dir.mkdir()
        template_file = github_dir / "template.yml"

        with open(template_file, "w") as f:
            yaml.dump(
                {
                    "template-repository": "jebel-quant/rhiza",
                    "template-branch": "main",
                    "include": ["dir"],
                    "exclude": ["dir/excluded.txt"],
                },
                f,
            )

        # Mock tempfile with files
        temp_dir = tmp_path / "temp"
        temp_dir.mkdir()
        dir_path = temp_dir / "dir"
        dir_path.mkdir()
        included_file = dir_path / "included.txt"
        excluded_file = dir_path / "excluded.txt"
        included_file.write_text("included")
        excluded_file.write_text("excluded")
        mock_mkdtemp.return_value = str(temp_dir)

        # Mock subprocess to succeed
        mock_subprocess.return_value = Mock(returncode=0)

        # Run inject
        materialize(tmp_path, "main", None, False)

        # Check that only included file was copied
        # This is implementation-specific, but we can check copy2 calls
        if mock_copy2.called:
            # Verify excluded.txt was not in the copy calls
            copy_calls = [str(call) for call in mock_copy2.call_args_list]
            assert any("included.txt" in str(call) for call in copy_calls)

    @patch("rhiza.commands.materialize.subprocess.run")
    @patch("rhiza.commands.materialize.shutil.rmtree")
    def test_inject_cleans_up_temp_dir(self, mock_rmtree, mock_subprocess, tmp_path):
        """Test that inject cleans up the temporary directory."""
        # Setup git repo
        git_dir = tmp_path / ".git"
        git_dir.mkdir()

        # Create minimal template.yml
        github_dir = tmp_path / ".github"
        github_dir.mkdir()
        template_file = github_dir / "template.yml"

        with open(template_file, "w") as f:
            yaml.dump(
                {"template-repository": "jebel-quant/rhiza", "template-branch": "main", "include": [".github"]}, f
            )

        # Mock subprocess to succeed
        mock_subprocess.return_value = Mock(returncode=0)

        # Run inject
        materialize(tmp_path, "main", None, False)

        # Verify rmtree was called to clean up
        assert mock_rmtree.called

    @patch("rhiza.commands.materialize.subprocess.run")
    @patch("rhiza.commands.materialize.shutil.rmtree")
    @patch("rhiza.commands.materialize.shutil.copy2")
    @patch("rhiza.commands.materialize.tempfile.mkdtemp")
    def test_cli_materialize_command(self, mock_mkdtemp, mock_copy2, mock_rmtree, mock_subprocess, tmp_path):
        """Test the CLI materialize command via Typer runner."""
        runner = CliRunner()

        # Setup git repo
        git_dir = tmp_path / ".git"
        git_dir.mkdir()

        # Create template.yml
        github_dir = tmp_path / ".github"
        github_dir.mkdir()
        template_file = github_dir / "template.yml"

        with open(template_file, "w") as f:
            yaml.dump(
                {"template-repository": "jebel-quant/rhiza", "template-branch": "main", "include": [".github"]}, f
            )

        # Mock tempfile
        temp_dir = tmp_path / "temp"
        temp_dir.mkdir()
        mock_mkdtemp.return_value = str(temp_dir)

        # Mock subprocess to succeed
        mock_subprocess.return_value = Mock(returncode=0)

        # Run CLI command
        result = runner.invoke(cli.app, ["materialize", str(tmp_path), "--branch", "main"])
        assert result.exit_code == 0

    @patch("rhiza.commands.materialize.subprocess.run")
    @patch("rhiza.commands.materialize.shutil.rmtree")
    @patch("rhiza.commands.materialize.shutil.copy2")
    @patch("rhiza.commands.materialize.tempfile.mkdtemp")
    def test_materialize_creates_rhiza_history_file(
        self, mock_mkdtemp, mock_copy2, mock_rmtree, mock_subprocess, tmp_path
    ):
        """Test that materialize creates a .rhiza.history file listing all template files."""
        # Setup git repo
        git_dir = tmp_path / ".git"
        git_dir.mkdir()

        # Create template.yml
        github_dir = tmp_path / ".github"
        github_dir.mkdir()
        template_file = github_dir / "template.yml"

        with open(template_file, "w") as f:
            yaml.dump(
                {
                    "template-repository": "jebel-quant/rhiza",
                    "template-branch": "main",
                    "include": ["file1.txt", "file2.txt"],
                },
                f,
            )

        # Mock tempfile with actual files
        temp_dir = tmp_path / "temp"
        temp_dir.mkdir()
        file1 = temp_dir / "file1.txt"
        file2 = temp_dir / "file2.txt"
        file1.write_text("content1")
        file2.write_text("content2")
        mock_mkdtemp.return_value = str(temp_dir)

        # Mock subprocess to succeed
        mock_subprocess.return_value = Mock(returncode=0)

        # Run materialize
        materialize(tmp_path, "main", None, False)

        # Verify .rhiza.history was created
        history_file = tmp_path / ".rhiza.history"
        assert history_file.exists()

        # Verify content
        history_content = history_file.read_text()
        assert "# Rhiza Template History" in history_content
        assert "# Template repository: jebel-quant/rhiza" in history_content
        assert "# Template branch: main" in history_content
        assert "file1.txt" in history_content
        assert "file2.txt" in history_content

    @patch("rhiza.commands.materialize.subprocess.run")
    @patch("rhiza.commands.materialize.shutil.rmtree")
    @patch("rhiza.commands.materialize.shutil.copy2")
    @patch("rhiza.commands.materialize.tempfile.mkdtemp")
    def test_materialize_history_includes_skipped_files(
        self, mock_mkdtemp, mock_copy2, mock_rmtree, mock_subprocess, tmp_path
    ):
        """Test that .rhiza.history includes files that already exist (were skipped)."""
        # Setup git repo
        git_dir = tmp_path / ".git"
        git_dir.mkdir()

        # Create existing file that will be skipped
        existing_file = tmp_path / "existing.txt"
        existing_file.write_text("existing content")

        # Create template.yml
        github_dir = tmp_path / ".github"
        github_dir.mkdir()
        template_file = github_dir / "template.yml"

        with open(template_file, "w") as f:
            yaml.dump(
                {
                    "template-repository": "jebel-quant/rhiza",
                    "template-branch": "main",
                    "include": ["existing.txt"],
                },
                f,
            )

        # Mock tempfile with the file to copy
        temp_dir = tmp_path / "temp"
        temp_dir.mkdir()
        src_file = temp_dir / "existing.txt"
        src_file.write_text("new content")
        mock_mkdtemp.return_value = str(temp_dir)

        # Mock subprocess to succeed
        mock_subprocess.return_value = Mock(returncode=0)

        # Run materialize without force (should skip existing file)
        materialize(tmp_path, "main", None, False)

        # Verify .rhiza.history includes the skipped file
        history_file = tmp_path / ".rhiza.history"
        assert history_file.exists()
        history_content = history_file.read_text()
        assert "existing.txt" in history_content

    @patch("rhiza.commands.materialize.subprocess.run")
    @patch("rhiza.commands.materialize.shutil.rmtree")
    @patch("rhiza.commands.materialize.shutil.copy2")
    @patch("rhiza.commands.materialize.tempfile.mkdtemp")
    def test_materialize_gitlab_repository(self, mock_mkdtemp, mock_copy2, mock_rmtree, mock_subprocess, tmp_path):
        """Test that materialize uses GitLab URL when template-host is gitlab."""
        # Setup git repo
        git_dir = tmp_path / ".git"
        git_dir.mkdir()

        # Create template.yml with gitlab host
        github_dir = tmp_path / ".github"
        github_dir.mkdir()
        template_file = github_dir / "template.yml"

        with open(template_file, "w") as f:
            yaml.dump(
                {
                    "template-repository": "mygroup/myproject",
                    "template-branch": "main",
                    "template-host": "gitlab",
                    "include": [".gitlab-ci.yml"],
                },
                f,
            )

        # Mock tempfile
        temp_dir = tmp_path / "temp"
        temp_dir.mkdir()
        mock_mkdtemp.return_value = str(temp_dir)

        # Mock subprocess to succeed
        mock_subprocess.return_value = Mock(returncode=0)

        # Run materialize
        materialize(tmp_path, "main", None, False)

        # Verify the git clone command used GitLab URL
        clone_call = mock_subprocess.call_args_list[0]
        assert "gitlab.com" in str(clone_call)
        assert "mygroup/myproject.git" in str(clone_call)

    @patch("rhiza.commands.materialize.subprocess.run")
    @patch("rhiza.commands.materialize.shutil.rmtree")
    @patch("rhiza.commands.materialize.shutil.copy2")
    @patch("rhiza.commands.materialize.tempfile.mkdtemp")
    def test_materialize_github_repository_explicit(
        self, mock_mkdtemp, mock_copy2, mock_rmtree, mock_subprocess, tmp_path
    ):
        """Test that materialize uses GitHub URL when template-host is explicitly github."""
        # Setup git repo
        git_dir = tmp_path / ".git"
        git_dir.mkdir()

        # Create template.yml with explicit github host
        github_dir = tmp_path / ".github"
        github_dir.mkdir()
        template_file = github_dir / "template.yml"

        with open(template_file, "w") as f:
            yaml.dump(
                {
                    "template-repository": "jebel-quant/rhiza",
                    "template-branch": "main",
                    "template-host": "github",
                    "include": [".github"],
                },
                f,
            )

        # Mock tempfile
        temp_dir = tmp_path / "temp"
        temp_dir.mkdir()
        mock_mkdtemp.return_value = str(temp_dir)

        # Mock subprocess to succeed
        mock_subprocess.return_value = Mock(returncode=0)

        # Run materialize
        materialize(tmp_path, "main", None, False)

        # Verify the git clone command used GitHub URL
        clone_call = mock_subprocess.call_args_list[0]
        assert "github.com" in str(clone_call)
        assert "jebel-quant/rhiza.git" in str(clone_call)

    @patch("rhiza.commands.materialize.subprocess.run")
    @patch("rhiza.commands.materialize.shutil.rmtree")
    @patch("rhiza.commands.materialize.tempfile.mkdtemp")
    def test_materialize_fails_with_invalid_host(self, mock_mkdtemp, mock_rmtree, mock_subprocess, tmp_path):
        """Test that materialize fails with an unsupported template-host."""
        # Setup git repo
        git_dir = tmp_path / ".git"
        git_dir.mkdir()

        # Create template.yml with invalid host
        github_dir = tmp_path / ".github"
        github_dir.mkdir()
        template_file = github_dir / "template.yml"

        with open(template_file, "w") as f:
            yaml.dump(
                {
                    "template-repository": "invalid/repo",
                    "template-branch": "main",
                    "template-host": "bitbucket",
                    "include": [".github"],
                },
                f,
            )

        # Mock tempfile
        temp_dir = tmp_path / "temp"
        temp_dir.mkdir()
        mock_mkdtemp.return_value = str(temp_dir)

        # Run materialize and expect it to fail with ValueError
        with pytest.raises(ValueError, match="Unsupported template-host"):
            materialize(tmp_path, "main", None, False)

    @patch("rhiza.commands.materialize.subprocess.run")
    @patch("rhiza.commands.materialize.shutil.rmtree")
    @patch("rhiza.commands.materialize.shutil.copy2")
    @patch("rhiza.commands.materialize.tempfile.mkdtemp")
    def test_materialize_warns_for_workflow_files(
        self, mock_mkdtemp, mock_copy2, mock_rmtree, mock_subprocess, tmp_path
    ):
        """Test that materialize warns when workflow files are materialized."""
        # Setup git repo
        git_dir = tmp_path / ".git"
        git_dir.mkdir()

        # Create template.yml including workflow files
        github_dir = tmp_path / ".github"
        github_dir.mkdir()
        template_file = github_dir / "template.yml"

        with open(template_file, "w") as f:
            yaml.dump(
                {
                    "template-repository": "jebel-quant/rhiza",
                    "template-branch": "main",
                    "include": [".github/workflows"],
                },
                f,
            )

        # Mock tempfile with workflow files
        temp_dir = tmp_path / "temp"
        temp_dir.mkdir()
        workflows_dir = temp_dir / ".github" / "workflows"
        workflows_dir.mkdir(parents=True)
        workflow_file = workflows_dir / "ci.yml"
        workflow_file.write_text("name: CI")
        mock_mkdtemp.return_value = str(temp_dir)

        # Mock subprocess to succeed
        mock_subprocess.return_value = Mock(returncode=0)

        # Patch logger.warning to verify it's called
        with patch("rhiza.commands.materialize.logger.warning") as mock_warning:
            # Run materialize
            materialize(tmp_path, "main", None, False)

            # Verify warning was called
            mock_warning.assert_called_once()
            # Verify the warning message contains expected text
            call_args = mock_warning.call_args[0][0]
            assert "workflow" in call_args.lower()
            assert "permission" in call_args.lower()

    @patch("rhiza.commands.materialize.init")
    def test_materialize_empty_include_paths_raises_error(self, mock_init, tmp_path):
        """Test that materialize raises RuntimeError when include_paths is empty after validation."""
        # Setup git repo
        git_dir = tmp_path / ".git"
        git_dir.mkdir()

        # Create template.yml with empty include (bypassing normal validation)
        github_dir = tmp_path / ".github"
        github_dir.mkdir()
        template_file = github_dir / "template.yml"

        with open(template_file, "w") as f:
            yaml.dump(
                {
                    "template-repository": "jebel-quant/rhiza",
                    "template-branch": "main",
                    "include": [],
                },
                f,
            )

        # Mock init to return True (bypass validation that would catch this)
        mock_init.return_value = True

        # Run materialize and expect RuntimeError
        with pytest.raises(RuntimeError, match="No include paths found"):
            materialize(tmp_path, "main", None, False)

    @patch("rhiza.commands.materialize.subprocess.run")
    @patch("rhiza.commands.materialize.shutil.rmtree")
    @patch("rhiza.commands.materialize.shutil.copy2")
    @patch("rhiza.commands.materialize.tempfile.mkdtemp")
    def test_materialize_creates_new_branch(self, mock_mkdtemp, mock_copy2, mock_rmtree, mock_subprocess, tmp_path):
        """Test that materialize creates a new branch when target_branch is specified and doesn't exist."""
        # Setup git repo
        git_dir = tmp_path / ".git"
        git_dir.mkdir()

        # Create template.yml
        github_dir = tmp_path / ".github"
        github_dir.mkdir()
        template_file = github_dir / "template.yml"

        with open(template_file, "w") as f:
            yaml.dump(
                {"template-repository": "jebel-quant/rhiza", "template-branch": "main", "include": [".github"]}, f
            )

        # Mock tempfile
        temp_dir = tmp_path / "temp"
        temp_dir.mkdir()
        mock_mkdtemp.return_value = str(temp_dir)

        # Mock subprocess calls
        # First call: git rev-parse (branch doesn't exist)
        # Remaining calls: git clone, git sparse-checkout, etc.
        def subprocess_side_effect(*args, **kwargs):
            cmd = args[0] if args else kwargs.get("args", [])
            if "rev-parse" in cmd:
                # Return non-zero to indicate branch doesn't exist
                mock_result = Mock()
                mock_result.returncode = 1
                mock_result.stdout = ""
                mock_result.stderr = "fatal: Needed a single revision"
                return mock_result
            # Other commands succeed
            mock_result = Mock()
            mock_result.returncode = 0
            return mock_result

        mock_subprocess.side_effect = subprocess_side_effect

        # Run materialize with target_branch
        materialize(tmp_path, "main", "feature/test-branch", False)

        # Verify git checkout -b was called to create the branch
        checkout_calls = [
            call
            for call in mock_subprocess.call_args_list
            if len(call[0]) > 0 and call[0][0] == ["git", "checkout", "-b", "feature/test-branch"]
        ]
        assert len(checkout_calls) > 0, "Expected git checkout -b feature/test-branch to be called"

    @patch("rhiza.commands.materialize.subprocess.run")
    @patch("rhiza.commands.materialize.shutil.rmtree")
    @patch("rhiza.commands.materialize.shutil.copy2")
    @patch("rhiza.commands.materialize.tempfile.mkdtemp")
    def test_materialize_checks_out_existing_branch(
        self, mock_mkdtemp, mock_copy2, mock_rmtree, mock_subprocess, tmp_path
    ):
        """Test that materialize checks out an existing branch when target_branch is specified."""
        # Setup git repo
        git_dir = tmp_path / ".git"
        git_dir.mkdir()

        # Create template.yml
        github_dir = tmp_path / ".github"
        github_dir.mkdir()
        template_file = github_dir / "template.yml"

        with open(template_file, "w") as f:
            yaml.dump(
                {"template-repository": "jebel-quant/rhiza", "template-branch": "main", "include": [".github"]}, f
            )

        # Mock tempfile
        temp_dir = tmp_path / "temp"
        temp_dir.mkdir()
        mock_mkdtemp.return_value = str(temp_dir)

        # Mock subprocess calls
        def subprocess_side_effect(*args, **kwargs):
            cmd = args[0] if args else kwargs.get("args", [])
            if "rev-parse" in cmd:
                # Return zero to indicate branch exists
                mock_result = Mock()
                mock_result.returncode = 0
                mock_result.stdout = "abc123"
                return mock_result
            # Other commands succeed
            mock_result = Mock()
            mock_result.returncode = 0
            return mock_result

        mock_subprocess.side_effect = subprocess_side_effect

        # Run materialize with target_branch
        materialize(tmp_path, "main", "existing-branch", False)

        # Verify git checkout (without -b) was called to checkout existing branch
        checkout_calls = [
            call
            for call in mock_subprocess.call_args_list
            if len(call[0]) > 0 and call[0][0] == ["git", "checkout", "existing-branch"]
        ]
        assert len(checkout_calls) > 0, "Expected git checkout existing-branch to be called"

    @patch("rhiza.commands.materialize.subprocess.run")
    @patch("rhiza.commands.materialize.shutil.rmtree")
    @patch("rhiza.commands.materialize.shutil.copy2")
    @patch("rhiza.commands.materialize.tempfile.mkdtemp")
    def test_materialize_no_branch_stays_on_current(
        self, mock_mkdtemp, mock_copy2, mock_rmtree, mock_subprocess, tmp_path
    ):
        """Test that materialize stays on current branch when target_branch is not specified."""
        # Setup git repo
        git_dir = tmp_path / ".git"
        git_dir.mkdir()

        # Create template.yml
        github_dir = tmp_path / ".github"
        github_dir.mkdir()
        template_file = github_dir / "template.yml"

        with open(template_file, "w") as f:
            yaml.dump(
                {"template-repository": "jebel-quant/rhiza", "template-branch": "main", "include": [".github"]}, f
            )

        # Mock tempfile
        temp_dir = tmp_path / "temp"
        temp_dir.mkdir()
        mock_mkdtemp.return_value = str(temp_dir)

        # Mock subprocess to succeed
        mock_subprocess.return_value = Mock(returncode=0)

        # Run materialize without target_branch
        materialize(tmp_path, "main", None, False)

        # Verify no git checkout commands were called for branch switching
        # We check for git commands that start with ["git", "checkout", ...] but exclude sparse-checkout
        branch_checkout_calls = [
            call
            for call in mock_subprocess.call_args_list
            if (
                len(call[0]) > 0
                and len(call[0][0]) >= 2
                and call[0][0][0] == "git"
                and call[0][0][1] == "checkout"
                and "sparse-checkout" not in " ".join(call[0][0])
            )
        ]
        assert len(branch_checkout_calls) == 0, "No git checkout for branch switching should be called"

    @patch("rhiza.commands.materialize.subprocess.run")
    @patch("rhiza.commands.materialize.shutil.rmtree")
    @patch("rhiza.commands.materialize.tempfile.mkdtemp")
    def test_materialize_exits_on_branch_checkout_failure(self, mock_mkdtemp, mock_rmtree, mock_subprocess, tmp_path):
        """Test that materialize exits when branch checkout fails."""
        # Setup git repo
        git_dir = tmp_path / ".git"
        git_dir.mkdir()

        # Create template.yml
        github_dir = tmp_path / ".github"
        github_dir.mkdir()
        template_file = github_dir / "template.yml"

        with open(template_file, "w") as f:
            yaml.dump(
                {"template-repository": "jebel-quant/rhiza", "template-branch": "main", "include": [".github"]}, f
            )

        # Mock tempfile
        temp_dir = tmp_path / "temp"
        temp_dir.mkdir()
        mock_mkdtemp.return_value = str(temp_dir)

        # Mock subprocess to fail on checkout
        def subprocess_side_effect(*args, **kwargs):
            cmd = args[0] if args else kwargs.get("args", [])
            if "checkout" in cmd:
                raise subprocess.CalledProcessError(1, cmd, stderr="error: pathspec 'bad' did not match")
            mock_result = Mock()
            mock_result.returncode = 0
            return mock_result

        mock_subprocess.side_effect = subprocess_side_effect

        # Run materialize and expect it to exit
        with pytest.raises(SystemExit):
            materialize(tmp_path, "main", "bad-branch", False)
