# ========= 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. =========

import errno
from unittest.mock import AsyncMock, patch

import pytest

from mirage.accessor.dropbox import DropboxAccessor
from mirage.core.dropbox.client import DropboxTokenManager
from mirage.core.dropbox.rmdir import rmdir
from mirage.resource.dropbox.config import DropboxConfig
from mirage.types import PathSpec
from tests.core.dropbox.conftest import FakeDropboxRpc

FOLDER = {".tag": "folder", "name": "docs"}
FILE = {".tag": "file", "name": "a.txt", "size": 1}


def make_accessor() -> DropboxAccessor:
    config = DropboxConfig(client_id="c", client_secret="s", refresh_token="r")
    return DropboxAccessor(config, DropboxTokenManager(config))


@pytest.mark.asyncio
async def test_rmdir_nonempty_raises_enotempty_without_deleting():
    with patch("mirage.core.dropbox.rmdir.get_metadata",
               new_callable=AsyncMock,
               return_value=FOLDER):
        with patch("mirage.core.dropbox.rmdir.list_folder",
                   new_callable=AsyncMock,
                   return_value=[FILE]):
            with patch("mirage.core.dropbox.rmdir.delete_path",
                       new_callable=AsyncMock) as deleted:
                with pytest.raises(OSError) as excinfo:
                    await rmdir(make_accessor(),
                                PathSpec.from_str_path("/docs"))
    assert excinfo.value.errno == errno.ENOTEMPTY
    deleted.assert_not_awaited()


@pytest.mark.asyncio
async def test_rmdir_removes_empty_folder():
    with patch("mirage.core.dropbox.rmdir.get_metadata",
               new_callable=AsyncMock,
               return_value=FOLDER):
        with patch("mirage.core.dropbox.rmdir.list_folder",
                   new_callable=AsyncMock,
                   return_value=[]):
            with patch("mirage.core.dropbox.rmdir.delete_path",
                       new_callable=AsyncMock) as deleted:
                await rmdir(make_accessor(), PathSpec.from_str_path("/docs"))
    assert deleted.await_args.args[1] == "/docs"


@pytest.mark.asyncio
async def test_rmdir_file_raises_enotdir():
    with patch("mirage.core.dropbox.rmdir.get_metadata",
               new_callable=AsyncMock,
               return_value=FILE):
        with pytest.raises(NotADirectoryError):
            await rmdir(make_accessor(), PathSpec.from_str_path("/a.txt"))


@pytest.mark.asyncio
async def test_rmdir_probe_is_bounded_to_one_entry(dropbox_accessor):
    # The emptiness probe is bounded, not a full listing: `list_folder`
    # follows every continuation cursor, so asking it with a small page
    # size made one request per child to answer a yes/no.
    rpc = FakeDropboxRpc(entries=[FILE, FILE, FILE], metadata=FOLDER)
    with patch("mirage.core.dropbox.api.dropbox_rpc", new=rpc):
        with pytest.raises(OSError) as excinfo:
            await rmdir(dropbox_accessor, PathSpec.from_str_path("/docs"))
    assert excinfo.value.errno == errno.ENOTEMPTY
    assert rpc.list_limits == [1]
    assert rpc.list_requests == 1
    assert rpc.deleted == []
