import pytest
import time
from datetime import datetime
from unittest.mock import patch, MagicMock
from pathlib import Path
from runtime.scheduler import schedule_task, list_schedules, run_due_schedules
from runtime.integrations.scheduler import parse_cron, cron_matches_now, ScheduleStore

def test_parse_cron_valid():
    result = parse_cron("*/5 * * * *")
    assert result["minute"] == "*/5"
    assert result["hour"] == "*"
    assert result["dom"] == "*"
    assert result["month"] == "*"
    assert result["dow"] == "*"


def test_parse_cron_invalid():
    with pytest.raises(ValueError):
        parse_cron("* *")



def test_schedule_task(tmp_path):
    data_dir = tmp_path
    sched_id = schedule_task(data_dir, "test prompt", interval_minutes=1)
    
    assert sched_id is not None
    schedules = list_schedules(data_dir)
    assert len(schedules) == 1
    assert schedules[0]["id"] == sched_id
    assert schedules[0]["prompt"] == "test prompt"
    assert schedules[0]["interval_minutes"] == 1
    assert schedules[0]["last_run"] == 0

def test_run_due_schedules(tmp_path):
    data_dir = tmp_path
    schedule_task(data_dir, "test prompt", interval_minutes=0) # due immediately
    
    callback = MagicMock()
    executed = run_due_schedules(data_dir, callback)
    
    assert len(executed) == 1
    assert executed[0]["prompt"] == "test prompt"
    assert callback.called
    
    schedules = list_schedules(data_dir)
    assert schedules[0]["last_run"] > 0
    
    # Should not run again immediately since interval_minutes > 0 in logic?
    # Actually interval is 0, so it will always be due if interval_secs=0.
    # Let's test with interval=10
    
    schedule_task(data_dir, "test 2", interval_minutes=10)
    # The first one might run again if time passed, but let's mock time.
    
    with patch("time.time", return_value=0):
        schedule_task(data_dir, "test 3", interval_minutes=10)
        
    with patch("time.time", return_value=10):
        # 10 seconds is not 10 minutes.
        callback2 = MagicMock()
        executed2 = run_due_schedules(data_dir, callback2)
        # only the 0 interval one should run
        assert all(e["prompt"] != "test 3" for e in executed2)
        
    with patch("time.time", return_value=601):
        # 601 seconds > 10 minutes (600)
        callback3 = MagicMock()
        executed3 = run_due_schedules(data_dir, callback3)
        assert any(e["prompt"] == "test 3" for e in executed3)


def test_cron_matches_now():
    cron_dict = parse_cron("30 14 * * *")
    assert cron_matches_now(cron_dict, datetime(2026, 1, 1, 14, 30)) is True
    assert cron_matches_now(cron_dict, datetime(2026, 1, 1, 14, 31)) is False


def test_schedule_store_create_and_list(tmp_path):
    with patch("runtime.integrations.scheduler.Path.home", return_value=tmp_path):
        store = ScheduleStore()
        sid = store.create(
            name="nightly report",
            cron_expr="0 2 * * *",
            chain_template="summarize logs",
            enabled=True,
        )

        assert sid is not None
        schedules = store.list_all()
        assert len(schedules) == 1

        s = schedules[0]
        assert s["id"] == sid
        assert s["name"] == "nightly report"
        assert s["cron_expr"] == "0 2 * * *"
        assert s["chain_template"] == "summarize logs"
        assert s["enabled"] is True
        assert s["last_run"] is None
        assert s["next_run"] is not None
