"""Tests for glreview.git module."""

from __future__ import annotations

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

import pytest

from glreview.git import (
    GitError,
    count_lines,
    get_changed_files,
    get_current_commit,
    get_full_commit,
    get_gitlab_host,
    get_gitlab_project,
    get_remote_url,
    has_changes_since,
    run_git,
)


class TestRunGit:
    """Tests for run_git function."""

    def test_runs_command(self, temp_git_repo):
        """Runs git command successfully."""
        result = run_git("status", cwd=temp_git_repo)
        assert result.returncode == 0

    def test_captures_output(self, temp_git_repo):
        """Captures stdout."""
        result = run_git("rev-parse", "--short", "HEAD", cwd=temp_git_repo)
        assert result.stdout.strip()  # Should have commit SHA

    def test_raises_on_failure(self, temp_git_repo):
        """Raises GitError on failure when check=True."""
        with pytest.raises(GitError, match="failed"):
            run_git("nonexistent-command", cwd=temp_git_repo)

    def test_no_raise_with_check_false(self, temp_git_repo):
        """Does not raise when check=False."""
        result = run_git("nonexistent-command", cwd=temp_git_repo, check=False)
        assert result.returncode != 0


class TestGetCurrentCommit:
    """Tests for get_current_commit function."""

    def test_returns_short_sha(self, temp_git_repo):
        """Returns short SHA by default."""
        commit = get_current_commit(cwd=temp_git_repo)
        assert len(commit) == 7  # Short SHA

    def test_returns_full_sha(self, temp_git_repo):
        """Returns full SHA when short=False."""
        commit = get_current_commit(cwd=temp_git_repo, short=False)
        assert len(commit) == 40  # Full SHA


class TestGetFullCommit:
    """Tests for get_full_commit function."""

    def test_expands_short_sha(self, temp_git_repo):
        """Expands short SHA to full."""
        short = get_current_commit(cwd=temp_git_repo, short=True)
        full = get_full_commit(short, cwd=temp_git_repo)
        assert len(full) == 40
        assert full.startswith(short)

    def test_returns_none_for_invalid(self, temp_git_repo):
        """Returns None for invalid SHA."""
        result = get_full_commit("invalidsha123", cwd=temp_git_repo)
        assert result is None


class TestHasChangesSince:
    """Tests for has_changes_since function."""

    def test_no_changes(self, temp_git_repo):
        """Returns False when file unchanged."""
        # README.md was created in initial commit
        commit = get_current_commit(cwd=temp_git_repo)
        changed = has_changes_since("README.md", commit, cwd=temp_git_repo)
        assert changed is False

    def test_has_changes(self, temp_git_repo):
        """Returns True when file changed."""
        # Get initial commit
        initial = get_current_commit(cwd=temp_git_repo)

        # Modify file and commit
        readme = temp_git_repo / "README.md"
        readme.write_text("# Updated\n")
        subprocess.run(["git", "add", "."], cwd=temp_git_repo, capture_output=True)
        subprocess.run(
            ["git", "commit", "-m", "Update"],
            cwd=temp_git_repo,
            capture_output=True,
        )

        # Check for changes
        changed = has_changes_since("README.md", initial, cwd=temp_git_repo)
        assert changed is True

    def test_none_commit_returns_true(self, temp_git_repo):
        """Returns True when commit is None."""
        changed = has_changes_since("README.md", None, cwd=temp_git_repo)
        assert changed is True

    def test_invalid_commit_returns_true(self, temp_git_repo):
        """Returns True when commit is invalid."""
        changed = has_changes_since("README.md", "invalidsha", cwd=temp_git_repo)
        assert changed is True


class TestGetChangedFiles:
    """Tests for get_changed_files function."""

    def test_returns_changed_files(self, temp_git_repo):
        """Returns list of changed files."""
        # Create and commit a new file
        new_file = temp_git_repo / "new_file.py"
        new_file.write_text("# new")
        subprocess.run(["git", "add", "."], cwd=temp_git_repo, capture_output=True)
        subprocess.run(
            ["git", "commit", "-m", "Add new file"],
            cwd=temp_git_repo,
            capture_output=True,
        )

        files = get_changed_files(base="HEAD~1", head="HEAD", cwd=temp_git_repo)
        assert "new_file.py" in files

    def test_empty_on_no_changes(self, temp_git_repo):
        """Returns empty list when no changes."""
        files = get_changed_files(base="HEAD", head="HEAD", cwd=temp_git_repo)
        assert files == []

    def test_returns_empty_on_git_error(self, temp_git_repo):
        """Returns empty list when git diff fails."""
        # Use an invalid base ref that doesn't exist
        files = get_changed_files(base="invalid_ref", head="HEAD", cwd=temp_git_repo)
        assert files == []


