using UtopiaCanteenSystem.Models; namespace UtopiaCanteenSystem.Services; /// /// Resolves current meal session from production (MySQL meal_schedule). Caches schedules per site for 60 seconds. /// No SQLite; scan validation uses production data only. /// public class DbMealSessionResolver : IMealSessionResolver { private readonly IMealScheduleService _mealScheduleService; private readonly TimeSpan _cacheTtl = TimeSpan.FromSeconds(60); private readonly Dictionary Schedules, DateTime ExpiryUtc)> _cache = new(StringComparer.OrdinalIgnoreCase); private readonly object _cacheLock = new(); public DbMealSessionResolver(IMealScheduleService mealScheduleService) { _mealScheduleService = mealScheduleService; } /// public MealSession GetCurrentSession(DateTime nowLocal, string siteId) { var normalizedSite = (siteId ?? string.Empty).Trim(); if (string.IsNullOrEmpty(normalizedSite)) normalizedSite = "01"; if (normalizedSite.Length == 1 && char.IsDigit(normalizedSite[0])) normalizedSite = normalizedSite.PadLeft(2, '0'); var schedules = GetSchedulesForSiteCached(normalizedSite); var t = nowLocal.TimeOfDay; foreach (var s in schedules) { if (!TryParseTime(s.StartTime, out var start) || !TryParseTime(s.EndTime, out var end)) continue; if (t >= start && t < end) return (MealSession)s.MealSession; } return MealSession.None; } private List GetSchedulesForSiteCached(string siteId) { lock (_cacheLock) { if (_cache.TryGetValue(siteId, out var entry) && DateTime.UtcNow < entry.ExpiryUtc) return entry.Schedules; } var list = _mealScheduleService.GetActiveSchedulesForSite(siteId).ToList(); lock (_cacheLock) { _cache[siteId] = (list, DateTime.UtcNow.Add(_cacheTtl)); } return list; } private static bool TryParseTime(string value, out TimeSpan time) { time = TimeSpan.Zero; if (string.IsNullOrWhiteSpace(value)) return false; return TimeSpan.TryParse(value.Trim(), null, out time) || TimeSpan.TryParse(value.Trim().Replace(".", ":"), null, out time); } }