"""Offline smoke for compute_next_run_at (app/services/scheduler.py).

#2397 Part C removed the legacy scheduler_loop + trigger_*/get_due_schedules/_claim_run/
reap_zombies/_spawn_tracked/_drain_inflight machinery from app.services.scheduler — the kit
scheduler (scraper_kit.orchestration.scheduler, covered by test_scraper_kit_scheduler_parity.py)
is now the only scheduling path. compute_next_run_at survives as a shared helper (still used
by admin.py for the operator-facing "next run" preview), so this file now covers only it.
"""

import os

os.environ.setdefault("DATABASE_URL", "postgresql+psycopg://test:test@localhost:5432/test")

from datetime import UTC, datetime

from app.services.scheduler import compute_next_run_at


def test_compute_next_run_in_normal_window() -> None:
    now = datetime(2026, 5, 23, 12, 0, tzinfo=UTC)
    next_at = compute_next_run_at(2, 5, now=now)
    assert next_at.date() == datetime(2026, 5, 24).date()
    assert 2 <= next_at.hour <= 4  # window [2, 5) UTC


def test_compute_next_run_cross_midnight() -> None:
    now = datetime(2026, 5, 23, 12, 0, tzinfo=UTC)
    # Window 22..3 = 22:00-23:59 + 00:00-02:59
    next_at = compute_next_run_at(22, 3, now=now)
    # Должен попасть либо в [22,23] либо в [0,3) range
    hour = next_at.hour
    assert hour >= 22 or hour < 3


def test_compute_next_run_window_1_hour() -> None:
    now = datetime(2026, 5, 23, 12, 0, tzinfo=UTC)
    # Минимальное окно [3, 4) → ровно 3-й час
    next_at = compute_next_run_at(3, 4, now=now)
    assert next_at.hour == 3


def test_compute_next_run_is_future() -> None:
    """next_run_at всегда в будущем (завтра) — не в прошлом."""
    now = datetime(2026, 5, 23, 3, 30, tzinfo=UTC)
    next_at = compute_next_run_at(2, 5, now=now)
    assert next_at > now


def test_compute_next_run_timezone_aware() -> None:
    """Результат всегда timezone-aware (UTC)."""
    now = datetime(2026, 5, 23, 12, 0, tzinfo=UTC)
    next_at = compute_next_run_at(2, 5, now=now)
    assert next_at.tzinfo is not None


# ── per-source interval_days cadence (weekly exhaustive avito) ──────────────


def test_compute_next_run_default_interval_is_daily() -> None:
    """Без interval_days → +1 сутки (back-compat, как раньше)."""
    now = datetime(2026, 5, 23, 12, 0, tzinfo=UTC)
    next_at = compute_next_run_at(13, 15, now=now)
    assert next_at.date() == datetime(2026, 5, 24).date()
    assert 13 <= next_at.hour <= 14  # window [13, 15) UTC


def test_compute_next_run_interval_days_1_explicit() -> None:
    """interval_days=1 явно → +1 сутки (идентично default)."""
    now = datetime(2026, 5, 23, 12, 0, tzinfo=UTC)
    next_at = compute_next_run_at(13, 15, now=now, interval_days=1)
    assert next_at.date() == datetime(2026, 5, 24).date()
    assert 13 <= next_at.hour <= 14


def test_compute_next_run_interval_days_7_weekly() -> None:
    """interval_days=7 → ~7 суток вперёд, всё ещё в окне."""
    now = datetime(2026, 5, 23, 12, 0, tzinfo=UTC)
    next_at = compute_next_run_at(13, 15, now=now, interval_days=7)
    assert next_at.date() == datetime(2026, 5, 30).date()  # 23 + 7
    assert 13 <= next_at.hour <= 14  # всё ещё внутри окна [13, 15) UTC
    assert next_at > now


def test_compute_next_run_interval_days_cross_midnight_weekly() -> None:
    """interval_days с cross-midnight окном → целевой день +N, час в окне."""
    now = datetime(2026, 5, 23, 12, 0, tzinfo=UTC)
    next_at = compute_next_run_at(22, 3, now=now, interval_days=7)
    assert next_at > now
    assert next_at.hour >= 22 or next_at.hour < 3
