"""Тесты для backend/app/workers/tasks/scrape_kn_catalog_objects.py.

Покрывают skip-today фильтр и force flag (admin "Загрузить все").
"""

from __future__ import annotations

from datetime import date
from typing import Any
from unittest.mock import MagicMock, patch


def _make_mock_db(rows: list[dict[str, Any]]) -> tuple[MagicMock, list[dict[str, Any]]]:
    """Mock DB session. Возвращает (db, captured_params)."""
    captured: list[dict[str, Any]] = []

    def execute_router(stmt: Any, params: Any = None) -> MagicMock:
        if params is not None:
            captured.append(params)
        result = MagicMock()
        result.mappings.return_value.all.return_value = rows
        return result

    db = MagicMock()
    db.execute = execute_router
    return db, captured


def test_skip_today_filter_active_by_default() -> None:
    """force=False (default) → :force = False передаётся в SQL → SQL пропустит
    объекты с catalog_scraped_at = сегодня (логика в WHERE)."""
    from app.workers.tasks import scrape_kn_catalog_objects as task_mod

    db, captured = _make_mock_db(rows=[])

    with patch.object(task_mod, "SessionLocal", return_value=db):
        result = task_mod.scrape_kn_catalog_objects.run(region_code=66)

    # SQL вызван 1 раз с force=False
    assert len(captured) == 1
    assert captured[0]["force"] is False
    assert captured[0]["region_code"] == 66
    # Пустой результат → ранний выход
    assert result["obj_ids_count"] == 0
    assert result["force"] is False


def test_force_true_passes_through_to_sql() -> None:
    """force=True → :force = True передаётся в SQL → SQL грузит все объекты."""
    from app.workers.tasks import scrape_kn_catalog_objects as task_mod

    db, captured = _make_mock_db(rows=[])

    with patch.object(task_mod, "SessionLocal", return_value=db):
        result = task_mod.scrape_kn_catalog_objects.run(region_code=66, force=True)

    assert len(captured) == 1
    assert captured[0]["force"] is True
    assert result["force"] is True


def test_max_objects_passed_as_limit() -> None:
    """max_objects явный — должен прилететь в SQL bind как :max_objects."""
    from app.workers.tasks import scrape_kn_catalog_objects as task_mod

    db, captured = _make_mock_db(rows=[])

    with patch.object(task_mod, "SessionLocal", return_value=db):
        task_mod.scrape_kn_catalog_objects.run(region_code=66, max_objects=10)

    assert captured[0]["max_objects"] == 10


def test_processes_rows_when_returned() -> None:
    """Если SELECT вернул строки — вызывается scrape_catalog_objects, snapshot_date
    из первой строки."""
    from app.workers.tasks import scrape_kn_catalog_objects as task_mod

    rows = [
        {"obj_id": 65136, "snapshot_date": date(2026, 5, 17)},
        {"obj_id": 65137, "snapshot_date": date(2026, 5, 17)},
    ]
    db, _ = _make_mock_db(rows=rows)

    mock_stats = {"processed": 2, "succeeded": 2, "failed": 0, "skipped": 0}
    with (
        patch.object(task_mod, "SessionLocal", return_value=db),
        patch(
            "app.services.scrapers.domrf_catalog_object.scrape_catalog_objects",
            return_value=mock_stats,
        ) as mock_scrape,
    ):
        result = task_mod.scrape_kn_catalog_objects.run(region_code=66)

    mock_scrape.assert_called_once()
    call_kwargs = mock_scrape.call_args[1]
    assert call_kwargs["obj_ids"] == [65136, 65137]
    assert call_kwargs["snapshot_date"] == date(2026, 5, 17)
    assert call_kwargs["region_code"] == 66
    assert result["obj_ids_count"] == 2
    assert result["snapshot_date"] == "2026-05-17"


def test_sql_contains_skip_today_predicate() -> None:
    """Smoke: финальный SQL содержит DATE(catalog_scraped_at) < CURRENT_DATE и
    CAST(:force AS boolean) — иначе фильтр не работает."""
    from app.workers.tasks.scrape_kn_catalog_objects import _SELECT_TARGETS_SQL

    sql = str(_SELECT_TARGETS_SQL).upper()
    assert "DATE(CATALOG_SCRAPED_AT) < CURRENT_DATE" in sql
    assert "CAST(:FORCE AS BOOLEAN)" in sql
    assert "CATALOG_SCRAPED_AT IS NULL" in sql
