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

Покрывает Fix H — per-quarter freshness skip в enqueue_cadastre_harvest.
"""

from __future__ import annotations

import datetime as _dt
from typing import Any
from unittest.mock import MagicMock, patch


def _make_mock_job(quarters: list[str], raw_targets_extra: dict[str, Any] | None = None) -> dict:
    """Mock cadastre_jobs row."""
    raw_targets = {"quarters": quarters}
    if raw_targets_extra:
        raw_targets.update(raw_targets_extra)
    return {
        "job_id": 99,
        "status": "pending",
        "raw_targets": raw_targets,
    }


def _make_mock_db(job: dict, fresh_quarters: list[str] | None = None) -> MagicMock:
    """Mock DB session.

    1. db.execute("SELECT * FROM cadastre_jobs ...").mappings().first() → job
    2. db.execute("SELECT cad_number FROM cad_quarter_stats ...").scalars().all() → fresh
    3. db.execute(UPDATE) → no-op
    """
    db = MagicMock()
    fresh_quarters = fresh_quarters or []

    def execute_router(stmt: Any, params: Any = None) -> MagicMock:
        sql = str(stmt).upper()
        result = MagicMock()
        if "FROM CADASTRE_JOBS" in sql and "WHERE JOB_ID" in sql and "SELECT *" in str(stmt):
            result.mappings.return_value.first.return_value = job
        elif "FROM CAD_QUARTER_STATS" in sql:
            result.scalars.return_value.all.return_value = fresh_quarters
        else:
            # UPDATE / other queries
            result.scalars.return_value.all.return_value = []
            result.mappings.return_value.first.return_value = None
        return result

    db.execute = execute_router
    return db


def test_enqueue_skips_fresh_quarters_with_default_threshold() -> None:
    """Fix H: фреш кварталы (cad_quarter_stats.fetched_at < 1h) пропускаются."""
    from app.workers.tasks import scrape_cadastre

    quarters = ["66:41:0001001", "66:41:0001002", "66:41:0001003"]
    fresh = ["66:41:0001001", "66:41:0001002"]  # 2 свежих, 1 stale

    job = _make_mock_job(quarters)
    db = _make_mock_db(job, fresh_quarters=fresh)

    with (
        patch.object(scrape_cadastre, "SessionLocal", return_value=db),
        patch.object(scrape_cadastre.bulk_harvest_quarter_task, "apply_async") as mock_apply,
    ):
        result = scrape_cadastre.enqueue_cadastre_harvest.run(job_id=99)

    # Только 1 stale quarter должен попасть в apply_async
    assert mock_apply.call_count == 1
    assert mock_apply.call_args[1]["args"] == ["66:41:0001003", 99]
    assert result["enqueued"] == 1
    assert result["skipped_fresh"] == 2


def test_enqueue_no_skip_when_skip_fresh_hours_zero() -> None:
    """skip_fresh_hours=0 → отключает freshness check, все quarters enqueued."""
    from app.workers.tasks import scrape_cadastre

    quarters = ["66:41:0001001", "66:41:0001002"]

    job = _make_mock_job(quarters, raw_targets_extra={"skip_fresh_hours": 0})
    db = _make_mock_db(job, fresh_quarters=["66:41:0001001"])  # один свежий

    with (
        patch.object(scrape_cadastre, "SessionLocal", return_value=db),
        patch.object(scrape_cadastre.bulk_harvest_quarter_task, "apply_async") as mock_apply,
    ):
        result = scrape_cadastre.enqueue_cadastre_harvest.run(job_id=99)

    # Все 2 quarters должны быть enqueued (skip отключен)
    assert mock_apply.call_count == 2
    assert result["enqueued"] == 2
    assert result["skipped_fresh"] == 0


def test_enqueue_freshness_threshold_passed_in_sql() -> None:
    """skip_fresh_hours=2 → threshold = NOW() - 2h в SQL query."""
    from app.workers.tasks import scrape_cadastre

    quarters = ["66:41:0001001"]
    captured_params: list[dict[str, Any]] = []

    job = _make_mock_job(quarters, raw_targets_extra={"skip_fresh_hours": 2})

    def execute_router(stmt: Any, params: Any = None) -> MagicMock:
        sql = str(stmt).upper()
        result = MagicMock()
        if "FROM CADASTRE_JOBS" in sql and "SELECT *" in str(stmt):
            result.mappings.return_value.first.return_value = job
        elif "FROM CAD_QUARTER_STATS" in sql:
            if params:
                captured_params.append(params)
            result.scalars.return_value.all.return_value = []
        else:
            result.scalars.return_value.all.return_value = []
        return result

    db = MagicMock()
    db.execute = execute_router

    with (
        patch.object(scrape_cadastre, "SessionLocal", return_value=db),
        patch.object(scrape_cadastre.bulk_harvest_quarter_task, "apply_async"),
    ):
        scrape_cadastre.enqueue_cadastre_harvest.run(job_id=99)

    assert len(captured_params) == 1
    threshold: _dt.datetime = captured_params[0]["t"]
    now = _dt.datetime.now(_dt.UTC)
    # Threshold должен быть приблизительно 2h назад (±5s tolerance)
    delta = now - threshold
    assert _dt.timedelta(hours=1, minutes=59) < delta < _dt.timedelta(hours=2, minutes=1)
