"""历史查询应用服务的分页、筛选和 DTO 边界测试。"""

from types import SimpleNamespace
from unittest.mock import AsyncMock

import pytest

from app.application.history import HistoryQueryService
from app.schemas.history import DownloadHistory, TransferHistory


def _make_service(
    *,
    downloads: AsyncMock | None = None,
    transfers: AsyncMock | None = None,
) -> tuple[HistoryQueryService, AsyncMock, AsyncMock]:
    """构造使用可观察异步仓储的历史查询服务。"""
    download_repository = downloads or AsyncMock()
    transfer_repository = transfers or AsyncMock()
    return (
        HistoryQueryService(
            download_repository=download_repository,
            transfer_repository=transfer_repository,
        ),
        download_repository,
        transfer_repository,
    )


@pytest.mark.asyncio
async def test_list_download_returns_schema_dtos() -> None:
    """下载历史查询不得把仓储对象原样泄漏给 API。"""
    service, download_repository, _ = _make_service()
    raw_record = SimpleNamespace(id=7, title="Movie")
    download_repository.async_list_by_page.return_value = [raw_record]

    records = await service.list_download(page=2, count=10)

    assert records == [DownloadHistory(id=7, title="Movie")]
    assert records[0] is not raw_record
    download_repository.async_list_by_page.assert_awaited_once_with(2, 10)


@pytest.mark.asyncio
async def test_list_transfer_maps_failed_alias_to_status_filter() -> None:
    """“失败”快捷条件应继续映射为无标题的失败状态查询。"""
    service, _, transfer_repository = _make_service()
    transfer_repository.async_list_by_page.return_value = [
        SimpleNamespace(id=3, status=False)
    ]
    transfer_repository.async_count.return_value = 1

    page = await service.list_transfer(title="失败", page=3, count=5)

    assert page.list == [TransferHistory(id=3, status=False)]
    assert page.total == 1
    transfer_repository.async_list_by_page.assert_awaited_once_with(
        page=3,
        count=5,
        status=False,
    )
    transfer_repository.async_count.assert_awaited_once_with(status=False)


@pytest.mark.asyncio
async def test_list_transfer_preserves_glob_escaping() -> None:
    """glob 查询应转义 SQL 通配符并显式启用 wildcard 模式。"""
    service, _, transfer_repository = _make_service()
    transfer_repository.async_list_by_title.return_value = []
    transfer_repository.async_count_by_title.return_value = 0

    page = await service.list_transfer(
        title=r"show_100%*.mkv",
        page=1,
        count=30,
        status=True,
    )

    assert page.total == 0
    pattern = r"show\_100\%%.mkv"
    transfer_repository.async_count_by_title.assert_awaited_once_with(
        pattern,
        status=True,
        wildcard=True,
    )
    transfer_repository.async_list_by_title.assert_awaited_once_with(
        pattern,
        page=1,
        count=30,
        status=True,
        wildcard=True,
    )


@pytest.mark.asyncio
async def test_get_transfers_preserves_order_and_reports_missing_ids() -> None:
    """批量 AI 重做准备应保持去重后的输入顺序并报告缺失记录。"""
    service, _, transfer_repository = _make_service()
    transfer_repository.async_get.side_effect = [
        SimpleNamespace(id=11),
        None,
        SimpleNamespace(id=13),
    ]

    records, missing_ids = await service.get_transfers([11, 12, 13])

    assert [record.id for record in records] == [11, 13]
    assert missing_ids == [12]
    assert transfer_repository.async_get.await_args_list == [
        ((11,), {}),
        ((12,), {}),
        ((13,), {}),
    ]
