# ========= 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 datetime import datetime, timezone

import pytest

from mirage.cache.index import IndexCacheStore, IndexEntry
from mirage.cache.index.ram import RAMIndexCacheStore
from mirage.core.github.find import find
from mirage.types import PathSpec
from mirage.utils.key_prefix import mount_key


def _index() -> RAMIndexCacheStore:
    index = RAMIndexCacheStore()
    index._entries.update({
        "/src":
        IndexEntry(id="a", name="src", resource_type="folder", size=None),
        "/src/main.py":
        IndexEntry(id="b", name="main.py", resource_type="file", size=120),
        "/src/utils":
        IndexEntry(id="c", name="utils", resource_type="folder", size=None),
        "/src/utils/helpers.py":
        IndexEntry(id="d", name="helpers.py", resource_type="file", size=80),
        "/README.md":
        IndexEntry(id="e", name="README.md", resource_type="file", size=50),
    })
    return index


class EntryOnlyIndex(IndexCacheStore):

    async def entries(self) -> dict[str, IndexEntry]:
        return await _index().entries()


def _spec(path: str, prefix: str = "") -> PathSpec:
    return PathSpec(resource_path=mount_key(path, prefix),
                    virtual=path,
                    directory=path)


@pytest.mark.asyncio
async def test_find_all_from_root():
    results = await find(None, _spec("/"), index=_index())
    assert results == [
        "/", "/README.md", "/src", "/src/main.py", "/src/utils",
        "/src/utils/helpers.py"
    ]


@pytest.mark.asyncio
async def test_find_uses_store_interface_not_ram_implementation():
    results = await find(None, _spec("/"), index=EntryOnlyIndex())
    assert "/src/main.py" in results


@pytest.mark.asyncio
async def test_find_name_pattern():
    results = await find(None, _spec("/"), name="*.py", index=_index())
    assert results == ["/src/main.py", "/src/utils/helpers.py"]


@pytest.mark.asyncio
async def test_find_type_directory():
    results = await find(None, _spec("/src"), type="d", index=_index())
    assert results == ["/src", "/src/utils"]


@pytest.mark.asyncio
async def test_find_type_file_under_subdir():
    results = await find(None, _spec("/src"), type="f", index=_index())
    assert results == ["/src/main.py", "/src/utils/helpers.py"]


@pytest.mark.asyncio
async def test_find_maxdepth():
    results = await find(None, _spec("/src"), maxdepth=1, index=_index())
    assert results == ["/src", "/src/main.py", "/src/utils"]


@pytest.mark.asyncio
async def test_find_mindepth():
    results = await find(None, _spec("/src"), mindepth=2, index=_index())
    assert results == ["/src/utils/helpers.py"]


@pytest.mark.asyncio
async def test_find_strips_mount_prefix():
    results = await find(None,
                         _spec("/github/src", prefix="/github"),
                         type="f",
                         index=_index())
    assert results == ["/src/main.py", "/src/utils/helpers.py"]


@pytest.mark.asyncio
async def test_find_size_filters():
    # Directories contribute size 0 to -size, so the root is excluded
    # under a positive minimum (#318).
    results = await find(None, _spec("/"), min_size=100, index=_index())
    assert results == ["/src/main.py"]


@pytest.mark.asyncio
async def test_find_file_start_path():
    results = await find(None, _spec("/src/main.py"), index=_index())
    assert results == ["/src/main.py"]


@pytest.mark.asyncio
async def test_find_size_filters_file_start():
    too_big = await find(None,
                         _spec("/src/main.py"),
                         max_size=50,
                         index=_index())
    assert too_big == []
    big_enough = await find(None,
                            _spec("/src/main.py"),
                            min_size=100,
                            index=_index())
    assert big_enough == ["/src/main.py"]


@pytest.mark.asyncio
async def test_find_mtime_filters_unknown_and_out_of_window_entries():
    index = _index()
    index._entries["/src/main.py"] = index._entries["/src/main.py"].model_copy(
        update={"remote_time": "2026-07-15T12:00:00+00:00"})

    results = await find(
        None,
        _spec("/"),
        mtime_min=datetime(2026, 7, 15, tzinfo=timezone.utc).timestamp(),
        mtime_max=datetime(2026, 7, 16, tzinfo=timezone.utc).timestamp(),
        index=index,
    )

    assert results == ["/src/main.py"]


@pytest.mark.asyncio
async def test_find_empty_matches_empty_files_and_directories():
    index = _index()
    index._entries["/empty.txt"] = IndexEntry(id="empty-file",
                                              name="empty.txt",
                                              resource_type="file",
                                              size=0)
    index._entries["/empty-dir"] = IndexEntry(id="empty-dir",
                                              name="empty-dir",
                                              resource_type="folder")

    results = await find(None, _spec("/"), empty=True, index=index)

    assert results == ["/empty-dir", "/empty.txt"]
