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; } private static MealSession MapMealNameToSession(string? mealName) { if (string.IsNullOrWhiteSpace(mealName)) return MealSession.None; var name = mealName.Trim(); return name.ToLowerInvariant() switch { "breakfast" => MealSession.Breakfast, "sehri" => MealSession.Breakfast, "lunch" => MealSession.Lunch, "iftari" => MealSession.Lunch, "tea" => MealSession.Tea, "dinner" => MealSession.Dinner, _ => MealSession.None }; } /// //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; // //} // 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 MapMealNameToSession(s.MealName); // } // return MealSession.None; //} public ResolvedMealSession? 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 new ResolvedMealSession { Session = MapMealNameToSession(s.MealName), MealName = s.MealName ?? string.Empty, SiteId = normalizedSite, StartTime = s.StartTime ?? string.Empty, EndTime = s.EndTime ?? string.Empty }; } } return null; } 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); } }