"""
Tests for the git_wrapper module.
"""

from pathlib import Path
from unittest.mock import MagicMock, patch

import pytest
from git import GitCommandError

from exceptions import (
    BranchExistsError,
    BranchNotFoundError,
    CheckoutError,
    CloneError,
    CommitError,
    GitError,
    PullError,
    PushError,
)
from git_wrapper import GitWrapper, run_in_executor
from models import FileChangeType


class TestGitWrapperInit:
    """Tests for GitWrapper initialization."""

    def test_init_with_valid_path(self, temp_workspace, test_settings):
        """Test initialization with a valid path."""
        wrapper = GitWrapper(temp_workspace, test_settings)
        assert wrapper.workspace_path == temp_workspace
        assert wrapper.settings == test_settings

    def test_repo_property_raises_on_non_git(self, temp_workspace, test_settings):
        """Test that accessing repo on non-git dir raises error."""
        wrapper = GitWrapper(temp_workspace, test_settings)
        with pytest.raises(GitError, match="Not a git repository"):
            _ = wrapper.repo

    def test_repo_property_works_on_git_dir(self, git_repo, test_settings):
        """Test that repo property works for git directory."""
        wrapper = GitWrapper(Path(git_repo.working_dir), test_settings)
        assert wrapper.repo is not None
        assert wrapper.repo.head is not None


class TestGitWrapperStatus:
    """Tests for git status operations."""

    @pytest.mark.asyncio
    async def test_status_clean_repo(self, git_wrapper_with_repo):
        """Test status on a clean repository."""
        result = await git_wrapper_with_repo.status()

        assert result.branch == "main"
        assert result.is_clean is True
        assert len(result.staged) == 0
        assert len(result.unstaged) == 0
        assert len(result.untracked) == 0

    @pytest.mark.asyncio
    async def test_status_with_untracked(self, git_wrapper_with_repo, git_repo):
        """Test status with untracked files."""
        # Create untracked file
        untracked_file = Path(git_repo.working_dir) / "untracked.txt"
        untracked_file.write_text("untracked content")

        result = await git_wrapper_with_repo.status()

        assert result.is_clean is False
        assert "untracked.txt" in result.untracked

    @pytest.mark.asyncio
    async def test_status_with_modified(self, git_wrapper_with_repo, git_repo):
        """Test status with modified files."""
        # Modify existing file
        readme = Path(git_repo.working_dir) / "README.md"
        readme.write_text("# Modified content\n")

        result = await git_wrapper_with_repo.status()

        assert result.is_clean is False
        assert len(result.unstaged) > 0

    @pytest.mark.asyncio
    async def test_status_with_staged(self, git_wrapper_with_repo, git_repo):
        """Test status with staged changes."""
        # Create and stage a file
        new_file = Path(git_repo.working_dir) / "staged.txt"
        new_file.write_text("staged content")
        git_repo.index.add(["staged.txt"])

        result = await git_wrapper_with_repo.status()

        assert result.is_clean is False
        assert len(result.staged) > 0

    @pytest.mark.asyncio
    async def test_status_exclude_untracked(self, git_wrapper_with_repo, git_repo):
        """Test status without untracked files."""
        untracked_file = Path(git_repo.working_dir) / "untracked.txt"
        untracked_file.write_text("untracked")

        result = await git_wrapper_with_repo.status(include_untracked=False)

        assert len(result.untracked) == 0


