"""Tests for gittool module."""

from __future__ import annotations

import subprocess
from unittest.mock import MagicMock, patch

import pytest

from pytola.dev.gittool.cli import (
    GitBranchCommand,
    GitCheckoutCommand,
    GitCleanCommand,
    GitInitCommand,
    GitPullCommand,
    GitPushCommand,
    ParallelConfig,
    _execute_single_command,
    check_git_status,
    run_parallel_commands,
    run_sequential_commands,
)


class TestGitStatusCheck:
    """Tests for git status checking functionality."""

    @patch("subprocess.run")
    def test_clean_repository(self, mock_subprocess):
        """Test checking status of clean repository."""
        # Mock clean repository (no output)
        mock_result = MagicMock()
        mock_result.stdout = ""
        mock_result.stderr = ""
        mock_result.returncode = 0
        mock_subprocess.return_value = mock_result

        result = check_git_status()
        assert result is True
        mock_subprocess.assert_called_once_with(
            ["git", "status", "--porcelain"],
            capture_output=True,
            text=True,
            check=False,
        )

    @patch("subprocess.run")
    def test_dirty_repository(self, mock_subprocess):
        """Test checking status of dirty repository."""
        # Mock dirty repository (has changes)
        mock_result = MagicMock()
        mock_result.stdout = " M modified_file.py\n?? new_file.txt"
        mock_result.stderr = ""
        mock_result.returncode = 0
        mock_subprocess.return_value = mock_result

        result = check_git_status()
        assert result is False

    @patch("subprocess.run")
    def test_git_command_failure(self, mock_subprocess):
        """Test handling of git command failure."""
        mock_subprocess.side_effect = subprocess.CalledProcessError(1, "git status")

        result = check_git_status()
        assert result is False


class TestParallelFramework:
    """Tests for parallel execution framework."""

    def test_parallel_config_auto(self):
        """Test automatic parallel configuration."""
        config = ParallelConfig.auto(cpu_count=4)
        assert config.max_workers == 8  # 4 * 2
        assert config.batch_size == 8  # 4 * 2
        assert config.show_progress is True

    def test_parallel_config_custom(self):
        """Test custom parallel configuration."""
        config = ParallelConfig(max_workers=6, batch_size=15, show_progress=False)
        assert config.max_workers == 6
        assert config.batch_size == 15
        assert config.show_progress is False

    @patch("subprocess.run")
    def test_execute_single_command_success(self, mock_subprocess):
        """Test successful single command execution."""
        mock_result = MagicMock()
        mock_result.stdout = "Command output"
        mock_result.stderr = ""
        mock_result.returncode = 0
        mock_subprocess.return_value = mock_result

        success, message = _execute_single_command(["git", "status"])
        assert success is True
        assert message == "Command output"

    @patch("subprocess.run")
    def test_execute_single_command_failure(self, mock_subprocess):
        """Test failed single command execution."""
        mock_subprocess.side_effect = subprocess.CalledProcessError(1, "git status", stderr="Error occurred")

        success, message = _execute_single_command(["git", "status"])
        assert success is False
        assert "Error occurred" in message

    @patch("pytola.dev.gittool.cli._execute_single_command")
    def test_run_sequential_commands(self, mock_execute):
        """Test sequential command execution."""
        mock_execute.side_effect = [
            (True, "Success 1"),
            (False, "Failure"),
            (True, "Success 2"),
        ]

        commands = [["cmd1"], ["cmd2"], ["cmd3"]]
        results = run_sequential_commands(commands)

        assert len(results) == 3
        assert results[0] == (["cmd1"], True, "Success 1")
        assert results[1] == (["cmd2"], False, "Failure")
        assert results[2] == (["cmd3"], True, "Success 2")

    @patch("concurrent.futures.ThreadPoolExecutor")
    @patch("pytola.dev.gittool.cli._execute_single_command")
    def test_run_parallel_commands(self, mock_execute, mock_executor_class):
        """Test parallel command execution."""
        # Mock the executor and futures
        mock_executor = MagicMock()
        mock_future1 = MagicMock()
        mock_future2 = MagicMock()

        mock_future1.result.return_value = (True, "Success 1")
        mock_future2.result.return_value = (False, "Failure")

        mock_executor.__enter__.return_value = mock_executor
        mock_executor.submit.side_effect = [mock_future1, mock_future2]
        mock_executor_class.return_value = mock_executor

        mock_execute.side_effect = [
            (True, "Success 1"),
            (False, "Failure"),
        ]

        commands = [["cmd1"], ["cmd2"]]
        config = ParallelConfig(max_workers=2)
        results = run_parallel_commands(commands, config)

        assert len(results) == 2
        # Results order may vary due to parallel execution
        result_commands = [result[0] for result in results]
        assert ["cmd1"] in result_commands
        assert ["cmd2"] in result_commands


