# ========= 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.cache.index.ram import RAMIndexCacheStore
from mirage.commands.builtin.slack.grep import grep
from mirage.commands.builtin.slack.rg import rg
from mirage.types import PathSpec
from mirage.utils.key_prefix import mount_key


def _concrete_paths(n: int = 7):
    return [
        PathSpec(
            resource_path=mount_key(
                f"/slack/channels/general__C1/2026-01-{d:02d}/chat.jsonl",
                "/slack"),
            virtual=(
                f"/slack/channels/general__C1/2026-01-{d:02d}/chat.jsonl"),
            directory=(
                f"/slack/channels/general__C1/2026-01-{d:02d}/chat.jsonl"),
        ) for d in range(1, n + 1)
    ]


@pytest.mark.asyncio
async def test_grep_with_many_concrete_paths_uses_native_search():
    accessor = AsyncMock()
    accessor.config = AsyncMock()
    fake_payload = (b'{"messages":{"matches":[{"channel":{"name":"general",'
                    b'"id":"C1"},"ts":"1700000000.0","text":"hello there"}]}}')
    with patch(
            "mirage.commands.builtin.slack.grep.search_messages",
            new=AsyncMock(return_value=fake_payload),
    ) as fake_search:
        out, io = await grep(accessor,
                             _concrete_paths(7),
                             "hello",
                             index=RAMIndexCacheStore(),
                             i=True)
    assert fake_search.await_count == 1
    assert io.exit_code == 0
    assert b"hello there" in out


@pytest.mark.asyncio
async def test_rg_with_many_concrete_paths_uses_native_search():
    accessor = AsyncMock()
    accessor.config = AsyncMock()
    fake_payload = (b'{"messages":{"matches":[{"channel":{"name":"general",'
                    b'"id":"C1"},"ts":"1700000000.0","text":"hello rg"}]}}')
    with patch(
            "mirage.commands.builtin.slack.rg.search_messages",
            new=AsyncMock(return_value=fake_payload),
    ) as fake_search:
        out, io = await rg(accessor,
                           _concrete_paths(7),
                           "hello",
                           index=RAMIndexCacheStore(),
                           i=True)
    assert fake_search.await_count == 1
    assert io.exit_code == 0
    assert b"hello rg" in out


@pytest.mark.asyncio
async def test_grep_falls_back_when_native_search_raises():
    accessor = AsyncMock()
    accessor.config = AsyncMock()
    paths = [
        PathSpec(resource_path=mount_key("/slack/channels/general__C1/*.jsonl",
                                         "/slack"),
                 virtual="/slack/channels/general__C1/*.jsonl",
                 directory="/slack/channels/general__C1/",
                 pattern="*.jsonl"),
    ]
    resolved = [
        PathSpec(resource_path=mount_key(
            "/slack/channels/general__C1/2026-04-10/chat.jsonl", "/slack"),
                 virtual="/slack/channels/general__C1/2026-04-10/chat.jsonl",
                 directory="/slack/channels/general__C1/2026-04-10/"),
    ]
    with patch(
            "mirage.commands.builtin.slack.grep.search_messages",
            new=AsyncMock(
                side_effect=RuntimeError("missing search:read scope")),
    ), patch(
            "mirage.commands.builtin.slack.grep.resolve_glob",
            new=AsyncMock(return_value=resolved),
    ), patch(
            "mirage.commands.builtin.slack.grep.slack_read",
            new=AsyncMock(return_value=b""),
    ):
        out, io = await grep(accessor,
                             paths,
                             "hello",
                             index=RAMIndexCacheStore(),
                             i=True)
    assert io.exit_code in (0, 1)


@pytest.mark.asyncio
async def test_grep_native_empty_does_not_trigger_fallback():
    accessor = AsyncMock()
    accessor.config = AsyncMock()
    empty_payload = b'{"messages":{"matches":[]}}'
    with patch(
            "mirage.commands.builtin.slack.grep.search_messages",
            new=AsyncMock(return_value=empty_payload),
    ) as fake_search, patch(
            "mirage.commands.builtin.slack.grep.slack_read",
            new=AsyncMock(return_value=b""),
    ) as fake_read:
        out, io = await grep(accessor,
                             _concrete_paths(7),
                             "missing",
                             index=RAMIndexCacheStore())
    assert fake_search.await_count == 1
    assert fake_read.await_count == 0
    assert io.exit_code == 1
    assert out == b""
