using Health.Domain.Entities;
using Health.Domain.Enums;

namespace Health.Application.Medications;

public sealed class MedicationReminderScanner(IMedicationRepository medications) : IMedicationReminderScanner
{
    private readonly IMedicationRepository _medications = medications;

    public async Task<IReadOnlyList<MedicationReminderTask>> ScanAsync(DateTime utcNow, CancellationToken ct)
    {
        var beijingNow = utcNow.AddHours(8);
        var date = DateOnly.FromDateTime(beijingNow);
        var time = TimeOnly.FromDateTime(beijingNow);
        var windowStart = time.AddMinutes(-5);
        var todayStartUtc = beijingNow.Date.AddHours(-8);
        var todayEndUtc = todayStartUtc.AddDays(1);
        var active = await _medications.ListActiveForReminderScanAsync(ct);
        var tasks = new List<MedicationReminderTask>();

        foreach (var medication in active)
        {
            if (!IsActiveOn(medication, date) || !GetDosesForToday(medication, date)) continue;

            foreach (var scheduledTime in medication.TimeOfDay.Where(t => IsInWindow(t, windowStart, time)))
            {
                if (await _medications.DoseLogExistsAsync(
                    medication.UserId,
                    medication.Id,
                    scheduledTime,
                    todayStartUtc,
                    todayEndUtc,
                    ct))
                    continue;

                tasks.Add(new MedicationReminderTask(
                    Guid.NewGuid(),
                    medication.UserId,
                    medication.Id,
                    medication.Name,
                    medication.Dosage,
                    date,
                    scheduledTime));
            }
        }

        return tasks;
    }

    private static bool IsActiveOn(Medication medication, DateOnly date) =>
        (medication.StartDate == null || medication.StartDate <= date)
        && (medication.EndDate == null || medication.EndDate >= date);

    private static bool IsInWindow(TimeOnly value, TimeOnly start, TimeOnly end) =>
        end >= start ? value >= start && value <= end : value >= start || value <= end;

    private static bool GetDosesForToday(Medication medication, DateOnly today)
    {
        if (medication.Frequency is MedicationFrequency.Daily
            or MedicationFrequency.TwiceDaily
            or MedicationFrequency.ThreeTimesDaily
            or MedicationFrequency.AsNeeded)
            return true;

        var startDate = medication.StartDate ?? today;
        if (medication.Frequency == MedicationFrequency.EveryOtherDay)
            return (today.DayNumber - startDate.DayNumber) % 2 == 0;
        if (medication.Frequency == MedicationFrequency.Weekly)
            return today.DayOfWeek == startDate.DayOfWeek;
        return true;
    }
}
