# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========

from unittest.mock import AsyncMock, patch

import pytest

from mirage.core.github.config import GitHubConfig
from mirage.core.github.search import SearchResult, narrow_paths, search_code
from mirage.types import PathSpec
from mirage.utils.key_prefix import mount_key


@pytest.fixture
def config():
    return GitHubConfig(token="ghp_test")


@pytest.mark.asyncio
@patch("mirage.core.github.search.github_get", new_callable=AsyncMock)
async def test_search_code_basic(mock_get, config):
    mock_get.return_value = {
        "items": [
            {
                "path": "src/main.py",
                "sha": "aaa"
            },
            {
                "path": "src/utils.py",
                "sha": "bbb"
            },
        ]
    }
    result = await search_code(config, "acme", "proj", "import os")
    assert len(result) == 2
    assert result[0] == SearchResult(path="src/main.py", sha="aaa")
    mock_get.assert_awaited_once_with(config.token,
                                      "/search/code",
                                      params={"q": "import os repo:acme/proj"},
                                      base_url=None)


@pytest.mark.asyncio
@patch("mirage.core.github.search.github_get", new_callable=AsyncMock)
async def test_search_code_empty_results(mock_get, config):
    mock_get.return_value = {"items": []}
    result = await search_code(config, "acme", "proj", "nonexistent")
    assert result == []


@pytest.mark.asyncio
@patch("mirage.core.github.search.github_get", new_callable=AsyncMock)
async def test_search_code_with_path_filter(mock_get, config):
    mock_get.return_value = {"items": [{"path": "src/main.py", "sha": "aaa"}]}
    await search_code(config, "acme", "proj", "import os", path_filter="src/")
    mock_get.assert_awaited_once_with(
        config.token,
        "/search/code",
        params={"q": "import os repo:acme/proj path:src/"},
        base_url=None)


@pytest.mark.asyncio
@patch("mirage.core.github.search.search_code", new_callable=AsyncMock)
async def test_narrow_paths_strips_leading_slash_in_filter(
        mock_search, config):
    mock_search.return_value = [SearchResult(path="src/main.py", sha="aaa")]
    paths = [PathSpec(resource_path="src", virtual="/src", directory="/src")]
    await narrow_paths(config, "acme", "proj", "import", paths)
    _, kwargs = mock_search.await_args
    assert kwargs["path_filter"] == "src"


@pytest.mark.asyncio
@patch("mirage.core.github.search.search_code", new_callable=AsyncMock)
async def test_narrow_paths_root_uses_no_filter(mock_search, config):
    mock_search.return_value = [SearchResult(path="src/main.py", sha="aaa")]
    paths = [PathSpec(resource_path="", virtual="/", directory="/")]
    await narrow_paths(config, "acme", "proj", "import", paths)
    _, kwargs = mock_search.await_args
    assert kwargs["path_filter"] is None


@pytest.mark.asyncio
@patch("mirage.core.github.search.search_code", new_callable=AsyncMock)
async def test_narrow_paths_normalizes_results_with_leading_slash(
        mock_search, config):
    mock_search.return_value = [
        SearchResult(path="src/main.py", sha="aaa"),
        SearchResult(path="src/utils.py", sha="bbb"),
    ]
    paths = [
        PathSpec(resource_path=mount_key("/gh", "/gh"),
                 virtual="/gh",
                 directory="/gh")
    ]
    out = await narrow_paths(config, "acme", "proj", "import", paths)
    assert [p.virtual for p in out] == ["/gh/src/main.py", "/gh/src/utils.py"]
    assert [p.resource_path for p in out] == ["src/main.py", "src/utils.py"]


@pytest.mark.asyncio
@patch("mirage.core.github.search.search_code", new_callable=AsyncMock)
async def test_narrow_paths_logs_and_continues_on_error(mock_search, config):
    mock_search.side_effect = RuntimeError("boom")
    paths = [PathSpec(resource_path="src", virtual="/src", directory="/src")]
    out = await narrow_paths(config, "acme", "proj", "import", paths)
    assert out == []
