using ClaudeDo.Data.Models;
using ClaudeDo.Worker.Prime;

namespace ClaudeDo.Worker.Tests.Prime;

public class NextDueCalculatorTests
{
    private static PrimeScheduleDto Schedule(
        PrimeDays days, TimeSpan time,
        bool enabled = true, DateTimeOffset? lastRun = null) =>
        new(Guid.NewGuid(), (int)days, time, enabled, lastRun, null, PrimeActionKind.FillMyDay);

    [Fact]
    public void Disabled_Schedule_Returns_Null()
    {
        var now = new DateTimeOffset(2026, 5, 5, 6, 0, 0, TimeSpan.FromHours(2));
        var s = Schedule(PrimeDays.All, new(7, 0, 0), enabled: false);
        Assert.Null(NextDueCalculator.Compute(new[] { s }, now, TimeSpan.FromMinutes(30)));
    }

    [Fact]
    public void No_Days_Selected_Returns_Null()
    {
        var now = new DateTimeOffset(2026, 5, 5, 6, 0, 0, TimeSpan.FromHours(2));
        var s = Schedule(PrimeDays.None, new(7, 0, 0));
        Assert.Null(NextDueCalculator.Compute(new[] { s }, now, TimeSpan.FromMinutes(30)));
    }

    [Fact]
    public void Future_Same_Day_Returns_Today_At_Target()
    {
        var now = new DateTimeOffset(2026, 5, 5, 6, 0, 0, TimeSpan.FromHours(2)); // Tue
        var s = Schedule(PrimeDays.All, new(7, 0, 0));
        var r = NextDueCalculator.Compute(new[] { s }, now, TimeSpan.FromMinutes(30));
        Assert.NotNull(r);
        Assert.Equal(new DateTimeOffset(2026, 5, 5, 7, 0, 0, now.Offset), r!.At);
        Assert.False(r.FireImmediately);
    }

    [Fact]
    public void Within_CatchUp_Window_Fires_Immediately()
    {
        var now = new DateTimeOffset(2026, 5, 5, 7, 15, 0, TimeSpan.FromHours(2));
        var s = Schedule(PrimeDays.All, new(7, 0, 0));
        var r = NextDueCalculator.Compute(new[] { s }, now, TimeSpan.FromMinutes(30));
        Assert.NotNull(r);
        Assert.True(r!.FireImmediately);
    }

    [Fact]
    public void Past_CatchUp_Window_Skips_To_Next_Eligible_Day()
    {
        var now = new DateTimeOffset(2026, 5, 5, 9, 0, 0, TimeSpan.FromHours(2)); // Tue
        var s = Schedule(PrimeDays.All, new(7, 0, 0));
        var r = NextDueCalculator.Compute(new[] { s }, now, TimeSpan.FromMinutes(30));
        Assert.NotNull(r);
        Assert.Equal(new DateOnly(2026, 5, 6), DateOnly.FromDateTime(r!.At.LocalDateTime));
    }

    [Fact]
    public void Weekdays_Only_Skips_Weekend()
    {
        var now = new DateTimeOffset(2026, 5, 8, 8, 0, 0, TimeSpan.FromHours(2)); // Fri, past catch-up
        var s = Schedule(PrimeDays.Weekdays, new(7, 0, 0));
        var r = NextDueCalculator.Compute(new[] { s }, now, TimeSpan.FromMinutes(30));
        Assert.NotNull(r);
        Assert.Equal(DayOfWeek.Monday, r!.At.LocalDateTime.DayOfWeek);
        Assert.Equal(new DateOnly(2026, 5, 11), DateOnly.FromDateTime(r.At.LocalDateTime));
    }

    [Fact]
    public void Single_Day_Schedule_Targets_That_Weekday()
    {
        var now = new DateTimeOffset(2026, 5, 5, 8, 0, 0, TimeSpan.FromHours(2)); // Tue, past catch-up
        var s = Schedule(PrimeDays.Friday, new(7, 0, 0));
        var r = NextDueCalculator.Compute(new[] { s }, now, TimeSpan.FromMinutes(30));
        Assert.NotNull(r);
        Assert.Equal(DayOfWeek.Friday, r!.At.LocalDateTime.DayOfWeek);
        Assert.Equal(new DateOnly(2026, 5, 8), DateOnly.FromDateTime(r.At.LocalDateTime));
    }

    [Fact]
    public void Already_Fired_Today_Skips_To_Next_Eligible_Day()
    {
        var now = new DateTimeOffset(2026, 5, 5, 6, 0, 0, TimeSpan.FromHours(2));
        var lastRun = new DateTimeOffset(2026, 5, 5, 7, 1, 0, TimeSpan.FromHours(2));
        var s = Schedule(PrimeDays.All, new(7, 0, 0), lastRun: lastRun);
        var r = NextDueCalculator.Compute(new[] { s }, now, TimeSpan.FromMinutes(30));
        Assert.NotNull(r);
        Assert.Equal(new DateOnly(2026, 5, 6), DateOnly.FromDateTime(r!.At.LocalDateTime));
    }

    [Fact]
    public void Multiple_Schedules_Returns_Earliest()
    {
        var now = new DateTimeOffset(2026, 5, 5, 6, 0, 0, TimeSpan.FromHours(2));
        var early = Schedule(PrimeDays.All, new(7, 0, 0));
        var late = Schedule(PrimeDays.All, new(9, 0, 0));
        var r = NextDueCalculator.Compute(new[] { late, early }, now, TimeSpan.FromMinutes(30));
        Assert.NotNull(r);
        Assert.Equal(early.Id, r!.Schedule.Id);
    }
}
