from __future__ import annotations

from pathlib import Path

import pytest
from mjr_am_backend import deps


@pytest.mark.asyncio
async def test_scan_flow_incremental_and_remove(tmp_path: Path, monkeypatch):
    db_path = tmp_path / "scan.sqlite"
    root = tmp_path / "scan_root"
    root.mkdir()
    file_one = root / "one.png"
    file_two = root / "two.png"
    file_one.write_bytes(b"x")
    file_two.write_bytes(b"y")

    monkeypatch.setattr(deps, "WATCHER_ENABLED", False)
    services_res = await deps.build_services(str(db_path))
    assert services_res.ok, services_res.error
    services = services_res.data
    assert isinstance(services, dict)

    index = services["index"]
    db = services["db"]
    sync_worker = services.get("rating_tags_sync")

    try:
        first = await index.scan_directory(
            directory=str(root),
            recursive=False,
            incremental=False,
            source="output",
            root_id=None,
        )
        assert first.ok, first.error

        searched = await index.search_scoped("*", roots=[str(root)], limit=50, offset=0, filters=None, include_total=True)
        assert searched.ok, searched.error
        before_assets = searched.data.get("assets") or []
        assert len(before_assets) >= 2

        # Remove one file physically then remove the indexed row using the exact stored filepath.
        file_two.unlink()
        two_rows = [a for a in before_assets if str(a.get("filename") or "") == "two.png"]
        assert two_rows
        rem = await index.remove_file(str(two_rows[0].get("filepath") or ""))
        assert rem.ok, rem.error

        second = await index.scan_directory(
            directory=str(root),
            recursive=False,
            incremental=True,
            source="output",
            root_id=None,
        )
        assert second.ok, second.error

        after = await index.search_scoped("*", roots=[str(root)], limit=50, offset=0, filters=None, include_total=True)
        assert after.ok, after.error
        after_assets = after.data.get("assets") or []
        after_names = {str(a.get("filename") or "") for a in after_assets}
        assert "one.png" in after_names
        assert "two.png" not in after_names
    finally:
        if sync_worker is not None:
            try:
                sync_worker.stop()
            except Exception:
                pass
        await db.aclose()


@pytest.mark.asyncio
async def test_scan_prunes_files_deleted_outside_majoor(tmp_path: Path, monkeypatch):
    db_path = tmp_path / "scan.sqlite"
    root = tmp_path / "scan_root"
    root.mkdir()
    file_one = root / "one.png"
    file_two = root / "two.png"
    file_one.write_bytes(b"x")
    file_two.write_bytes(b"y")

    monkeypatch.setattr(deps, "WATCHER_ENABLED", False)
    services_res = await deps.build_services(str(db_path))
    assert services_res.ok, services_res.error
    services = services_res.data
    assert isinstance(services, dict)

    index = services["index"]
    db = services["db"]
    sync_worker = services.get("rating_tags_sync")

    try:
        first = await index.scan_directory(
            directory=str(root),
            recursive=False,
            incremental=False,
            source="output",
            root_id=None,
        )
        assert first.ok, first.error

        file_two.unlink()

        second = await index.scan_directory(
            directory=str(root),
            recursive=False,
            incremental=True,
            source="output",
            root_id=None,
        )
        assert second.ok, second.error
        assert second.data.get("pruned", 0) >= 1

        after = await index.search_scoped("*", roots=[str(root)], limit=50, offset=0, filters=None, include_total=True)
        assert after.ok, after.error
        after_names = {str(a.get("filename") or "") for a in after.data.get("assets") or []}
        assert "one.png" in after_names
        assert "two.png" not in after_names
    finally:
        if sync_worker is not None:
            try:
                sync_worker.stop()
            except Exception:
                pass
        await db.aclose()