class TestGitInitCommand:
    """Tests for GitInitCommand."""

    @patch("subprocess.run")
    @patch("pytola.dev.gittool.cli.logger")
    def test_successful_initialization(self, mock_logger, mock_subprocess):
        """Test successful git initialization."""
        mock_subprocess.return_value = MagicMock()

        cmd = GitInitCommand()
        cmd.run()

        # Should call all three subcommands
        assert mock_subprocess.call_count == 3
        mock_logger.info.assert_called_with("Git initialization completed successfully")

    @patch("subprocess.run")
    @patch("pytola.dev.gittool.cli.logger")
    def test_initialization_failure(self, mock_logger, mock_subprocess):
        """Test git initialization with failure."""
        # Make second command fail
        mock_subprocess.side_effect = [
            MagicMock(),  # git init succeeds
            subprocess.CalledProcessError(1, "git add"),  # git add fails
            MagicMock(),  # This won't be reached
        ]

        cmd = GitInitCommand()
        cmd.run()

        # Should log exception and stop execution
        assert mock_logger.exception.called
        assert mock_subprocess.call_count == 2  # Only first two commands called

    @patch("pytola.dev.gittool.cli._execute_single_command")
    @patch("pytola.dev.gittool.cli.run_parallel_commands")
    @patch("pytola.dev.gittool.cli.logger")
    def test_parallel_initialization_success(self, mock_logger, mock_parallel, mock_execute):
        """Test successful parallel git initialization."""
        # Mock sequential git init success
        mock_execute.return_value = (True, "Initialized empty Git repository")

        # Mock parallel commands success
        mock_parallel.return_value = [
            (["git", "add", "."], True, "Added files"),
            (["git", "commit", "-m", "initial commit"], True, "Committed changes"),
        ]

        cmd = GitInitCommand()
        cmd.run(parallel=True, max_workers=2)

        # Verify _execute_single_command was called for git init
        mock_execute.assert_called_once_with(["git", "init"])

        # Verify run_parallel_commands was called
        mock_parallel.assert_called_once()

        # Should log success message
        mock_logger.info.assert_called_with("Git initialization completed successfully with parallel execution")

    @patch("pytola.dev.gittool.cli._execute_single_command")
    @patch("pytola.dev.gittool.cli.logger")
    def test_parallel_initialization_init_failure(self, mock_logger, mock_execute):
        """Test parallel git initialization with init failure."""
        # Mock git init failure
        mock_execute.return_value = (False, "fatal: not a git repository")

        cmd = GitInitCommand()
        cmd.run(parallel=True)

        # Should log error and return early
        mock_logger.error.assert_called_with("Git init failed: fatal: not a git repository")
        mock_execute.assert_called_once_with(["git", "init"])


class TestGitCleanCommand:
    """Tests for GitCleanCommand."""

    @patch("subprocess.run")
    @patch("pytola.dev.gittool.cli.logger")
    def test_normal_clean(self, mock_logger, mock_subprocess):
        """Test normal clean operation."""
        mock_subprocess.return_value = MagicMock()

        cmd = GitCleanCommand()
        cmd.run()

        # Should call clean and checkout
        assert mock_subprocess.call_count == 2
        mock_logger.info.assert_called_with("Clean completed successfully")

    @patch("subprocess.run")
    @patch("pytola.dev.gittool.cli.logger")
    def test_force_clean(self, mock_logger, mock_subprocess):
        """Test force clean operation."""
        mock_subprocess.return_value = MagicMock()

        cmd = GitCleanCommand()
        cmd.run(force=True)

        # Check that the first call includes -f flag
        first_call_args = mock_subprocess.call_args_list[0][0][0]
        assert "-f" in first_call_args
        assert "clean" in first_call_args

    @patch("subprocess.run")
    @patch("pytola.dev.gittool.cli.logger")
    def test_clean_with_exclusions(self, mock_logger, mock_subprocess):
        """Test clean operation respects excluded directories."""
        mock_subprocess.return_value = MagicMock()

        cmd = GitCleanCommand()
        cmd.run()

        # Verify exclusion patterns are included
        call_args = mock_subprocess.call_args_list[0][0][0]
        assert "-e" in call_args
        assert ".venv" in call_args
        assert "node_modules" in call_args
        assert ".git" in call_args

    @patch("pytola.dev.gittool.cli.run_parallel_commands")
    @patch("pytola.dev.gittool.cli.logger")
    def test_parallel_clean_success(self, mock_logger, mock_parallel):
        """Test successful parallel clean operation."""
        # Mock successful parallel execution
        mock_parallel.return_value = [
            (
                [
                    "git",
                    "clean",
                    "-xd",
                    "-e",
                    ".venv",
                    "-e",
                    "node_modules",
                    "-e",
                    ".git",
                    "-e",
                    ".idea",
                    "-e",
                    ".vscode",
                    "-f",
                ],
                True,
                "Cleaned successfully",
            ),
            (["git", "checkout", "."], True, "Checked out successfully"),
        ]

        cmd = GitCleanCommand()
        cmd.run(force=True, parallel=True, max_workers=4)

        # Verify run_parallel_commands was called
        mock_parallel.assert_called_once()
        mock_logger.info.assert_called_with("Parallel clean completed successfully")

    @patch("pytola.dev.gittool.cli.run_parallel_commands")
    @patch("pytola.dev.gittool.cli.logger")
    def test_parallel_clean_failure(self, mock_logger, mock_parallel):
        """Test parallel clean operation with failures."""
        # Mock failed parallel execution
        mock_parallel.return_value = [
            (["git", "clean", "-xd"], False, "Clean failed"),
            (["git", "checkout", "."], True, "Checked out successfully"),
        ]

        cmd = GitCleanCommand()
        cmd.run(parallel=True)

        # Should log error about failed commands
        assert mock_logger.error.called


