using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using PowderCoating.Application.Interfaces; using PowderCoating.Core.Interfaces; using PowderCoating.Shared.Constants; namespace PowderCoating.Web.Controllers; [Authorize(Policy = AppConstants.Policies.SuperAdminOnly)] public class AiUsageReportController : Controller { private readonly IUnitOfWork _unitOfWork; private readonly IAiUsageReportService _aiUsageReport; private readonly ILogger _logger; public AiUsageReportController( IUnitOfWork unitOfWork, IAiUsageReportService aiUsageReport, ILogger logger) { _unitOfWork = unitOfWork; _aiUsageReport = aiUsageReport; _logger = logger; } /// /// Platform-wide AI usage report. Shows per-company call counts, photo upload totals, top /// feature used, and a usage tier so SuperAdmins can identify abusive or unusually heavy tenants. /// Companies and plan configs come from IUnitOfWork; AiUsageLogs aggregations and photo counts /// come from IAiUsageReportService (which runs SQL GROUP BY queries via ApplicationDbContext). /// public async Task Index() { try { var companies = (await _unitOfWork.Companies.GetAllAsync(ignoreQueryFilters: true)) .Where(c => !c.IsDeleted) .Select(c => new { c.Id, c.CompanyName, c.SubscriptionPlan, c.IsActive }) .ToList(); var planConfigs = await _unitOfWork.SubscriptionPlanConfigs.GetAllAsync(); var planNames = planConfigs.ToDictionary(p => p.Plan, p => p.DisplayName); var data = await _aiUsageReport.GetReportDataAsync(); var topFeatureByCompany = data.FeatureStats .GroupBy(f => f.CompanyId) .ToDictionary( g => g.Key, g => g.OrderByDescending(f => f.Count).First().Feature); var featureBreakdownByCompany = data.FeatureStats .GroupBy(f => f.CompanyId) .ToDictionary( g => g.Key, g => g.ToDictionary(f => f.Feature, f => f.Count)); var usageDict = data.UsageByCompany.ToDictionary(u => u.CompanyId); var rows = companies.Select(c => { usageDict.TryGetValue(c.Id, out var u); data.PhotoCountsByCompany.TryGetValue(c.Id, out var photos); topFeatureByCompany.TryGetValue(c.Id, out var topFeature); featureBreakdownByCompany.TryGetValue(c.Id, out var breakdown); planNames.TryGetValue(c.SubscriptionPlan, out var planName); return new AiUsageReportRow { CompanyId = c.Id, CompanyName = c.CompanyName, Plan = planName ?? $"Plan {c.SubscriptionPlan}", IsActive = c.IsActive, Today = u?.Today ?? 0, Last7Days = u?.Last7Days ?? 0, Last30Days = u?.Last30Days ?? 0, AllTime = u?.AllTime ?? 0, PhotoCount = photos, TopFeature = topFeature, FeatureBreakdown = breakdown ?? [] }; }) .OrderByDescending(r => r.Last30Days) .ThenByDescending(r => r.AllTime) .ToList(); var vm = new AiUsageReportViewModel { Rows = rows, TotalCallsLast30Days = rows.Sum(r => r.Last30Days), TotalCallsToday = rows.Sum(r => r.Today), CompaniesActiveToday = rows.Count(r => r.Today > 0), TotalPhotosUploaded = rows.Sum(r => r.PhotoCount), MostActiveCompany = rows.FirstOrDefault(r => r.Last30Days > 0)?.CompanyName ?? "—" }; return View(vm); } catch (Exception ex) { _logger.LogError(ex, "Error loading AI usage report"); return View(new AiUsageReportViewModel()); } } } // ── View models ────────────────────────────────────────────────────────────── public class AiUsageReportViewModel { public List Rows { get; set; } = []; public int TotalCallsLast30Days { get; set; } public int TotalCallsToday { get; set; } public int CompaniesActiveToday { get; set; } public int TotalPhotosUploaded { get; set; } public string MostActiveCompany { get; set; } = "—"; } public class AiUsageReportRow { public int CompanyId { get; set; } public string CompanyName { get; set; } = string.Empty; public string Plan { get; set; } = string.Empty; public bool IsActive { get; set; } public int Today { get; set; } public int Last7Days { get; set; } public int Last30Days { get; set; } public int AllTime { get; set; } public int PhotoCount { get; set; } public string? TopFeature { get; set; } public Dictionary FeatureBreakdown { get; set; } = []; public string UsageTier => Last30Days switch { 0 => "Inactive", <= 10 => "Light", <= 50 => "Regular", <= 200 => "Heavy", _ => "Power User" }; public string TierBadgeClass => UsageTier switch { "Inactive" => "bg-secondary", "Light" => "bg-success", "Regular" => "bg-primary", "Heavy" => "bg-warning text-dark", "Power User" => "bg-danger", _ => "bg-secondary" }; public string FeatureDisplayName(string feature) => feature switch { "PhotoQuote" => "Photo Quote", "HelpChat" => "Help Chat", "ReceiptScan" => "Receipt Scan", "AccountSuggest" => "Account Suggest", "ArFollowUp" => "AR Follow-Up", "FinancialSummary" => "Financial Summary", "CashFlowForecast" => "Cash Flow", "AnomalyDetection" => "Anomaly Detection", _ => feature }; }