"""Tests for git operations: clone_repo and validation."""

from __future__ import annotations

from unittest.mock import AsyncMock, patch

import pytest

from github_app.git import _validate_clone_args, clone_repo

PATCH_TARGET = "github_app.git.asyncio.create_subprocess_exec"


# ---------------------------------------------------------------------------
# _validate_clone_args
# ---------------------------------------------------------------------------


def test_validate_clone_args_valid(tmp_path):
    _validate_clone_args("owner", "repo", tmp_path)


def test_validate_clone_args_owner_slash(tmp_path):
    with pytest.raises(ValueError, match="owner"):
        _validate_clone_args("bad/owner", "repo", tmp_path)


def test_validate_clone_args_owner_dotdot(tmp_path):
    with pytest.raises(ValueError, match="owner"):
        _validate_clone_args("..evil", "repo", tmp_path)


def test_validate_clone_args_repo_slash(tmp_path):
    with pytest.raises(ValueError, match="repo"):
        _validate_clone_args("owner", "bad/repo", tmp_path)


def test_validate_clone_args_repo_dotdot(tmp_path):
    with pytest.raises(ValueError, match="repo"):
        _validate_clone_args("owner", "repo..", tmp_path)


# ---------------------------------------------------------------------------
# clone_repo -- success
# ---------------------------------------------------------------------------


async def test_clone_repo_success(tmp_path):
    mock_proc = AsyncMock()
    mock_proc.communicate.return_value = (b"", b"")
    mock_proc.returncode = 0

    with patch(PATCH_TARGET, return_value=mock_proc):
        result = await clone_repo(
            "owner",
            "repo",
            "main",
            "tok",
            tmp_path,
        )

    assert result.parent == tmp_path
    assert result.name.startswith("owner_repo_")


async def test_clone_repo_unique_dirs(tmp_path):
    """Two concurrent clones get different directories."""
    mock_proc = AsyncMock()
    mock_proc.communicate.return_value = (b"", b"")
    mock_proc.returncode = 0

    with patch(PATCH_TARGET, return_value=mock_proc):
        r1 = await clone_repo("o", "r", "main", "tok", tmp_path)
        r2 = await clone_repo("o", "r", "main", "tok", tmp_path)

    assert r1 != r2


# ---------------------------------------------------------------------------
# clone_repo -- error path (generic message, no token leak)
# ---------------------------------------------------------------------------


async def test_clone_repo_failure_no_token_leak(tmp_path):
    """Stderr containing a token must NOT appear in the exception."""
    mock_proc = AsyncMock()
    mock_proc.communicate.return_value = (
        b"",
        b"fatal: https://x-access-token:ghp_SECRET@github.com/o/r.git",
    )
    mock_proc.returncode = 128

    with patch(PATCH_TARGET, return_value=mock_proc):
        with pytest.raises(RuntimeError, match="git clone failed") as exc_info:
            await clone_repo("owner", "repo", "main", "tok", tmp_path)

    # The exception message must NOT contain the token.
    assert "ghp_SECRET" not in str(exc_info.value)
    assert "x-access-token" not in str(exc_info.value)


# ---------------------------------------------------------------------------
# clone_repo -- path traversal
# ---------------------------------------------------------------------------


async def test_clone_repo_rejects_slash_in_owner(tmp_path):
    with pytest.raises(ValueError, match="owner"):
        await clone_repo("../evil", "repo", "main", "tok", tmp_path)


async def test_clone_repo_rejects_dotdot_in_repo(tmp_path):
    with pytest.raises(ValueError, match="repo"):
        await clone_repo("owner", "..repo", "main", "tok", tmp_path)