class TestGitWrapperBranch:
    """Tests for branch operations."""

    @pytest.mark.asyncio
    async def test_create_branch(self, git_wrapper_with_repo):
        """Test creating a new branch."""
        result = await git_wrapper_with_repo.create_branch("feature-test")

        assert result.success is True
        assert result.branch == "feature-test"
        assert result.is_current is True

    @pytest.mark.asyncio
    async def test_create_branch_without_checkout(self, git_wrapper_with_repo):
        """Test creating branch without checkout."""
        result = await git_wrapper_with_repo.create_branch(
            "feature-no-checkout", checkout=False
        )

        assert result.success is True
        assert result.branch == "feature-no-checkout"
        assert result.is_current is False

    @pytest.mark.asyncio
    async def test_create_branch_exists_error(self, git_wrapper_with_repo):
        """Test error when branch already exists."""
        await git_wrapper_with_repo.create_branch("existing-branch", checkout=False)

        with pytest.raises(BranchExistsError):
            await git_wrapper_with_repo.create_branch("existing-branch")

    @pytest.mark.asyncio
    async def test_delete_branch(self, git_wrapper_with_repo):
        """Test deleting a branch."""
        # Create branch first
        await git_wrapper_with_repo.create_branch("to-delete", checkout=False)

        # Delete it
        result = await git_wrapper_with_repo.delete_branch("to-delete")

        assert result.success is True
        assert result.branch == "to-delete"

    @pytest.mark.asyncio
    async def test_delete_branch_not_found(self, git_wrapper_with_repo):
        """Test error when deleting non-existent branch."""
        with pytest.raises(BranchNotFoundError):
            await git_wrapper_with_repo.delete_branch("nonexistent")

    @pytest.mark.asyncio
    async def test_delete_current_branch_error(self, git_wrapper_with_repo):
        """Test error when deleting current branch."""
        with pytest.raises(GitError, match="Cannot delete current branch"):
            await git_wrapper_with_repo.delete_branch("main")

    @pytest.mark.asyncio
    async def test_list_branches(self, git_wrapper_with_repo):
        """Test listing branches."""
        # Create some branches
        await git_wrapper_with_repo.create_branch("branch-a", checkout=False)
        await git_wrapper_with_repo.create_branch("branch-b", checkout=False)

        result = await git_wrapper_with_repo.list_branches()

        assert result.current_branch == "main"
        branch_names = [b["name"] for b in result.local_branches]
        assert "main" in branch_names
        assert "branch-a" in branch_names
        assert "branch-b" in branch_names


class TestGitWrapperCheckout:
    """Tests for checkout operations."""

    @pytest.mark.asyncio
    async def test_checkout_existing_branch(self, git_wrapper_with_repo):
        """Test checkout of existing branch."""
        # Create branch first
        await git_wrapper_with_repo.create_branch("test-branch", checkout=False)

        result = await git_wrapper_with_repo.checkout("test-branch")

        assert result.success is True
        assert result.ref == "test-branch"

    @pytest.mark.asyncio
    async def test_checkout_create_new(self, git_wrapper_with_repo):
        """Test checkout with branch creation."""
        result = await git_wrapper_with_repo.checkout("new-branch", create_branch=True)

        assert result.success is True
        assert result.ref == "new-branch"

    @pytest.mark.asyncio
    async def test_checkout_nonexistent_error(self, git_wrapper_with_repo):
        """Test error when checking out non-existent ref."""
        with pytest.raises(CheckoutError):
            await git_wrapper_with_repo.checkout("nonexistent-branch")


class TestGitWrapperCommit:
    """Tests for commit operations."""

    @pytest.mark.asyncio
    async def test_commit_staged_changes(self, git_wrapper_with_repo, git_repo):
        """Test committing staged changes."""
        # Create and stage a file
        new_file = Path(git_repo.working_dir) / "newfile.txt"
        new_file.write_text("new content")
        git_repo.index.add(["newfile.txt"])

        result = await git_wrapper_with_repo.commit("Add new file")

        assert result.success is True
        assert result.message == "Add new file"
        assert result.files_changed == 1

    @pytest.mark.asyncio
    async def test_commit_all_changes(self, git_wrapper_with_repo, git_repo):
        """Test committing all changes (auto-stage)."""
        # Create a file without staging
        new_file = Path(git_repo.working_dir) / "unstaged.txt"
        new_file.write_text("content")

        result = await git_wrapper_with_repo.commit("Commit unstaged")

        assert result.success is True

    @pytest.mark.asyncio
    async def test_commit_nothing_to_commit(self, git_wrapper_with_repo):
        """Test error when nothing to commit."""
        with pytest.raises(CommitError, match="Nothing to commit"):
            await git_wrapper_with_repo.commit("Empty commit")

    @pytest.mark.asyncio
    async def test_commit_with_author(self, git_wrapper_with_repo, git_repo):
        """Test commit with custom author."""
        new_file = Path(git_repo.working_dir) / "authored.txt"
        new_file.write_text("authored content")

        result = await git_wrapper_with_repo.commit(
            "Custom author commit",
            author_name="Custom Author",
            author_email="custom@test.com",
        )

        assert result.success is True


