namespace KArtSell.Modules.ModelOperations.Domain;

public sealed class ScheduleOccurrencePlanner
{
    public sealed record CatchUpPlan(
        IReadOnlyList<DateTimeOffset> OccurrencesToDispatch,
        DateTimeOffset NextDueAt);

    public static DateTimeOffset GetNextDueAt(
        DateTimeOffset scheduledFor,
        string cadence,
        string catchUpPolicy,
        int maxCatchUp,
        DateTimeOffset now)
        => Plan(scheduledFor, cadence, catchUpPolicy, maxCatchUp, now).NextDueAt;

    public static CatchUpPlan Plan(
        DateTimeOffset scheduledFor,
        string cadence,
        string catchUpPolicy,
        int maxCatchUp,
        DateTimeOffset now)
    {
        if (maxCatchUp is < 0 or > 31) throw new ArgumentOutOfRangeException(nameof(maxCatchUp));

        var due = GetDueOccurrences(scheduledFor, cadence, now);
        var nextDueAt = due.Count == 0 ? scheduledFor : Advance(due[^1], cadence);

        if (catchUpPolicy.Equals("LATEST_ONLY", StringComparison.OrdinalIgnoreCase))
            return new CatchUpPlan(due.Count == 0 ? [scheduledFor] : [due[^1]], nextDueAt);

        if (catchUpPolicy.Equals("SKIP_MISSED", StringComparison.OrdinalIgnoreCase))
            return new CatchUpPlan(scheduledFor < now ? [] : [scheduledFor], nextDueAt);

        if (catchUpPolicy.Equals("ALL_WITH_LIMIT", StringComparison.OrdinalIgnoreCase))
            return new CatchUpPlan(due.Count <= maxCatchUp ? due : due.TakeLast(maxCatchUp).ToArray(), nextDueAt);

        throw new InvalidOperationException($"Unsupported catch-up policy '{catchUpPolicy}'.");
    }

    private static IReadOnlyList<DateTimeOffset> GetDueOccurrences(DateTimeOffset scheduledFor, string cadence, DateTimeOffset now)
    {
        if (scheduledFor > now) return [];

        var due = new List<DateTimeOffset>();
        var occurrence = scheduledFor;
        var guard = 0;
        while (occurrence <= now)
        {
            due.Add(occurrence);
            occurrence = Advance(occurrence, cadence);
            if (++guard > 5000) throw new InvalidOperationException("Schedule catch-up exceeded safety guard.");
        }
        return due;
    }

    private static DateTimeOffset Advance(DateTimeOffset value, string cadence) => cadence.ToUpperInvariant() switch
    {
        "DAILY" => value.AddDays(1),
        "WEEKLY" => value.AddDays(7),
        "MONTHLY" => value.AddMonths(1),
        "QUARTERLY" => value.AddMonths(3),
        "EVENT_DRIVEN" => value.AddYears(100),
        _ => throw new InvalidOperationException($"Unsupported cadence '{cadence}'.")
    };
}
