using BCards.Web.Models; using BCards.Web.Repositories; using System.Text.RegularExpressions; using System.Globalization; using System.Text; using BCards.Web.Utils; namespace BCards.Web.Services; public class UserPageService : IUserPageService { private readonly IUserPageRepository _userPageRepository; private readonly IUserRepository _userRepository; private readonly ISubscriptionRepository _subscriptionRepository; public UserPageService( IUserPageRepository userPageRepository, IUserRepository userRepository, ISubscriptionRepository subscriptionRepository) { _userPageRepository = userPageRepository; _userRepository = userRepository; _subscriptionRepository = subscriptionRepository; } public async Task GetPageAsync(string category, string slug) { return await _userPageRepository.GetBySlugAsync(category, slug); } public async Task GetUserPageAsync(string userId) { return await _userPageRepository.GetByUserIdAsync(userId); } public async Task GetPageByIdAsync(string id) { return await _userPageRepository.GetByIdAsync(id); } public async Task> GetUserPagesAsync(string userId) { return await _userPageRepository.GetByUserIdAllAsync(userId); } public async Task> GetActivePagesAsync() { return await _userPageRepository.GetActivePagesAsync(); } public async Task CreatePageAsync(UserPage userPage) { userPage.Slug = await GenerateSlugAsync(userPage.Category, userPage.DisplayName); return await _userPageRepository.CreateAsync(userPage); } public async Task UpdatePageAsync(UserPage userPage) { return await _userPageRepository.UpdateAsync(userPage); } public async Task DeletePageAsync(string id) { await _userPageRepository.DeleteAsync(id); } public async Task ValidateSlugAsync(string category, string slug, string? excludeId = null) { if (string.IsNullOrWhiteSpace(slug) || string.IsNullOrWhiteSpace(category)) return false; if (!IsValidSlugFormat(slug)) return false; return !await _userPageRepository.SlugExistsAsync(category, slug, excludeId); } public async Task GenerateSlugAsync(string category, string name) { var slug = SlugHelper.CreateSlug(GenerateSlug(name)); var originalSlug = slug; var counter = 1; while (await _userPageRepository.SlugExistsAsync(category, slug)) { slug = $"{originalSlug}-{counter}"; counter++; } return slug; } public async Task CanCreateLinksAsync(string userId, int newLinksCount = 1) { var userPage = await _userPageRepository.GetByUserIdAsync(userId); if (userPage == null) return true; // New page var subscription = await _subscriptionRepository.GetByUserIdAsync(userId); var maxLinks = subscription?.MaxLinks ?? 5; // Default free plan if (maxLinks == -1) return true; // Unlimited var currentLinksCount = userPage.Links?.Count(l => l.IsActive) ?? 0; return (currentLinksCount + newLinksCount) <= maxLinks; } public async Task RecordPageViewAsync(string pageId, string? referrer = null, string? userAgent = null) { var page = await _userPageRepository.GetByIdAsync(pageId); if (page?.PlanLimitations.AllowAnalytics != true) return; var analytics = page.Analytics; analytics.TotalViews++; analytics.LastViewedAt = DateTime.UtcNow; // Monthly stats var monthKey = DateTime.UtcNow.ToString("yyyy-MM"); if (analytics.MonthlyViews.ContainsKey(monthKey)) analytics.MonthlyViews[monthKey]++; else analytics.MonthlyViews[monthKey] = 1; // Referrer stats if (!string.IsNullOrEmpty(referrer)) { var domain = ExtractDomain(referrer); if (!string.IsNullOrEmpty(domain)) { if (analytics.TopReferrers.ContainsKey(domain)) analytics.TopReferrers[domain]++; else analytics.TopReferrers[domain] = 1; } } // Device stats (simplified) if (!string.IsNullOrEmpty(userAgent)) { var deviceType = GetDeviceType(userAgent); if (analytics.DeviceStats.ContainsKey(deviceType)) analytics.DeviceStats[deviceType]++; else analytics.DeviceStats[deviceType] = 1; } await _userPageRepository.UpdateAnalyticsAsync(pageId, analytics); } public async Task RecordLinkClickAsync(string pageId, int linkIndex) { var page = await _userPageRepository.GetByIdAsync(pageId); if (page?.PlanLimitations.AllowAnalytics != true) return; if (linkIndex >= 0 && linkIndex < page.Links.Count) { page.Links[linkIndex].Clicks++; } var analytics = page.Analytics; analytics.TotalClicks++; // Monthly clicks var monthKey = DateTime.UtcNow.ToString("yyyy-MM"); if (analytics.MonthlyClicks.ContainsKey(monthKey)) analytics.MonthlyClicks[monthKey]++; else analytics.MonthlyClicks[monthKey] = 1; await _userPageRepository.UpdateAsync(page); } public async Task> GetRecentPagesAsync(int limit = 10) { return await _userPageRepository.GetRecentPagesAsync(limit); } public async Task> GetPagesByCategoryAsync(string category, int limit = 20) { return await _userPageRepository.GetByCategoryAsync(category, limit); } private static string GenerateSlug(string text) { if (string.IsNullOrWhiteSpace(text)) return string.Empty; // Remove acentos text = RemoveDiacritics(text); // Converter para minúsculas text = text.ToLowerInvariant(); // Substituir espaços e caracteres especiais por hífens text = Regex.Replace(text, @"[^a-z0-9\s-]", ""); text = Regex.Replace(text, @"[\s-]+", "-"); // Remover hífens do início e fim text = text.Trim('-'); return text; } private static string RemoveDiacritics(string text) { var normalizedString = text.Normalize(NormalizationForm.FormD); var stringBuilder = new StringBuilder(); foreach (var c in normalizedString) { var unicodeCategory = CharUnicodeInfo.GetUnicodeCategory(c); if (unicodeCategory != UnicodeCategory.NonSpacingMark) { stringBuilder.Append(c); } } return stringBuilder.ToString().Normalize(NormalizationForm.FormC); } private static bool IsValidSlugFormat(string slug) { return Regex.IsMatch(slug, @"^[a-z0-9-]+$") && !slug.StartsWith('-') && !slug.EndsWith('-'); } private static string ExtractDomain(string url) { try { var uri = new Uri(url); return uri.Host; } catch { return string.Empty; } } private static string GetDeviceType(string userAgent) { userAgent = userAgent.ToLowerInvariant(); if (userAgent.Contains("mobile") || userAgent.Contains("android") || userAgent.Contains("iphone")) return "mobile"; if (userAgent.Contains("tablet") || userAgent.Contains("ipad")) return "tablet"; return "desktop"; } }