class TestGitWrapperDiff:
    """Tests for diff operations."""

    @pytest.mark.asyncio
    async def test_diff_no_changes(self, git_wrapper_with_repo):
        """Test diff with no changes."""
        result = await git_wrapper_with_repo.diff()

        assert result.files_changed == 0
        assert result.total_additions == 0
        assert result.total_deletions == 0

    @pytest.mark.asyncio
    async def test_diff_with_changes(self, git_wrapper_with_repo, git_repo):
        """Test diff with modified files."""
        # Modify a file
        readme = Path(git_repo.working_dir) / "README.md"
        readme.write_text("# Modified\nNew line\n")

        result = await git_wrapper_with_repo.diff()

        assert result.files_changed > 0


class TestGitWrapperLog:
    """Tests for log operations."""

    @pytest.mark.asyncio
    async def test_log_basic(self, git_wrapper_with_repo):
        """Test basic log."""
        result = await git_wrapper_with_repo.log()

        assert result.total_commits > 0
        assert len(result.commits) > 0

    @pytest.mark.asyncio
    async def test_log_with_limit(self, git_wrapper_with_repo, git_repo):
        """Test log with limit."""
        # Create more commits
        for i in range(5):
            file_path = Path(git_repo.working_dir) / f"file{i}.txt"
            file_path.write_text(f"content {i}")
            git_repo.index.add([f"file{i}.txt"])
            git_repo.index.commit(f"Commit {i}")

        result = await git_wrapper_with_repo.log(limit=3)

        assert len(result.commits) == 3

    @pytest.mark.asyncio
    async def test_log_commit_info(self, git_wrapper_with_repo):
        """Test that log returns proper commit info."""
        result = await git_wrapper_with_repo.log(limit=1)

        commit = result.commits[0]
        assert "sha" in commit
        assert "message" in commit
        assert "author_name" in commit
        assert "author_email" in commit


class TestGitWrapperUtilities:
    """Tests for utility methods."""

    @pytest.mark.asyncio
    async def test_is_valid_ref_true(self, git_wrapper_with_repo):
        """Test valid ref detection."""
        is_valid = await git_wrapper_with_repo.is_valid_ref("main")
        assert is_valid is True

    @pytest.mark.asyncio
    async def test_is_valid_ref_false(self, git_wrapper_with_repo):
        """Test invalid ref detection."""
        is_valid = await git_wrapper_with_repo.is_valid_ref("nonexistent")
        assert is_valid is False

    def test_diff_to_change_type(self, git_wrapper_with_repo):
        """Test change type conversion."""
        wrapper = git_wrapper_with_repo

        assert wrapper._diff_to_change_type("A") == FileChangeType.ADDED
        assert wrapper._diff_to_change_type("M") == FileChangeType.MODIFIED
        assert wrapper._diff_to_change_type("D") == FileChangeType.DELETED
        assert wrapper._diff_to_change_type("R") == FileChangeType.RENAMED


