using System.Collections.Concurrent; using System.Collections.Generic; using System.Globalization; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using CMSMicroservice.Application.Common.Interfaces; using CMSMicroservice.Domain.Entities; using CMSMicroservice.Infrastructure.Persistence; namespace CMSMicroservice.Infrastructure.Repositories; /// /// ریپازیتوری هفته‌ها با کش در حافظه /// موقع استارت برنامه از دیتابیس لود می‌شود و در حافظه نگهداری می‌شود /// Thread-safe است و می‌توان از آن در محیط‌های multi-threaded استفاده کرد /// Singleton است و از IServiceScopeFactory برای دسترسی به DbContext استفاده می‌کند /// public class WeekDefinitionRepository : IWeekDefinitionRepository { private readonly IServiceScopeFactory _scopeFactory; private readonly ILogger _logger; private readonly PersianCalendar _persianCalendar; // کش در حافظه private readonly ConcurrentDictionary _cacheByGregorianWeek = new(); private readonly ConcurrentDictionary _cacheByPersianWeek = new(); private readonly ConcurrentDictionary _cacheByOrder = new(); private List _allWeeks = new(); private readonly object _cacheLock = new(); private bool _isCacheLoaded; public WeekDefinitionRepository( IServiceScopeFactory scopeFactory, ILogger logger) { _scopeFactory = scopeFactory; _logger = logger; _persianCalendar = new PersianCalendar(); } public bool IsCacheLoaded => _isCacheLoaded; public int CachedWeeksCount => _allWeeks.Count; #region Load Cache /// /// بارگذاری اولیه کش از دیتابیس /// این متد باید موقع استارت برنامه فراخوانی شود /// public async Task ReloadCacheAsync(CancellationToken cancellationToken = default) { _logger.LogInformation("Loading WeekDefinitions into memory cache..."); try { using var scope = _scopeFactory.CreateScope(); var context = scope.ServiceProvider.GetRequiredService(); var weeks = await context.WeekDefinitions .AsNoTracking() .Where(w => w.IsActive) .OrderBy(w => w.WeekOrder) .ToListAsync(cancellationToken); lock (_cacheLock) { _cacheByGregorianWeek.Clear(); _cacheByPersianWeek.Clear(); _cacheByOrder.Clear(); _allWeeks = weeks; foreach (var week in weeks) { _cacheByGregorianWeek.TryAdd(week.GregorianWeekNumber, week); _cacheByPersianWeek.TryAdd(week.PersianWeekNumber, week); _cacheByOrder.TryAdd(week.WeekOrder, week); } _isCacheLoaded = true; } _logger.LogInformation("Successfully loaded {Count} week definitions into cache", weeks.Count); } catch (Exception ex) { _logger.LogError(ex, "Error loading WeekDefinitions into cache"); throw; } } #endregion #region Current Week Methods public WeekDefinition? GetCurrentWeek() { EnsureCacheLoaded(); return GetWeekByDate(DateTime.Today); } public Task GetCurrentWeekAsync(CancellationToken cancellationToken = default) { return Task.FromResult(GetCurrentWeek()); } public string GetCurrentGregorianWeekNumber() { var currentWeek = GetCurrentWeek(); return currentWeek?.GregorianWeekNumber ?? CalculateGregorianWeekNumber(DateTime.Today); } public string GetCurrentPersianWeekNumber() { var currentWeek = GetCurrentWeek(); return currentWeek?.PersianWeekNumber ?? CalculatePersianWeekNumber(DateTime.Today); } public string GetCurrentWeekDisplayName() { var currentWeek = GetCurrentWeek(); return currentWeek?.DisplayName ?? "نامشخص"; } public bool IsDateInCurrentWeek(DateTime date) { var currentWeek = GetCurrentWeek(); if (currentWeek == null) return false; return date.Date >= currentWeek.StartDate.Date && date.Date <= currentWeek.EndDate.Date; } #endregion #region Navigation Methods public WeekDefinition? GetNextWeek() { var currentWeek = GetCurrentWeek(); if (currentWeek == null) return null; return GetNextWeek(currentWeek); } public WeekDefinition? GetNextWeek(WeekDefinition currentWeek) { EnsureCacheLoaded(); return _cacheByOrder.TryGetValue(currentWeek.WeekOrder + 1, out var nextWeek) ? nextWeek : null; } public WeekDefinition? GetPreviousWeek() { var currentWeek = GetCurrentWeek(); if (currentWeek == null) return null; return GetPreviousWeek(currentWeek); } public WeekDefinition? GetPreviousWeek(WeekDefinition currentWeek) { EnsureCacheLoaded(); if (currentWeek.WeekOrder <= 1) return null; return _cacheByOrder.TryGetValue(currentWeek.WeekOrder - 1, out var prevWeek) ? prevWeek : null; } #endregion #region Lookup Methods public WeekDefinition? GetWeekByDate(DateTime date) { EnsureCacheLoaded(); // شنبه اول هفته (تقویم شمسی) var saturday = GetStartOfWeek(date); return _allWeeks.FirstOrDefault(w => w.StartDate.Date == saturday.Date); } public WeekDefinition? GetWeekByGregorianWeekNumber(string gregorianWeekNumber) { EnsureCacheLoaded(); return _cacheByGregorianWeek.TryGetValue(gregorianWeekNumber, out var week) ? week : null; } public WeekDefinition? GetWeekByPersianWeekNumber(string persianWeekNumber) { EnsureCacheLoaded(); return _cacheByPersianWeek.TryGetValue(persianWeekNumber, out var week) ? week : null; } public WeekDefinition? GetWeekByOrder(int weekOrder) { EnsureCacheLoaded(); return _cacheByOrder.TryGetValue(weekOrder, out var week) ? week : null; } public IReadOnlyList GetAllWeeks() { EnsureCacheLoaded(); return _allWeeks.AsReadOnly(); } public IReadOnlyList GetWeeksByGregorianYear(int year) { EnsureCacheLoaded(); return _allWeeks.Where(w => w.GregorianYear == year).ToList().AsReadOnly(); } public IReadOnlyList GetWeeksByPersianYear(int year) { EnsureCacheLoaded(); return _allWeeks.Where(w => w.PersianYear == year).ToList().AsReadOnly(); } public (DateTime startDate, DateTime endDate)? GetWeekDateRange(string gregorianWeekNumber) { EnsureCacheLoaded(); var week = GetWeekByGregorianWeekNumber(gregorianWeekNumber); if (week == null) return null; return (week.StartDate, week.EndDate.AddHours(23).AddMinutes(59).AddSeconds(59)); } public (DateTime startDate, DateTime endDate)? GetWeekDateRange(long WeekDefinitionId) { EnsureCacheLoaded(); var week = GetWeekById(WeekDefinitionId); if (week == null) return null; return (week.StartDate, week.EndDate.AddHours(23).AddMinutes(59).AddSeconds(59)); } public string? GetPreviousWeekNumber(string gregorianWeekNumber) { EnsureCacheLoaded(); var currentWeek = GetWeekByGregorianWeekNumber(gregorianWeekNumber); if (currentWeek == null) return null; var previousWeek = GetWeekByOrder(currentWeek.WeekOrder - 1); return previousWeek?.GregorianWeekNumber; } public string? GetNextWeekNumber(string gregorianWeekNumber) { EnsureCacheLoaded(); var currentWeek = GetWeekByGregorianWeekNumber(gregorianWeekNumber); if (currentWeek == null) return null; var nextWeek = GetWeekByOrder(currentWeek.WeekOrder + 1); return nextWeek?.GregorianWeekNumber; } public string GetDisplayNameByGregorianWeekNumber(string gregorianWeekNumber) { EnsureCacheLoaded(); var week = GetWeekByGregorianWeekNumber(gregorianWeekNumber); return week?.DisplayName ?? gregorianWeekNumber; // اگر پیدا نشد، خود شماره میلادی رو برگردون } public IEnumerable SearchWeeksByDisplayName(string? filter = null) { EnsureCacheLoaded(); var weeks = _cacheByOrder.Values.OrderBy(w => w.WeekOrder); if (string.IsNullOrWhiteSpace(filter)) { return weeks; } return weeks.Where(w => w.DisplayName.Contains(filter, StringComparison.OrdinalIgnoreCase)); } public long? GetWeekDefinitionId(string gregorianWeekNumber) { EnsureCacheLoaded(); var week = GetWeekByGregorianWeekNumber(gregorianWeekNumber); return week?.Id; } public string? GetGregorianWeekNumber(long weekDefinitionId) { EnsureCacheLoaded(); var week = _allWeeks.FirstOrDefault(w => w.Id == weekDefinitionId); return week?.GregorianWeekNumber; } public WeekDefinition? GetWeekById(long weekDefinitionId) { EnsureCacheLoaded(); var week = _allWeeks.FirstOrDefault(w => w.Id == weekDefinitionId); return week; } #endregion #region Calculation Methods (Fallback when not in cache) public string CalculateGregorianWeekNumber(DateTime date) { // پیدا کردن شنبه این هفته var saturday = GetStartOfWeek(date); // دوشنبه همان هفته ISO var monday = saturday.AddDays(2); var cal = CultureInfo.InvariantCulture.Calendar; var weekNumber = cal.GetWeekOfYear(monday, CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday); var year = monday.Year; if (weekNumber == 1 && monday.Month == 12) year++; else if (weekNumber >= 52 && monday.Month == 1) year--; return $"{year}-W{weekNumber:D2}"; } public string CalculatePersianWeekNumber(DateTime date) { var saturday = GetStartOfWeek(date); var persianYear = _persianCalendar.GetYear(saturday); var dayOfYear = _persianCalendar.GetDayOfYear(saturday); var firstDayOfYear = _persianCalendar.ToDateTime(persianYear, 1, 1, 0, 0, 0, 0); var daysUntilFirstSaturday = ((int)DayOfWeek.Saturday - (int)firstDayOfYear.DayOfWeek + 7) % 7; int weekNumber; if (daysUntilFirstSaturday == 0) { weekNumber = ((dayOfYear - 1) / 7) + 1; } else { if (dayOfYear <= daysUntilFirstSaturday) weekNumber = 1; else weekNumber = ((dayOfYear - daysUntilFirstSaturday - 1) / 7) + 1; } return $"{persianYear}-W{weekNumber:D2}"; } #endregion #region Helper Methods private void EnsureCacheLoaded() { if (!_isCacheLoaded) { _logger.LogWarning("WeekDefinition cache is not loaded. Some methods may return null."); } } /// /// پیدا کردن شنبه این هفته (شروع هفته شمسی) /// private static DateTime GetStartOfWeek(DateTime date) { var diff = (7 + (date.DayOfWeek - DayOfWeek.Saturday)) % 7; return date.AddDays(-diff).Date; } #endregion }