using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using PowderCoating.Core.Enums; using PowderCoating.Core.Interfaces; using PowderCoating.Shared.Constants; namespace PowderCoating.Web.Controllers; /// /// SuperAdmin-only cross-company notification log viewer. /// The company-scoped version lives in NotificationLogsController (CanManageJobs policy). /// Uses IgnoreQueryFilters throughout to bypass the multi-tenancy global filter and see /// logs from all companies simultaneously. /// [Authorize(Policy = AppConstants.Policies.SuperAdminOnly)] public class PlatformNotificationsController : Controller { private readonly IUnitOfWork _unitOfWork; public PlatformNotificationsController(IUnitOfWork unitOfWork) => _unitOfWork = unitOfWork; /// /// Renders a paginated, filterable cross-company notification log view visible only to SuperAdmins. /// All filter/sort/paging is applied in-memory after a single filtered repository fetch. /// Company names are resolved in a follow-up batch query keyed on the page's distinct company IDs. /// Summary counts reflect the full unfiltered dataset so the health cards are accurate regardless of active filters. /// public async Task Index( int? companyId, string? type, string? status, string? channel, string? search, DateTime? from, DateTime? to, int page = 1, int pageSize = 50) { pageSize = pageSize is 25 or 50 or 100 ? pageSize : 50; page = Math.Max(1, page); // Load all non-deleted logs across all tenants, then filter in-memory. var all = (await _unitOfWork.NotificationLogs.FindAsync(n => !n.IsDeleted, ignoreQueryFilters: true)) .AsEnumerable(); if (companyId.HasValue) all = all.Where(n => n.CompanyId == companyId); if (!string.IsNullOrWhiteSpace(type) && Enum.TryParse(type, out var typeEnum)) all = all.Where(n => n.NotificationType == typeEnum); if (!string.IsNullOrWhiteSpace(status) && Enum.TryParse(status, out var statusEnum)) all = all.Where(n => n.Status == statusEnum); if (!string.IsNullOrWhiteSpace(channel) && Enum.TryParse(channel, out var channelEnum)) all = all.Where(n => n.Channel == channelEnum); if (!string.IsNullOrWhiteSpace(search)) all = all.Where(n => n.RecipientName.Contains(search, StringComparison.OrdinalIgnoreCase) || n.Recipient.Contains(search, StringComparison.OrdinalIgnoreCase) || (n.Subject != null && n.Subject.Contains(search, StringComparison.OrdinalIgnoreCase))); if (from.HasValue) all = all.Where(n => n.SentAt >= from.Value.Date); if (to.HasValue) all = all.Where(n => n.SentAt < to.Value.Date.AddDays(1)); var filtered = all.OrderByDescending(n => n.SentAt).ToList(); var totalCount = filtered.Count; var items = filtered .Skip((page - 1) * pageSize) .Take(pageSize) .Select(n => new PlatformNotificationRow { Id = n.Id, CompanyId = (int)n.CompanyId, Channel = n.Channel, NotificationType = n.NotificationType, Status = n.Status, RecipientName = n.RecipientName, Recipient = n.Recipient, Subject = n.Subject, ErrorMessage = n.ErrorMessage, SentAt = n.SentAt }) .ToList(); // Resolve company names for the page's rows in one query var cids = items.Select(i => i.CompanyId).Distinct().ToList(); var companyNames = (await _unitOfWork.Companies.FindAsync(c => cids.Contains(c.Id), ignoreQueryFilters: true)) .ToDictionary(c => c.Id, c => c.CompanyName); foreach (var item in items) item.CompanyName = companyNames.GetValueOrDefault(item.CompanyId); // Sidebar company list for filter dropdown var companies = (await _unitOfWork.Companies.FindAsync(c => !c.IsDeleted, ignoreQueryFilters: true)) .OrderBy(c => c.CompanyName) .Select(c => new { c.Id, c.CompanyName }) .ToList(); // Summary counts from the unfiltered dataset var allLogs = await _unitOfWork.NotificationLogs.FindAsync(n => !n.IsDeleted, ignoreQueryFilters: true); ViewBag.TotalCount = allLogs.Count(); ViewBag.FailedCount = allLogs.Count(n => n.Status == NotificationStatus.Failed); ViewBag.Last24hCount = allLogs.Count(n => n.SentAt >= DateTime.UtcNow.AddHours(-24)); ViewBag.Companies = companies; ViewBag.CompanyIdFilter = companyId; ViewBag.TypeFilter = type; ViewBag.StatusFilter = status; ViewBag.ChannelFilter = channel; ViewBag.Search = search; ViewBag.From = from?.ToString("yyyy-MM-dd"); ViewBag.To = to?.ToString("yyyy-MM-dd"); ViewBag.Page = page; ViewBag.PageSize = pageSize; ViewBag.FilteredCount = totalCount; ViewBag.TotalPages = (int)Math.Ceiling(totalCount / (double)pageSize); return View(items); } /// /// Returns the full notification log entry for the given id, including the complete message body and error details. /// public async Task Details(int id) { var log = await _unitOfWork.NotificationLogs.FirstOrDefaultAsync(n => n.Id == id, ignoreQueryFilters: true); if (log == null) return NotFound(); string? companyName = null; if (log.CompanyId > 0) companyName = (await _unitOfWork.Companies.FirstOrDefaultAsync( c => c.Id == log.CompanyId, ignoreQueryFilters: true))?.CompanyName; ViewBag.CompanyName = companyName; return View(log); } } public class PlatformNotificationRow { public int Id { get; set; } public int CompanyId { get; set; } public string? CompanyName { get; set; } public NotificationChannel Channel { get; set; } public NotificationType NotificationType { get; set; } public NotificationStatus Status { get; set; } public string RecipientName { get; set; } = string.Empty; public string Recipient { get; set; } = string.Empty; public string? Subject { get; set; } public string? ErrorMessage { get; set; } public DateTime SentAt { get; set; } }