class TestGitWrapperStage:
    """Tests for staging operations."""

    @pytest.mark.asyncio
    async def test_stage_specific_files(self, git_wrapper_with_repo, git_repo):
        """Test staging specific files."""
        # Create files
        file1 = Path(git_repo.working_dir) / "file1.txt"
        file2 = Path(git_repo.working_dir) / "file2.txt"
        file1.write_text("content 1")
        file2.write_text("content 2")

        count = await git_wrapper_with_repo.stage(["file1.txt"])

        assert count == 1

    @pytest.mark.asyncio
    async def test_stage_all(self, git_wrapper_with_repo, git_repo):
        """Test staging all files."""
        file1 = Path(git_repo.working_dir) / "all1.txt"
        file2 = Path(git_repo.working_dir) / "all2.txt"
        file1.write_text("content 1")
        file2.write_text("content 2")

        count = await git_wrapper_with_repo.stage()

        assert count >= 2

    @pytest.mark.asyncio
    async def test_unstage_files(self, git_wrapper_with_repo, git_repo):
        """Test unstaging files."""
        # Create and stage file
        file1 = Path(git_repo.working_dir) / "unstage.txt"
        file1.write_text("to unstage")
        git_repo.index.add(["unstage.txt"])

        count = await git_wrapper_with_repo.unstage()

        assert count >= 1


class TestGitWrapperReset:
    """Tests for reset operations."""

    @pytest.mark.asyncio
    async def test_reset_soft(self, git_wrapper_with_repo, git_repo):
        """Test soft reset."""
        # Create a commit to reset
        file1 = Path(git_repo.working_dir) / "reset_soft.txt"
        file1.write_text("content")
        git_repo.index.add(["reset_soft.txt"])
        git_repo.index.commit("Commit to reset")

        result = await git_wrapper_with_repo.reset("HEAD~1", mode="soft")

        assert result is True

    @pytest.mark.asyncio
    async def test_reset_mixed(self, git_wrapper_with_repo, git_repo):
        """Test mixed reset (default)."""
        file1 = Path(git_repo.working_dir) / "reset_mixed.txt"
        file1.write_text("content")
        git_repo.index.add(["reset_mixed.txt"])
        git_repo.index.commit("Commit to reset")

        result = await git_wrapper_with_repo.reset("HEAD~1", mode="mixed")

        assert result is True

    @pytest.mark.asyncio
    async def test_reset_invalid_mode(self, git_wrapper_with_repo):
        """Test error on invalid reset mode."""
        with pytest.raises(GitError, match="Invalid reset mode"):
            await git_wrapper_with_repo.reset("HEAD", mode="invalid")


class TestGitWrapperStash:
    """Tests for stash operations."""

    @pytest.mark.asyncio
    async def test_stash_changes(self, git_wrapper_with_repo, git_repo):
        """Test stashing changes."""
        # Make changes
        readme = Path(git_repo.working_dir) / "README.md"
        readme.write_text("Modified for stash")

        result = await git_wrapper_with_repo.stash("Test stash")

        # Result should be stash ref or None if nothing to stash
        # (depends on whether changes were already staged)
        assert result is None or result.startswith("stash@")

    @pytest.mark.asyncio
    async def test_stash_nothing(self, git_wrapper_with_repo):
        """Test stash with no changes."""
        result = await git_wrapper_with_repo.stash()

        assert result is None

    @pytest.mark.asyncio
    async def test_stash_pop(self, git_wrapper_with_repo, git_repo):
        """Test popping a stash."""
        # Make changes and stash them
        readme = Path(git_repo.working_dir) / "README.md"
        original_content = readme.read_text()
        readme.write_text("Modified for stash pop test")
        git_repo.index.add(["README.md"])

        stash_ref = await git_wrapper_with_repo.stash("Test stash for pop")

        if stash_ref:
            # Pop the stash
            result = await git_wrapper_with_repo.stash_pop()
            assert result is True


class TestGitWrapperRepoProperty:
    """Tests for repo property edge cases."""

    def test_repo_property_path_not_exists(self, test_settings):
        """Test that accessing repo on non-existent path raises error."""
        wrapper = GitWrapper(
            Path("/nonexistent/path/that/does/not/exist"), test_settings
        )
        with pytest.raises(GitError, match="Path does not exist"):
            _ = wrapper.repo

    def test_refresh_repo(self, git_wrapper_with_repo):
        """Test _refresh_repo clears cached repo."""
        # Access repo to cache it
        _ = git_wrapper_with_repo.repo
        assert git_wrapper_with_repo._repo is not None

        # Refresh should clear it
        git_wrapper_with_repo._refresh_repo()
        assert git_wrapper_with_repo._repo is None


