"""Unit tests for the pure campaign scheduling decision (no I/O)."""

from agenteval.evaluation.campaign_scheduler import (
    clock_offset,
    decide_schedule,
)
from agenteval.models import CampaignPlanEntry


def _plan() -> list[CampaignPlanEntry]:
    return [
        CampaignPlanEntry(scenario_id="s-1", offset_seconds=0, count=2),
        CampaignPlanEntry(scenario_id="s-2", offset_seconds=3600, count=1),
        CampaignPlanEntry(scenario_id="s-3", offset_seconds=3600, count=3),
    ]


# ── clock mapping ──────────────────────────────────────────────────────────

def test_clock_offset_real_wall_clock():
    assert clock_offset(elapsed_seconds=120.0, time_scale=1.0) == 120.0


def test_clock_offset_compressed():
    # 1 real second maps to 3600 window seconds at 3600x.
    assert clock_offset(elapsed_seconds=1.0, time_scale=3600.0) == 3600.0


# ── due / not-due matrix ─────────────────────────────────────────────────────

def test_nothing_due_before_first_offset():
    # Only offset-0 entries exist at t<0? Use a plan whose earliest offset is 10.
    plan = [CampaignPlanEntry(scenario_id="s-1", offset_seconds=10, count=1)]
    decision = decide_schedule(plan=plan, window_seconds=100, clock_offset_seconds=5)
    assert decision.due == ()
    assert decision.finished is False


def test_offset_zero_entry_due_at_start():
    decision = decide_schedule(plan=_plan(), window_seconds=7200, clock_offset_seconds=0)
    assert [d.index for d in decision.due] == [0]
    assert decision.due[0].entry.count == 2


def test_multiple_entries_due_at_same_offset():
    decision = decide_schedule(plan=_plan(), window_seconds=7200, clock_offset_seconds=3600)
    # entry 0 (offset 0) plus entries 1 and 2 (offset 3600) are all due.
    assert [d.index for d in decision.due] == [0, 1, 2]


def test_spawned_entries_excluded_idempotent():
    decision = decide_schedule(
        plan=_plan(), window_seconds=7200, clock_offset_seconds=3600,
        spawned_indices=[0, 1],
    )
    assert [d.index for d in decision.due] == [2]


def test_all_spawned_yields_no_due():
    decision = decide_schedule(
        plan=_plan(), window_seconds=7200, clock_offset_seconds=99999,
        spawned_indices=[0, 1, 2],
    )
    assert decision.due == ()


# ── window end ───────────────────────────────────────────────────────────────

def test_not_finished_inside_window():
    decision = decide_schedule(plan=_plan(), window_seconds=7200, clock_offset_seconds=7199)
    assert decision.finished is False


def test_finished_at_window_end():
    decision = decide_schedule(plan=_plan(), window_seconds=7200, clock_offset_seconds=7200)
    assert decision.finished is True
