"""Tests for gittool module."""

from __future__ import annotations

import subprocess
from unittest.mock import MagicMock, patch

import pytest

from sfi.gittool.gittool import (
    GitBranchCommand,
    GitCheckoutCommand,
    GitCleanCommand,
    GitInitCommand,
    GitPullCommand,
    GitPushCommand,
    check_git_status,
)


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 TestGitInitCommand:
    """Tests for GitInitCommand."""

    @patch("subprocess.run")
    @patch("sfi.gittool.gittool.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("sfi.gittool.gittool.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 error and stop execution
        assert mock_logger.error.called
        assert mock_subprocess.call_count == 2  # Only first two commands called


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

    @patch("subprocess.run")
    @patch("sfi.gittool.gittool.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("sfi.gittool.gittool.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("sfi.gittool.gittool.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


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

    @patch("subprocess.run")
    @patch("sfi.gittool.gittool.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("sfi.gittool.gittool.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("sfi.gittool.gittool.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("sfi.gittool.gittool.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("sfi.gittool.gittool.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("sfi.gittool.gittool.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("sfi.gittool.gittool.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("sfi.gittool.gittool.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("sfi.gittool.gittool.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"])