class TestGitWrapperBranchAdvanced:
    """Advanced tests for branch operations."""

    @pytest.mark.asyncio
    async def test_create_branch_from_ref(self, git_wrapper_with_repo, git_repo):
        """Test creating branch from specific ref."""
        # Get current HEAD SHA
        head_sha = git_repo.head.commit.hexsha

        result = await git_wrapper_with_repo.create_branch(
            "feature-from-ref",
            from_ref=head_sha,
            checkout=False,
        )

        assert result.success is True
        assert result.branch == "feature-from-ref"

    @pytest.mark.asyncio
    async def test_delete_branch_force(self, git_wrapper_with_repo, git_repo):
        """Test force deleting a branch."""
        # Create branch and add unmerged commit
        await git_wrapper_with_repo.create_branch("unmerged-branch", checkout=True)
        new_file = Path(git_repo.working_dir) / "unmerged.txt"
        new_file.write_text("unmerged content")
        git_repo.index.add(["unmerged.txt"])
        git_repo.index.commit("Unmerged commit")

        # Switch back to main
        await git_wrapper_with_repo.checkout("main")

        # Force delete
        result = await git_wrapper_with_repo.delete_branch(
            "unmerged-branch", force=True
        )

        assert result.success is True


class TestGitWrapperListBranchesRemote:
    """Tests for listing remote branches."""

    @pytest.mark.asyncio
    async def test_list_branches_with_remote(self, git_wrapper_with_repo):
        """Test listing branches including remote."""
        # Even without remotes, this should work
        result = await git_wrapper_with_repo.list_branches(include_remote=True)

        assert result.current_branch == "main"
        # Remote branches list should be empty for local repo
        assert len(result.remote_branches) == 0


class TestGitWrapperCheckoutAdvanced:
    """Advanced tests for checkout operations."""

    @pytest.mark.asyncio
    async def test_checkout_create_existing_error(self, git_wrapper_with_repo):
        """Test error when creating branch that already exists."""
        with pytest.raises(BranchExistsError):
            await git_wrapper_with_repo.checkout("main", create_branch=True)

    @pytest.mark.asyncio
    async def test_checkout_force(self, git_wrapper_with_repo, git_repo):
        """Test force checkout discards local changes."""
        # Create branch
        await git_wrapper_with_repo.create_branch("force-test", checkout=False)

        # Make local changes
        readme = Path(git_repo.working_dir) / "README.md"
        readme.write_text("local changes")

        # Force checkout should work
        result = await git_wrapper_with_repo.checkout("force-test", force=True)

        assert result.success is True


class TestGitWrapperCommitAdvanced:
    """Advanced tests for commit operations."""

    @pytest.mark.asyncio
    async def test_commit_specific_files(self, git_wrapper_with_repo, git_repo):
        """Test committing specific files only."""
        # Create multiple files
        file1 = Path(git_repo.working_dir) / "commit_specific1.txt"
        file2 = Path(git_repo.working_dir) / "commit_specific2.txt"
        file1.write_text("content 1")
        file2.write_text("content 2")

        result = await git_wrapper_with_repo.commit(
            "Commit specific file",
            files=["commit_specific1.txt"],
        )

        assert result.success is True
        assert result.files_changed == 1

    @pytest.mark.asyncio
    async def test_commit_with_partial_author(self, git_wrapper_with_repo, git_repo):
        """Test commit with only author name."""
        new_file = Path(git_repo.working_dir) / "partial_author.txt"
        new_file.write_text("content")

        result = await git_wrapper_with_repo.commit(
            "Partial author commit",
            author_name="Test Author",
        )

        assert result.success is True

    @pytest.mark.asyncio
    async def test_commit_allow_empty(self, git_wrapper_with_repo):
        """Test allowing empty commits."""
        result = await git_wrapper_with_repo.commit(
            "Empty commit allowed",
            allow_empty=True,
        )

        assert result.success is True