class TestGetRemoteUrl:
    """Tests for get_remote_url function."""

    def test_returns_url(self, temp_git_repo):
        """Returns remote URL when configured."""
        subprocess.run(
            ["git", "remote", "add", "origin", "git@gitlab.com:user/project.git"],
            cwd=temp_git_repo,
            capture_output=True,
        )
        url = get_remote_url(cwd=temp_git_repo)
        assert url == "git@gitlab.com:user/project.git"

    def test_returns_none_without_remote(self, temp_git_repo):
        """Returns None when no remote."""
        url = get_remote_url(cwd=temp_git_repo)
        assert url is None


class TestGetGitlabProject:
    """Tests for get_gitlab_project function."""

    def test_ssh_url(self, temp_git_repo):
        """Extracts project from SSH URL."""
        subprocess.run(
            ["git", "remote", "add", "origin", "git@gitlab.com:user/project.git"],
            cwd=temp_git_repo,
            capture_output=True,
        )
        project = get_gitlab_project(cwd=temp_git_repo)
        assert project == "user/project"

    def test_https_url(self, temp_git_repo):
        """Extracts project from HTTPS URL."""
        subprocess.run(
            ["git", "remote", "add", "origin", "https://gitlab.com/user/project.git"],
            cwd=temp_git_repo,
            capture_output=True,
        )
        project = get_gitlab_project(cwd=temp_git_repo)
        assert project == "user/project"

    def test_nested_project(self, temp_git_repo):
        """Handles nested project paths."""
        subprocess.run(
            [
                "git",
                "remote",
                "add",
                "origin",
                "git@git.ligo.org:org/subgroup/project.git",
            ],
            cwd=temp_git_repo,
            capture_output=True,
        )
        project = get_gitlab_project(cwd=temp_git_repo)
        # This implementation only gets last two path components
        assert "project" in project

    def test_returns_none_without_remote(self, temp_git_repo):
        """Returns None when no remote."""
        project = get_gitlab_project(cwd=temp_git_repo)
        assert project is None

    def test_unknown_url_format(self, temp_git_repo):
        """Returns None for unrecognized URL format."""
        # Use a path-style URL that doesn't match git@ or :// patterns
        subprocess.run(
            ["git", "remote", "add", "origin", "/local/path/repo.git"],
            cwd=temp_git_repo,
            capture_output=True,
        )
        project = get_gitlab_project(cwd=temp_git_repo)
        assert project is None


class TestGetGitlabHost:
    """Tests for get_gitlab_host function."""

    def test_ssh_url(self, temp_git_repo):
        """Extracts host from SSH URL."""
        subprocess.run(
            ["git", "remote", "add", "origin", "git@gitlab.com:user/project.git"],
            cwd=temp_git_repo,
            capture_output=True,
        )
        host = get_gitlab_host(cwd=temp_git_repo)
        assert host == "gitlab.com"

    def test_https_url(self, temp_git_repo):
        """Extracts host from HTTPS URL."""
        subprocess.run(
            ["git", "remote", "add", "origin", "https://git.ligo.org/user/project.git"],
            cwd=temp_git_repo,
            capture_output=True,
        )
        host = get_gitlab_host(cwd=temp_git_repo)
        assert host == "git.ligo.org"

    def test_returns_none_without_remote(self, temp_git_repo):
        """Returns None when no remote."""
        host = get_gitlab_host(cwd=temp_git_repo)
        assert host is None

    def test_unknown_url_format(self, temp_git_repo):
        """Returns None for unrecognized URL format."""
        # Use a path-style URL that doesn't match git@ or :// patterns
        subprocess.run(
            ["git", "remote", "add", "origin", "/local/path/repo.git"],
            cwd=temp_git_repo,
            capture_output=True,
        )
        host = get_gitlab_host(cwd=temp_git_repo)
        assert host is None


class TestCountLines:
    """Tests for count_lines function."""

    def test_counts_lines(self, temp_dir):
        """Counts lines in file."""
        f = temp_dir / "test.py"
        f.write_text("line1\nline2\nline3\n")
        count = count_lines("test.py", cwd=temp_dir)
        assert count == 3

    def test_counts_lines_no_trailing_newline(self, temp_dir):
        """Counts lines without trailing newline."""
        f = temp_dir / "test.py"
        f.write_text("line1\nline2")
        count = count_lines("test.py", cwd=temp_dir)
        assert count == 2

    def test_empty_file(self, temp_dir):
        """Empty file has 0 lines."""
        f = temp_dir / "empty.py"
        f.write_text("")
        count = count_lines("empty.py", cwd=temp_dir)
        assert count == 0

    def test_nonexistent_file(self, temp_dir):
        """Nonexistent file returns 0."""
        count = count_lines("nonexistent.py", cwd=temp_dir)
        assert count == 0
