"""Unit tests for git mount resolution with multi-mapping support."""

import os
import tempfile
from unittest.mock import MagicMock, patch

import pytest

from src.api.tool_instances import (
    _clone_git_repo,
    _expand_glob_source,
    _normalize_git_mount,
    _resolve_git_mount_mappings,
    _resolve_single_git_mount,
)


class TestNormalizeGitMount:
    """Tests for _normalize_git_mount."""

    def test_legacy_to_mappings(self) -> None:
        """Legacy source_path + target_path becomes mappings array."""
        entry = {
            "remote_url": "https://github.com/user/repo.git",
            "source_path": "packages/api",
            "target_path": "/app/api",
            "branch": "main",
        }
        result = _normalize_git_mount(entry)
        assert "mappings" in result
        assert result["mappings"] == [
            {"source_path": "packages/api", "target_path": "/app/api"}
        ]
        assert "source_path" not in result
        assert "target_path" not in result
        assert result["remote_url"] == "https://github.com/user/repo.git"
        assert result["branch"] == "main"

    def test_already_mappings(self) -> None:
        """Entry already with mappings is left unchanged."""
        entry = {
            "remote_url": "https://github.com/user/repo.git",
            "branch": "main",
            "mappings": [
                {"source_path": "a", "target_path": "/a"},
                {"source_path": "b", "target_path": "/b"},
            ],
        }
        result = _normalize_git_mount(entry)
        assert result["mappings"] == [
            {"source_path": "a", "target_path": "/a"},
            {"source_path": "b", "target_path": "/b"},
        ]
        assert "source_path" not in result
        assert "target_path" not in result

    def test_missing_target_path_no_mappings(self) -> None:
        """Entry with source_path but no target_path creates empty mappings."""
        entry = {
            "remote_url": "https://github.com/user/repo.git",
            "source_path": "src",
        }
        result = _normalize_git_mount(entry)
        assert "mappings" not in result


class TestResolveGitMountMappings:
    """Tests for _resolve_git_mount_mappings."""

    def test_single_mapping(self) -> None:
        """A single mapping produces one volume mount."""
        with tempfile.TemporaryDirectory() as repo_path:
            os.makedirs(os.path.join(repo_path, "packages", "api"))
            mappings = [
                {"source_path": "packages/api", "target_path": "/app/api"},
            ]
            result = _resolve_git_mount_mappings(repo_path, mappings, None)
            assert len(result) == 1
            assert result[0]["source"] == os.path.join(repo_path, "packages", "api")
            assert result[0]["target"] == "/app/api"
            assert result[0]["type"] == "bind"

    def test_multiple_mappings(self) -> None:
        """Multiple mappings from same repo produce multiple mounts."""
        with tempfile.TemporaryDirectory() as repo_path:
            os.makedirs(os.path.join(repo_path, "packages", "api"))
            os.makedirs(os.path.join(repo_path, "packages", "web"))
            mappings = [
                {"source_path": "packages/api", "target_path": "/app/api"},
                {"source_path": "packages/web", "target_path": "/app/web"},
            ]
            result = _resolve_git_mount_mappings(repo_path, mappings, None)
            assert len(result) == 2
            targets = {r["target"] for r in result}
            assert targets == {"/app/api", "/app/web"}

    def test_relative_target_path(self) -> None:
        """Relative target_path is resolved against working_directory."""
        with tempfile.TemporaryDirectory() as repo_path:
            os.makedirs(os.path.join(repo_path, "src"))
            mappings = [
                {"source_path": "src", "target_path": "code"},
            ]
            result = _resolve_git_mount_mappings(repo_path, mappings, "/workspace")
            assert len(result) == 1
            assert result[0]["target"] == "/workspace/code"

    def test_glob_expansion(self) -> None:
        """Glob patterns in source_path are expanded."""
        with tempfile.TemporaryDirectory() as repo_path:
            os.makedirs(os.path.join(repo_path, "packages", "api"))
            os.makedirs(os.path.join(repo_path, "packages", "web"))
            mappings = [
                {"source_path": "packages/*", "target_path": "/app/packages"},
            ]
            result = _resolve_git_mount_mappings(repo_path, mappings, None)
            assert len(result) == 2
            targets = {r["target"] for r in result}
            assert targets == {
                os.path.join("/app/packages", "packages", "api"),
                os.path.join("/app/packages", "packages", "web"),
            }

    def test_missing_target_path_skipped(self) -> None:
        """Mapping without target_path is skipped."""
        with tempfile.TemporaryDirectory() as repo_path:
            mappings = [
                {"source_path": "src"},
            ]
            result = _resolve_git_mount_mappings(repo_path, mappings, None)
            assert len(result) == 0

    def test_no_working_directory_for_relative_target(self) -> None:
        """Relative target without working_directory is skipped."""
        with tempfile.TemporaryDirectory() as repo_path:
            os.makedirs(os.path.join(repo_path, "src"))
            mappings = [
                {"source_path": "src", "target_path": "code"},
            ]
            result = _resolve_git_mount_mappings(repo_path, mappings, None)
            assert len(result) == 0


class TestResolveSingleGitMount:
    """Tests for _resolve_single_git_mount."""

    @pytest.mark.asyncio
    async def test_missing_remote_url(self) -> None:
        """Git mount without remote_url returns empty list."""
        result = await _resolve_single_git_mount(
            MagicMock(),
            {"mappings": [{"source_path": ".", "target_path": "/app"}]},
            "/tmp",
            None,
        )
        assert result == []

    @pytest.mark.asyncio
    async def test_missing_instance_dir(self) -> None:
        """Git mount without instance_dir returns empty list."""
        result = await _resolve_single_git_mount(
            MagicMock(),
            {
                "remote_url": "https://github.com/user/repo.git",
                "mappings": [{"source_path": ".", "target_path": "/app"}],
            },
            None,
            None,
        )
        assert result == []

    @pytest.mark.asyncio
    async def test_legacy_format_normalized(self) -> None:
        """Legacy format is normalized and resolved."""
        with tempfile.TemporaryDirectory() as instance_dir:
            with patch(
                "src.api.tool_instances._clone_git_repo",
                return_value=os.path.join(instance_dir, "repo-clone"),
            ):
                os.makedirs(os.path.join(instance_dir, "repo-clone", "src"))
                result = await _resolve_single_git_mount(
                    MagicMock(),
                    {
                        "remote_url": "https://github.com/user/repo.git",
                        "source_path": "src",
                        "target_path": "/app/src",
                    },
                    instance_dir,
                    None,
                )
                assert len(result) == 1
                assert result[0]["target"] == "/app/src"


class TestExpandGlobSource:
    """Tests for _expand_glob_source."""

    def test_no_glob(self) -> None:
        """Non-glob path returns single item if exists."""
        with tempfile.TemporaryDirectory() as tmp:
            path = os.path.join(tmp, "file.txt")
            open(path, "w").close()
            result = _expand_glob_source(path, tmp)
            assert result == [path]

    def test_no_glob_missing(self) -> None:
        """Non-glob path that doesn't exist returns empty list."""
        with tempfile.TemporaryDirectory() as tmp:
            path = os.path.join(tmp, "missing.txt")
            result = _expand_glob_source(path, tmp)
            assert result == []

    def test_glob_pattern(self) -> None:
        """Glob pattern expands to matched paths."""
        with tempfile.TemporaryDirectory() as tmp:
            open(os.path.join(tmp, "a.txt"), "w").close()
            open(os.path.join(tmp, "b.txt"), "w").close()
            result = _expand_glob_source(os.path.join(tmp, "*.txt"), tmp)
            assert len(result) == 2