class TestGitWrapperUnstageAdvanced:
    """Advanced tests for unstaging operations."""

    @pytest.mark.asyncio
    async def test_unstage_specific_files(self, git_wrapper_with_repo, git_repo):
        """Test unstaging specific files."""
        # Create and stage files
        file1 = Path(git_repo.working_dir) / "unstage1.txt"
        file2 = Path(git_repo.working_dir) / "unstage2.txt"
        file1.write_text("content 1")
        file2.write_text("content 2")
        git_repo.index.add(["unstage1.txt", "unstage2.txt"])

        count = await git_wrapper_with_repo.unstage(["unstage1.txt"])

        assert count == 1


class TestGitWrapperResetAdvanced:
    """Advanced tests for reset operations."""

    @pytest.mark.asyncio
    async def test_reset_hard(self, git_wrapper_with_repo, git_repo):
        """Test hard reset."""
        # Create a commit
        file1 = Path(git_repo.working_dir) / "reset_hard.txt"
        file1.write_text("content")
        git_repo.index.add(["reset_hard.txt"])
        git_repo.index.commit("Commit for hard reset")

        result = await git_wrapper_with_repo.reset("HEAD~1", mode="hard")

        assert result is True
        # File should be gone after hard reset
        assert not file1.exists()

    @pytest.mark.asyncio
    async def test_reset_specific_files(self, git_wrapper_with_repo, git_repo):
        """Test resetting specific files."""
        # Create and stage a file
        file1 = Path(git_repo.working_dir) / "reset_file.txt"
        file1.write_text("content")
        git_repo.index.add(["reset_file.txt"])

        result = await git_wrapper_with_repo.reset("HEAD", files=["reset_file.txt"])

        assert result is True


class TestGitWrapperDiffAdvanced:
    """Advanced tests for diff operations."""

    @pytest.mark.asyncio
    async def test_diff_between_refs(self, git_wrapper_with_repo, git_repo):
        """Test diff between two refs."""
        # Create initial commit
        file1 = Path(git_repo.working_dir) / "diff_ref.txt"
        file1.write_text("initial")
        git_repo.index.add(["diff_ref.txt"])
        commit1 = git_repo.index.commit("First commit for diff")

        # Create second commit
        file1.write_text("modified")
        git_repo.index.add(["diff_ref.txt"])
        commit2 = git_repo.index.commit("Second commit for diff")

        result = await git_wrapper_with_repo.diff(
            base=commit1.hexsha,
            head=commit2.hexsha,
        )

        assert result.files_changed > 0

    @pytest.mark.asyncio
    async def test_diff_specific_files(self, git_wrapper_with_repo, git_repo):
        """Test diff for specific files only."""
        # Create files
        file1 = Path(git_repo.working_dir) / "diff_specific1.txt"
        file2 = Path(git_repo.working_dir) / "diff_specific2.txt"
        file1.write_text("content 1")
        file2.write_text("content 2")

        result = await git_wrapper_with_repo.diff(files=["diff_specific1.txt"])

        # Should only show changes for specified file
        for f in result.files:
            assert "diff_specific2.txt" not in f.get("path", "")

    @pytest.mark.asyncio
    async def test_diff_base_only(self, git_wrapper_with_repo, git_repo):
        """Test diff with base ref only (vs HEAD)."""
        # Create commit
        file1 = Path(git_repo.working_dir) / "diff_base.txt"
        file1.write_text("content")
        git_repo.index.add(["diff_base.txt"])
        commit = git_repo.index.commit("Commit for diff base test")

        # Get parent commit
        parent = commit.parents[0] if commit.parents else commit

        result = await git_wrapper_with_repo.diff(base=parent.hexsha)

        assert isinstance(result.files_changed, int)


