from goodnews.db import connect, init_db
from goodnews.feeds import due_source_rows


def _src(c, sid, *, interval=120, failures=0, active=1):
    c.execute(
        "INSERT INTO sources (id, name, feed_url, poll_interval_minutes, consecutive_failures, active) "
        "VALUES (?, ?, ?, ?, ?, ?)",
        (sid, f"S{sid}", f"http://s/{sid}", interval, failures, active),
    )


def _attempt(c, sid, *, minutes_ago, status="ok"):
    c.execute(
        "INSERT INTO ingest_runs (source_id, finished_at, status) "
        "VALUES (?, datetime('now', ?), ?)",
        (sid, f"-{minutes_ago} minutes", status),
    )


def _due_ids(c):
    return {r["id"] for r in due_source_rows(c)}


def test_never_attempted_is_due(tmp_path):
    c = connect(str(tmp_path / "t.db")); init_db(c)
    _src(c, 1)
    assert _due_ids(c) == {1}


def test_recent_attempt_not_due_old_attempt_due(tmp_path):
    c = connect(str(tmp_path / "t.db")); init_db(c)
    _src(c, 1, interval=120); _attempt(c, 1, minutes_ago=5)     # polled recently
    _src(c, 2, interval=120); _attempt(c, 2, minutes_ago=200)   # overdue
    assert _due_ids(c) == {2}


def test_failing_source_backs_off(tmp_path):
    # The regression: a perpetually-FAILING source (no 'ok' run) used to be due
    # every cycle. With backoff it rests — effective wait = 60*(1+10)=660 min.
    c = connect(str(tmp_path / "t.db")); init_db(c)
    _src(c, 1, interval=60, failures=10)
    _attempt(c, 1, minutes_ago=200, status="failed")  # 200 < 660 → not due yet
    assert _due_ids(c) == set()
    # ...but once past the backed-off window it becomes due again.
    c.execute("DELETE FROM ingest_runs WHERE source_id = 1")
    _attempt(c, 1, minutes_ago=700, status="failed")  # 700 > 660 → due
    assert _due_ids(c) == {1}


def test_backoff_capped_at_a_day(tmp_path):
    # A long failure streak can't push the wait past MAX_BACKOFF_MINUTES (1440).
    c = connect(str(tmp_path / "t.db")); init_db(c)
    _src(c, 1, interval=120, failures=999)             # 120*1000 would be huge
    _attempt(c, 1, minutes_ago=1500, status="failed")  # >1440 → due despite streak
    assert _due_ids(c) == {1}


def test_inactive_never_due(tmp_path):
    c = connect(str(tmp_path / "t.db")); init_db(c)
    _src(c, 1, active=0)
    assert _due_ids(c) == set()
