using BCards.Web.Models; using BCards.Web.ViewModels; namespace BCards.Web.Services; public class DowngradeService : IDowngradeService { private readonly IUserPageService _userPageService; private readonly ILogger _logger; // Ordem de prioridade dos planos (menor = melhor) private static readonly Dictionary PlanPriority = new() { { PlanType.Trial, 0 }, { PlanType.Basic, 1 }, { PlanType.Professional, 2 }, { PlanType.Premium, 3 }, { PlanType.PremiumAffiliate, 4 } }; public DowngradeService(IUserPageService userPageService, ILogger logger) { _userPageService = userPageService; _logger = logger; } public async Task AnalyzeDowngradeImpact(string userId, PlanType newPlan) { var pages = await _userPageService.GetUserPagesAsync(userId); var activePagesOnly = pages.Where(p => p.Status == PageStatus.Active).ToList(); var newMaxPages = newPlan.GetMaxPages(); var newMaxLinks = newPlan.GetMaxLinksPerPage(); var eligiblePages = activePagesOnly .Where(p => newMaxLinks == -1 || (p.Links?.Count ?? 0) <= newMaxLinks) .OrderBy(p => p.CreatedAt) // Mais antigas primeiro .Take(newMaxPages) .Select(p => new PageInfo { Id = p.Id, DisplayName = p.DisplayName, LinkCount = p.Links?.Count ?? 0, CreatedAt = p.CreatedAt }) .ToList(); var affectedPages = activePagesOnly .Where(p => !eligiblePages.Any(ep => ep.Id == p.Id)) .Select(p => new PageInfo { Id = p.Id, DisplayName = p.DisplayName, LinkCount = p.Links?.Count ?? 0, CreatedAt = p.CreatedAt, SuspensionReason = GetSuspensionReason(p, newPlan) }) .ToList(); var issues = new List(); var pagesWithTooManyLinks = activePagesOnly .Where(p => newMaxLinks != -1 && (p.Links?.Count ?? 0) > newMaxLinks) .Count(); if (pagesWithTooManyLinks > 0) issues.Add($"{pagesWithTooManyLinks} páginas têm muitos links"); if (activePagesOnly.Count > newMaxPages) issues.Add($"Total de páginas ({activePagesOnly.Count}) excede limite ({newMaxPages})"); return new DowngradeAnalysis { EligiblePages = eligiblePages, AffectedPages = affectedPages, Issues = issues }; } public async Task ProcessAutomaticDowngrade(string userId, PlanType newPlan) { try { var analysis = await AnalyzeDowngradeImpact(userId, newPlan); // PROTEÇÃO: Se nenhuma página for elegível, cancelar downgrade if (analysis.IsCritical) { throw new InvalidOperationException( "Downgrade cancelado: nenhuma página atende aos critérios do novo plano. " + "Edite suas páginas para reduzir links ou escolha um plano superior." ); } // Processar normalmente - suspender páginas afetadas foreach (var pageInfo in analysis.AffectedPages) { var page = await _userPageService.GetPageByIdAsync(pageInfo.Id); if (page != null) { page.Status = PageStatus.SuspendedByPlanLimit; var detailedReason = GetDetailedSuspensionReason(page, newPlan); await _userPageService.UpdatePageAsync(page); _logger.LogInformation( "Página {PageName} (ID: {PageId}) suspensa por downgrade para {PlanType}. Motivo: {Reason}", page.DisplayName, page.Id, newPlan, detailedReason ); } } var result = new DowngradeResult { Success = true, KeptActive = analysis.EligiblePages.Count, Suspended = analysis.AffectedPages.Count, Message = $"Downgrade concluído. {analysis.EligiblePages.Count} páginas ativas, {analysis.AffectedPages.Count} suspensas.", Details = analysis.AffectedPages.Select(p => $"{p.DisplayName}: {p.SuspensionReason}").ToList() }; _logger.LogInformation( "Downgrade automático concluído para usuário {UserId}: {KeptActive} mantidas, {Suspended} suspensas", userId, result.KeptActive, result.Suspended ); return result; } catch (Exception ex) { _logger.LogError(ex, "Erro no processamento automático de downgrade para usuário {UserId}", userId); return new DowngradeResult { Success = false, Message = $"Erro no processamento: {ex.Message}", Details = new List { ex.Message } }; } } public async Task ProcessAutomaticUpgrade(string userId, PlanType newPlan) { try { var pages = await _userPageService.GetUserPagesAsync(userId); var suspendedPages = pages.Where(p => p.Status == PageStatus.SuspendedByPlanLimit).ToList(); var newMaxPages = newPlan.GetMaxPages(); var newMaxLinks = newPlan.GetMaxLinksPerPage(); var reactivated = 0; // Reativar páginas que agora cabem no novo plano foreach (var page in suspendedPages.OrderBy(p => p.CreatedAt).Take(newMaxPages)) { // Verificar se a página atende aos critérios de links if (newMaxLinks == -1 || (page.Links?.Count ?? 0) <= newMaxLinks) { page.Status = PageStatus.Active; await _userPageService.UpdatePageAsync(page); reactivated++; _logger.LogInformation( "Página {PageName} (ID: {PageId}) reativada por upgrade para {PlanType}", page.DisplayName, page.Id, newPlan ); } } return new DowngradeResult { Success = true, KeptActive = reactivated, Suspended = 0, Message = $"Upgrade concluído. {reactivated} páginas reativadas.", Details = suspendedPages.Take(reactivated).Select(p => $"{p.DisplayName}: Reativada").ToList() }; } catch (Exception ex) { _logger.LogError(ex, "Erro no processamento automático de upgrade para usuário {UserId}", userId); return new DowngradeResult { Success = false, Message = $"Erro no processamento: {ex.Message}", Details = new List { ex.Message } }; } } public bool IsDowngrade(PlanType oldPlan, PlanType newPlan) { return PlanPriority.TryGetValue(oldPlan, out var oldPriority) && PlanPriority.TryGetValue(newPlan, out var newPriority) && newPriority < oldPriority; } public bool IsUpgrade(PlanType oldPlan, PlanType newPlan) { return PlanPriority.TryGetValue(oldPlan, out var oldPriority) && PlanPriority.TryGetValue(newPlan, out var newPriority) && newPriority > oldPriority; } private string GetSuspensionReason(UserPage page, PlanType newPlan) { var maxLinks = newPlan.GetMaxLinksPerPage(); var currentLinks = page.Links?.Count ?? 0; if (maxLinks != -1 && currentLinks > maxLinks) return $"❌ Tem {currentLinks} links (limite: {maxLinks})"; return "❌ Excesso de páginas (critério: mais recentes são suspensas)"; } private string GetDetailedSuspensionReason(UserPage page, PlanType newPlan) { var maxLinks = newPlan.GetMaxLinksPerPage(); var currentLinks = page.Links?.Count ?? 0; if (maxLinks != -1 && currentLinks > maxLinks) return $"Página suspensa: possui {currentLinks} links, mas o plano {newPlan.GetDisplayName()} permite apenas {maxLinks} links por página."; return $"Página suspensa: plano {newPlan.GetDisplayName()} permite apenas {newPlan.GetMaxPages()} páginas ativas. Páginas mais recentes foram suspensas automaticamente."; } }