class TestGitWrapperLogAdvanced:
    """Advanced tests for log operations."""

    @pytest.mark.asyncio
    async def test_log_with_ref(self, git_wrapper_with_repo, git_repo):
        """Test log starting from specific ref."""
        # Create branch with commits
        await git_wrapper_with_repo.create_branch("log-test", checkout=True)
        file1 = Path(git_repo.working_dir) / "log_ref.txt"
        file1.write_text("content")
        git_repo.index.add(["log_ref.txt"])
        git_repo.index.commit("Commit on log-test branch")

        result = await git_wrapper_with_repo.log(ref="log-test", limit=5)

        assert result.total_commits > 0

    @pytest.mark.asyncio
    async def test_log_with_path(self, git_wrapper_with_repo, git_repo):
        """Test log filtered by path."""
        # Create file and commit
        file1 = Path(git_repo.working_dir) / "log_path.txt"
        file1.write_text("content")
        git_repo.index.add(["log_path.txt"])
        git_repo.index.commit("Commit for path log")

        result = await git_wrapper_with_repo.log(path="log_path.txt")

        assert result.total_commits >= 1

    @pytest.mark.asyncio
    async def test_log_with_skip(self, git_wrapper_with_repo, git_repo):
        """Test log with skip parameter."""
        # Create multiple commits
        for i in range(3):
            file_path = Path(git_repo.working_dir) / f"skip_test{i}.txt"
            file_path.write_text(f"content {i}")
            git_repo.index.add([f"skip_test{i}.txt"])
            git_repo.index.commit(f"Skip test commit {i}")

        result = await git_wrapper_with_repo.log(skip=1, limit=2)

        # Should have skipped first commit
        assert len(result.commits) <= 2


class TestGitWrapperRemoteUrl:
    """Tests for remote URL operations."""

    @pytest.mark.asyncio
    async def test_get_remote_url_nonexistent(self, git_wrapper_with_repo):
        """Test getting URL for non-existent remote."""
        url = await git_wrapper_with_repo.get_remote_url("nonexistent")

        assert url is None


class TestGitWrapperConfig:
    """Tests for git config operations."""

    @pytest.mark.asyncio
    async def test_set_and_get_config(self, git_wrapper_with_repo):
        """Test setting and getting config value."""
        await git_wrapper_with_repo.set_config("test.key", "test_value")

        value = await git_wrapper_with_repo.get_config("test.key")

        assert value == "test_value"

    @pytest.mark.asyncio
    async def test_get_config_nonexistent(self, git_wrapper_with_repo):
        """Test getting non-existent config value."""
        value = await git_wrapper_with_repo.get_config("nonexistent.key")

        assert value is None


class TestGitWrapperClone:
    """Tests for clone operations."""

    @pytest.mark.asyncio
    async def test_clone_success(self, temp_workspace, test_settings):
        """Test successful clone."""
        wrapper = GitWrapper(temp_workspace, test_settings)

        # Mock the clone operation
        with patch("git_wrapper.GitRepo") as mock_repo_class:
            mock_repo = MagicMock()
            mock_repo.active_branch.name = "main"
            mock_repo.head.commit.hexsha = "abc123"
            mock_repo_class.clone_from.return_value = mock_repo

            result = await wrapper.clone("https://github.com/test/repo.git")

            assert result.success is True
            assert result.branch == "main"
            assert result.commit_sha == "abc123"

    @pytest.mark.asyncio
    async def test_clone_with_auth_token(self, temp_workspace, test_settings):
        """Test clone with auth token."""
        wrapper = GitWrapper(temp_workspace, test_settings)

        with patch("git_wrapper.GitRepo") as mock_repo_class:
            mock_repo = MagicMock()
            mock_repo.active_branch.name = "main"
            mock_repo.head.commit.hexsha = "abc123"
            mock_repo_class.clone_from.return_value = mock_repo

            result = await wrapper.clone(
                "https://github.com/test/repo.git",
                auth_token="test-token",
            )

            assert result.success is True
            # Verify token was injected in URL
            call_args = mock_repo_class.clone_from.call_args
            assert "test-token@" in call_args.kwargs["url"]

    @pytest.mark.asyncio
    async def test_clone_with_branch_and_depth(self, temp_workspace, test_settings):
        """Test clone with branch and depth parameters."""
        wrapper = GitWrapper(temp_workspace, test_settings)

        with patch("git_wrapper.GitRepo") as mock_repo_class:
            mock_repo = MagicMock()
            mock_repo.active_branch.name = "develop"
            mock_repo.head.commit.hexsha = "def456"
            mock_repo_class.clone_from.return_value = mock_repo

            result = await wrapper.clone(
                "https://github.com/test/repo.git",
                branch="develop",
                depth=1,
            )

            assert result.success is True
            call_args = mock_repo_class.clone_from.call_args
            assert call_args.kwargs["branch"] == "develop"
            assert call_args.kwargs["depth"] == 1

    @pytest.mark.asyncio
    async def test_clone_failure(self, temp_workspace, test_settings):
        """Test clone failure raises CloneError."""
        wrapper = GitWrapper(temp_workspace, test_settings)

        with patch("git_wrapper.GitRepo") as mock_repo_class:
            mock_repo_class.clone_from.side_effect = GitCommandError(
                "git clone", 128, stderr="Authentication failed"
            )

            with pytest.raises(CloneError):
                await wrapper.clone("https://github.com/test/repo.git")


