using AutoMapper;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using PowderCoating.Application.DTOs.Subscription;
using PowderCoating.Core.Interfaces;
using PowderCoating.Shared.Constants;
namespace PowderCoating.Web.Controllers;
///
/// SuperAdmin-only interface for managing subscription plan configurations
/// (limits, pricing, feature flags, and Stripe price IDs). Changes here
/// affect every new tenant assignment of the plan and any quota checks that
/// read plan limits at runtime. Plan DisplayName and Plan enum
/// value are intentionally not editable here to prevent breaking existing company
/// subscription records that reference them by integer value.
///
[Authorize(Policy = AppConstants.Policies.SuperAdminOnly)]
public class PlatformSubscriptionController : Controller
{
private readonly IUnitOfWork _unitOfWork;
private readonly ILogger _logger;
public PlatformSubscriptionController(IUnitOfWork unitOfWork, ILogger logger)
{
_unitOfWork = unitOfWork;
_logger = logger;
}
///
/// Lists all subscription plan configurations ordered by display sort order.
/// Uses ignoreQueryFilters: true because plan configs carry
/// CompanyId = 0 and are otherwise hidden by the multi-tenancy filter.
/// Projects to to avoid exposing the
/// full entity (including internal Stripe secrets) to the view layer.
///
[HttpGet]
public async Task Index()
{
var configs = (await _unitOfWork.SubscriptionPlanConfigs.GetAllAsync(ignoreQueryFilters: true))
.OrderBy(c => c.SortOrder)
.ToList();
var dtos = configs.Select(c => new SubscriptionPlanConfigDto
{
Id = c.Id,
Plan = c.Plan,
DisplayName = c.DisplayName,
Description = c.Description,
MaxUsers = c.MaxUsers,
MaxActiveJobs = c.MaxActiveJobs,
MaxCustomers = c.MaxCustomers,
MaxQuotes = c.MaxQuotes,
MaxCatalogItems = c.MaxCatalogItems,
MaxJobPhotos = c.MaxJobPhotos,
MaxQuotePhotos = c.MaxQuotePhotos,
MaxAiPhotoQuotesPerMonth = c.MaxAiPhotoQuotesPerMonth,
MonthlyPrice = c.MonthlyPrice,
AnnualPrice = c.AnnualPrice,
StripePriceIdMonthly = c.StripePriceIdMonthly,
StripePriceIdAnnual = c.StripePriceIdAnnual,
AllowOnlinePayments = c.AllowOnlinePayments,
AllowAccounting = c.AllowAccounting,
AllowAiPhotoQuotes = c.AllowAiPhotoQuotes,
AllowAiInventoryAssist = c.AllowAiInventoryAssist,
AllowAiCatalogPriceCheck = c.AllowAiCatalogPriceCheck,
AllowSms = c.AllowSms,
AllowCustomFormulas = c.AllowCustomFormulas,
IsActive = c.IsActive,
SortOrder = c.SortOrder
}).ToList();
return View(dtos);
}
///
/// Returns the Edit form for a plan config, loaded into an
/// to prevent over-posting of
/// immutable fields such as Plan, DisplayName, and SortOrder.
/// The plan display name is placed in ViewBag for the page heading.
///
[HttpGet]
public async Task Edit(int id)
{
var config = await _unitOfWork.SubscriptionPlanConfigs.GetByIdAsync(id, ignoreQueryFilters: true);
if (config == null) return NotFound();
var dto = new UpdateSubscriptionPlanConfigDto
{
Id = config.Id,
Description = config.Description,
MaxUsers = config.MaxUsers,
MaxActiveJobs = config.MaxActiveJobs,
MaxCustomers = config.MaxCustomers,
MaxQuotes = config.MaxQuotes,
MaxCatalogItems = config.MaxCatalogItems,
MaxJobPhotos = config.MaxJobPhotos,
MaxQuotePhotos = config.MaxQuotePhotos,
MaxAiPhotoQuotesPerMonth = config.MaxAiPhotoQuotesPerMonth,
MonthlyPrice = config.MonthlyPrice,
AnnualPrice = config.AnnualPrice,
StripePriceIdMonthly = config.StripePriceIdMonthly,
StripePriceIdAnnual = config.StripePriceIdAnnual,
AllowOnlinePayments = config.AllowOnlinePayments,
AllowAccounting = config.AllowAccounting,
AllowAiPhotoQuotes = config.AllowAiPhotoQuotes,
AllowAiInventoryAssist = config.AllowAiInventoryAssist,
AllowAiCatalogPriceCheck = config.AllowAiCatalogPriceCheck,
AllowSms = config.AllowSms,
AllowCustomFormulas = config.AllowCustomFormulas,
IsActive = config.IsActive
};
ViewBag.PlanName = config.DisplayName;
return View(dto);
}
///
/// Applies the updated plan configuration values and saves. Uses explicit field
/// mapping from to the tracked entity so that immutable
/// identity fields (Plan, DisplayName, SortOrder) are never
/// overwritten. Logs the change at Information level for the audit trail.
///
[HttpPost]
[ValidateAntiForgeryToken]
public async Task Edit(int id, UpdateSubscriptionPlanConfigDto dto)
{
if (!ModelState.IsValid)
{
var configForView = await _unitOfWork.SubscriptionPlanConfigs.GetByIdAsync(id, ignoreQueryFilters: true);
ViewBag.PlanName = configForView?.DisplayName ?? "Unknown";
return View(dto);
}
var config = await _unitOfWork.SubscriptionPlanConfigs.GetByIdAsync(id, ignoreQueryFilters: true);
if (config == null) return NotFound();
config.Description = dto.Description;
config.MaxUsers = dto.MaxUsers;
config.MaxActiveJobs = dto.MaxActiveJobs;
config.MaxCustomers = dto.MaxCustomers;
config.MaxQuotes = dto.MaxQuotes;
config.MaxCatalogItems = dto.MaxCatalogItems;
config.MaxJobPhotos = dto.MaxJobPhotos;
config.MaxQuotePhotos = dto.MaxQuotePhotos;
config.MaxAiPhotoQuotesPerMonth = dto.MaxAiPhotoQuotesPerMonth;
config.MonthlyPrice = dto.MonthlyPrice;
config.AnnualPrice = dto.AnnualPrice;
config.StripePriceIdMonthly = dto.StripePriceIdMonthly;
config.StripePriceIdAnnual = dto.StripePriceIdAnnual;
config.AllowOnlinePayments = dto.AllowOnlinePayments;
config.AllowAccounting = dto.AllowAccounting;
config.AllowAiPhotoQuotes = dto.AllowAiPhotoQuotes;
config.AllowAiInventoryAssist = dto.AllowAiInventoryAssist;
config.AllowAiCatalogPriceCheck = dto.AllowAiCatalogPriceCheck;
config.AllowSms = dto.AllowSms;
config.AllowCustomFormulas = dto.AllowCustomFormulas;
config.IsActive = dto.IsActive;
await _unitOfWork.SubscriptionPlanConfigs.UpdateAsync(config);
await _unitOfWork.CompleteAsync();
_logger.LogInformation("SuperAdmin updated subscription plan config: {Plan}", config.DisplayName);
TempData["Success"] = $"{config.DisplayName} plan configuration updated successfully.";
return RedirectToAction(nameof(Index));
}
}