"""FC-3d: tick_due_sources task tests.

Verifies (a) due sources are queued, (b) sources with a pending/running
DownloadEvent are skipped, (c) the tick is read-only on Source itself
(no last_checked_at write), and (d) the result dict shape.
"""
import pytest
from sqlalchemy import select

import backend.app.tasks.scan  # noqa: F401 — side-effect: register celery task
from backend.app.celery_app import celery
from backend.app.models import Artist, DownloadEvent, Source

pytestmark = pytest.mark.integration


class _FakeTask:
    """Stand-in for the real Celery Task object — only `.delay()` matters."""

    def __init__(self, sink: list):
        self._sink = sink

    def delay(self, source_id: int):
        self._sink.append(source_id)


@pytest.mark.asyncio
async def test_tick_returns_counts_dict_when_no_due_sources(db, monkeypatch):
    """Conftest TRUNCATEs after every integration test, so we start clean."""
    delayed: list[int] = []
    monkeypatch.setattr(
        "backend.app.tasks.download.download_source", _FakeTask(delayed),
    )
    from backend.app.tasks.scan import _tick_due_sources_async

    result = await _tick_due_sources_async()
    assert set(result.keys()) == {"due", "queued", "skipped_in_flight", "tick_at"}
    assert result["due"] == 0
    assert result["queued"] == 0
    assert result["skipped_in_flight"] == 0
    assert delayed == []


@pytest.mark.asyncio
async def test_tick_queues_due_source(db, monkeypatch):
    artist = Artist(name="tick-q", slug="tick-q", auto_check=True)
    db.add(artist)
    await db.flush()
    src = Source(
        artist_id=artist.id, platform="patreon", url="https://tick-q",
        enabled=True, consecutive_failures=0,
    )
    db.add(src)
    await db.commit()

    delayed: list[int] = []
    monkeypatch.setattr(
        "backend.app.tasks.download.download_source", _FakeTask(delayed),
    )
    from backend.app.tasks.scan import _tick_due_sources_async
    result = await _tick_due_sources_async()
    assert result["queued"] >= 1
    assert src.id in delayed

    pending_count = (await db.execute(
        select(DownloadEvent.id).where(
            DownloadEvent.source_id == src.id,
            DownloadEvent.status == "pending",
        )
    )).scalars().all()
    assert len(pending_count) == 1


@pytest.mark.asyncio
async def test_tick_skips_in_flight_source(db, monkeypatch):
    artist = Artist(name="tick-inf", slug="tick-inf", auto_check=True)
    db.add(artist)
    await db.flush()
    src = Source(
        artist_id=artist.id, platform="patreon", url="https://tick-inf",
        enabled=True, consecutive_failures=0,
    )
    db.add(src)
    await db.flush()
    db.add(DownloadEvent(source_id=src.id, status="running"))
    await db.commit()

    delayed: list[int] = []
    monkeypatch.setattr(
        "backend.app.tasks.download.download_source", _FakeTask(delayed),
    )
    from backend.app.tasks.scan import _tick_due_sources_async
    result = await _tick_due_sources_async()
    assert src.id not in delayed
    assert result["skipped_in_flight"] >= 1


@pytest.mark.asyncio
async def test_tick_does_not_modify_source_last_checked_at(db, monkeypatch):
    """The tick creates DownloadEvent(pending) but never writes Source columns.
    Source.last_checked_at is owned by DownloadService.finalize.
    """
    artist = Artist(name="tick-ro", slug="tick-ro", auto_check=True)
    db.add(artist)
    await db.flush()
    src = Source(
        artist_id=artist.id, platform="patreon", url="https://tick-ro",
        enabled=True, consecutive_failures=0,
    )
    db.add(src)
    await db.commit()
    src_id = src.id

    before = (await db.execute(
        select(Source.last_checked_at).where(Source.id == src_id)
    )).scalar_one()

    monkeypatch.setattr(
        "backend.app.tasks.download.download_source", _FakeTask([]),
    )
    from backend.app.tasks.scan import _tick_due_sources_async
    await _tick_due_sources_async()

    after = (await db.execute(
        select(Source.last_checked_at).where(Source.id == src_id)
    )).scalar_one()
    assert before == after  # still None / unchanged


def test_tick_task_is_registered():
    """Side-effect import at the top of this test file forces task module load."""
    assert "backend.app.tasks.scan.tick_due_sources" in celery.tasks