class TestGitWrapperPush:
    """Tests for push operations."""

    @pytest.mark.asyncio
    async def test_push_force_disabled(self, git_wrapper_with_repo, git_repo):
        """Test force push is disabled by default."""
        git_repo.create_remote("origin", "https://github.com/test/repo.git")

        with pytest.raises(PushError, match="Force push is disabled"):
            await git_wrapper_with_repo.push(force=True)

    @pytest.mark.asyncio
    async def test_push_remote_not_found(self, git_wrapper_with_repo):
        """Test push to non-existent remote."""
        with pytest.raises(PushError, match="Remote not found"):
            await git_wrapper_with_repo.push(remote="nonexistent")


class TestGitWrapperPull:
    """Tests for pull operations."""

    @pytest.mark.asyncio
    async def test_pull_remote_not_found(self, git_wrapper_with_repo):
        """Test pull from non-existent remote."""
        with pytest.raises(PullError, match="Remote not found"):
            await git_wrapper_with_repo.pull(remote="nonexistent")


class TestGitWrapperFetch:
    """Tests for fetch operations."""

    @pytest.mark.asyncio
    async def test_fetch_remote_not_found(self, git_wrapper_with_repo):
        """Test fetch from non-existent remote."""
        with pytest.raises(GitError, match="Remote not found"):
            await git_wrapper_with_repo.fetch(remote="nonexistent")


class TestGitWrapperDiffHeadOnly:
    """Tests for diff with head ref only."""

    @pytest.mark.asyncio
    async def test_diff_head_only(self, git_wrapper_with_repo, git_repo):
        """Test diff with head ref only (working tree vs ref)."""
        # Make some changes
        readme = Path(git_repo.working_dir) / "README.md"
        readme.write_text("modified content")

        # This tests the head-only branch (base=None, head=specified)
        result = await git_wrapper_with_repo.diff(head="HEAD")

        assert isinstance(result.files_changed, int)


class TestGitWrapperRemoteWithUrl:
    """Tests for getting remote URL when remote exists."""

    @pytest.mark.asyncio
    async def test_get_remote_url_exists(self, git_wrapper_with_repo, git_repo):
        """Test getting URL for existing remote."""
        git_repo.create_remote("origin", "https://github.com/test/repo.git")

        url = await git_wrapper_with_repo.get_remote_url("origin")

        assert url == "https://github.com/test/repo.git"


class TestRunInExecutor:
    """Tests for run_in_executor utility."""

    @pytest.mark.asyncio
    async def test_run_in_executor(self):
        """Test running function in executor."""

        def blocking_func(x, y):
            return x + y

        result = await run_in_executor(blocking_func, 1, 2)

        assert result == 3