class TestGitPushCommand:
    """Tests for GitPushCommand."""

    @patch("subprocess.run")
    @patch("pytola.dev.gittool.cli.logger")
    def test_normal_push(self, mock_logger, mock_subprocess):
        """Test normal push operation."""
        mock_subprocess.return_value = MagicMock()

        cmd = GitPushCommand()
        cmd.run()

        mock_subprocess.assert_called_with(["git", "push"], check=True)
        mock_logger.info.assert_called_with("Push completed successfully")

    @patch("subprocess.run")
    @patch("pytola.dev.gittool.cli.logger")
    def test_push_all_branches(self, mock_logger, mock_subprocess):
        """Test push all branches operation."""
        mock_subprocess.return_value = MagicMock()

        cmd = GitPushCommand()
        cmd.run(all_branches=True)

        mock_subprocess.assert_called_with(["git", "push", "--all"], check=True)
        mock_logger.info.assert_called_with("Push all branches completed successfully")


class TestGitPullCommand:
    """Tests for GitPullCommand."""

    @patch("subprocess.run")
    @patch("pytola.dev.gittool.cli.logger")
    def test_normal_pull(self, mock_logger, mock_subprocess):
        """Test normal pull operation."""
        mock_subprocess.return_value = MagicMock()

        cmd = GitPullCommand()
        cmd.run()

        mock_subprocess.assert_called_with(["git", "pull"], check=True)
        mock_logger.info.assert_called_with("Pull completed successfully")

    @patch("subprocess.run")
    @patch("pytola.dev.gittool.cli.logger")
    def test_pull_with_rebase(self, mock_logger, mock_subprocess):
        """Test pull with rebase operation."""
        mock_subprocess.return_value = MagicMock()

        cmd = GitPullCommand()
        cmd.run(rebase=True)

        mock_subprocess.assert_called_with(["git", "pull", "--rebase"], check=True)
        mock_logger.info.assert_called_with("Pull with rebase completed successfully")


class TestGitCheckoutCommand:
    """Tests for GitCheckoutCommand."""

    @patch("subprocess.run")
    @patch("pytola.dev.gittool.cli.logger")
    def test_checkout_existing_branch(self, mock_logger, mock_subprocess):
        """Test checking out existing branch."""
        mock_subprocess.return_value = MagicMock()

        cmd = GitCheckoutCommand()
        cmd.run("main")

        mock_subprocess.assert_called_with(["git", "checkout", "main"], check=True)
        mock_logger.info.assert_called_with("Checked out branch: main")

    @patch("subprocess.run")
    @patch("pytola.dev.gittool.cli.logger")
    def test_create_and_checkout_branch(self, mock_logger, mock_subprocess):
        """Test creating and checking out new branch."""
        mock_subprocess.return_value = MagicMock()

        cmd = GitCheckoutCommand()
        cmd.run("feature/new", create=True)

        mock_subprocess.assert_called_with(["git", "checkout", "-b", "feature/new"], check=True)
        mock_logger.info.assert_called_with("Created and checked out branch: feature/new")


class TestGitBranchCommand:
    """Tests for GitBranchCommand."""

    @patch("subprocess.run")
    @patch("pytola.dev.gittool.cli.logger")
    def test_list_local_branches(self, mock_logger, mock_subprocess):
        """Test listing local branches."""
        mock_result = MagicMock()
        mock_result.stdout = "* main\n  develop\n  feature/test"
        mock_subprocess.return_value = mock_result

        cmd = GitBranchCommand()
        cmd.run()

        mock_subprocess.assert_called_with(
            ["git", "branch"],
            capture_output=True,
            text=True,
            check=True,
        )

    @patch("subprocess.run")
    @patch("pytola.dev.gittool.cli.logger")
    def test_list_all_branches(self, mock_logger, mock_subprocess):
        """Test listing all branches."""
        mock_result = MagicMock()
        mock_result.stdout = "* main\n  remotes/origin/main"
        mock_subprocess.return_value = mock_result

        cmd = GitBranchCommand()
        cmd.run(list_all=True)

        mock_subprocess.assert_called_with(
            ["git", "branch", "-a"],
            capture_output=True,
            text=True,
            check=True,
        )

    @patch("subprocess.run")
    @patch("pytola.dev.gittool.cli.logger")
    def test_delete_branch(self, mock_logger, mock_subprocess):
        """Test deleting branch."""
        mock_subprocess.return_value = MagicMock()

        cmd = GitBranchCommand()
        cmd.run(delete="feature/old")

        mock_subprocess.assert_called_with(["git", "branch", "-d", "feature/old"], check=True)
        mock_logger.info.assert_called_with("Deleted branch: feature/old")


if __name__ == "__main__":
    pytest.main([__file__, "-v"])
