using AnimeAnnouncer.Cache; using Microsoft.AspNetCore.Mvc.RazorPages; namespace AnimeAnnouncer.Web.Pages; public class IndexModel : PageModel { private readonly ILogger _logger; private readonly TMDBCache? _tmdbCache; public List AiringShows { get; set; } = new(); public string? ErrorMessage { get; set; } public IndexModel(ILogger logger, IServiceProvider serviceProvider) { _logger = logger; _tmdbCache = serviceProvider.GetService(); } public async Task OnGetAsync() { if (_tmdbCache == null) { ErrorMessage = "Redis cache connection is not configured or unavailable."; return; } try { List? airingList = await _tmdbCache.GetAiringList(); if (airingList == null || airingList.Count == 0) { AiringShows = new List(); return; } var viewModels = new List(); foreach (var airingItem in airingList) { TMDBCacheItem? cacheItem = null; try { cacheItem = await _tmdbCache.GetCacheItem($"ShowCache-{airingItem.Title}"); } catch (Exception ex) { _logger.LogWarning(ex, "Failed to retrieve cache item for title: {Title}", airingItem.Title); } viewModels.Add(new AiringShowViewModel { AiringSoon = airingItem, CachedDetails = cacheItem }); } // Exclude placeholder/sentinel last-air-dates (e.g. DateTime.MaxValue == 12/31/9999, // used by the announcer when an exact air date is unknown), then sort descending // by LastAirDate (nulls last). var displayable = viewModels.Where(x => { var date = x.EffectiveLastAirDate; // Keep items with a real, displayable date; drop the MaxValue sentinel. return !date.HasValue || date.Value < DateTime.MaxValue.AddMonths(-1); }).ToList(); AiringShows = displayable .Where(x => x.EffectiveLastAirDate.HasValue && x.EffectiveLastAirDate.Value.Date >= DateTime.Now.Date) .OrderBy(x => x.EffectiveLastAirDate) .ThenBy(x => x.Title) .ToList(); } catch (Exception ex) { _logger.LogError(ex, "Error loading airing soon list"); ErrorMessage = $"Unable to load airing soon list: {ex.Message}"; } } public class AiringShowViewModel { public required AiringSoonItem AiringSoon { get; set; } public TMDBCacheItem? CachedDetails { get; set; } public string Title => AiringSoon.Title; public int ShowID => AiringSoon.ShowID; public DateTime? EffectiveLastAirDate => AiringSoon.LastAirDate ?? CachedDetails?.LastAirDate; public int LatestEpisodeNumber => AiringSoon.LatestEpisodeNumber > 0 ? AiringSoon.LatestEpisodeNumber : (CachedDetails?.LatestEpisodeNumber ?? 0); public int? LastEpisodeNumber => CachedDetails?.LastEpisodeNumber; public int? LatestSeasonNumber => CachedDetails?.LatestSeasonNumber; public int? LatestOrdinalEpisodeNumber => CachedDetails?.LatestOrdinalEpisodeNumber; public double? VoteAverage => CachedDetails?.VoteAverage; public string? Overview => !string.IsNullOrWhiteSpace(CachedDetails?.LatestSeasonOverview) ? CachedDetails.LatestSeasonOverview : CachedDetails?.Overview; public string? PosterUrl { get { if (!string.IsNullOrWhiteSpace(CachedDetails?.LatestSeasonPosterPath)) return CachedDetails.LatestSeasonPosterPath; if (!string.IsNullOrWhiteSpace(CachedDetails?.PosterPath)) return CachedDetails.PosterPath; //if (!string.IsNullOrWhiteSpace(CachedDetails?.AnilistPosterPath)) // return CachedDetails.AnilistPosterPath; return null; } } public string? BackdropUrl => CachedDetails?.BackdropPath; public DateTime AirReferenceNow => DateTime.Now; public bool IsFuture { get { var date = EffectiveLastAirDate; return date.HasValue && date.Value > AirReferenceNow; } } public int DaysUntil { get { var date = EffectiveLastAirDate; if (!date.HasValue) return 0; return (int)((date.Value - AirReferenceNow).Ticks / TimeSpan.FromDays(1).Ticks); } } public string AirDateFormatted { get { var date = EffectiveLastAirDate; return date.HasValue ? date.Value.ToString("MM/dd/yyyy") : "Unknown"; } } } }