using Microsoft.AspNetCore.Mvc; using QRRapidoApp.Models.ViewModels; using QRRapidoApp.Services; using System.Diagnostics; using System.Security.Claims; using System.Text; namespace QRRapidoApp.Controllers { [ApiController] [Route("api/[controller]")] public class QRController : ControllerBase { private readonly IQRCodeService _qrService; private readonly IUserService _userService; private readonly AdDisplayService _adService; private readonly ILogger _logger; public QRController(IQRCodeService qrService, IUserService userService, AdDisplayService adService, ILogger logger) { _qrService = qrService; _userService = userService; _adService = adService; _logger = logger; } [HttpPost("GenerateRapid")] public async Task GenerateRapid([FromBody] QRGenerationRequest request) { var stopwatch = Stopwatch.StartNew(); var requestId = Guid.NewGuid().ToString("N")[..8]; var userId = User?.FindFirst(ClaimTypes.NameIdentifier)?.Value; var isAuthenticated = User?.Identity?.IsAuthenticated ?? false; using (_logger.BeginScope(new Dictionary { ["RequestId"] = requestId, ["UserId"] = userId ?? "anonymous", ["IsAuthenticated"] = isAuthenticated, ["QRType"] = request.Type ?? "unknown", ["ContentLength"] = request.Content?.Length ?? 0, ["QRGeneration"] = true })) { _logger.LogInformation("QR generation request started - Type: {QRType}, ContentLength: {ContentLength}, User: {UserType}", request.Type, request.Content?.Length ?? 0, isAuthenticated ? "authenticated" : "anonymous"); try { // Quick validations if (string.IsNullOrWhiteSpace(request.Content)) { _logger.LogWarning("QR generation failed - empty content provided"); return BadRequest(new { error = "Conteúdo é obrigatório", success = false }); } if (request.Content.Length > 4000) // Limit to maintain speed { _logger.LogWarning("QR generation failed - content too long: {ContentLength} characters", request.Content.Length); return BadRequest(new { error = "Conteúdo muito longo. Máximo 4000 caracteres.", success = false }); } // Check user status var user = await _userService.GetUserAsync(userId); // Validate premium features if (!string.IsNullOrEmpty(request.CornerStyle) && request.CornerStyle != "square" && user?.IsPremium != true) { _logger.LogWarning("Custom corner style attempted by non-premium user - UserId: {UserId}, CornerStyle: {CornerStyle}", userId ?? "anonymous", request.CornerStyle); return BadRequest(new { error = "Estilos de borda personalizados são exclusivos do plano Premium. Faça upgrade para usar esta funcionalidade.", requiresPremium = true, success = false }); } // Rate limiting for free users var rateLimitPassed = await CheckRateLimitAsync(userId, user); if (!rateLimitPassed) { _logger.LogWarning("QR generation rate limited - User: {UserId}, IsPremium: {IsPremium}", userId ?? "anonymous", user?.IsPremium ?? false); return StatusCode(429, new { error = "Limite de QR codes atingido", upgradeUrl = "/Premium/Upgrade", success = false }); } // Configure optimizations based on user request.IsPremium = user?.IsPremium == true; request.OptimizeForSpeed = true; _logger.LogDebug("Generating QR code - IsPremium: {IsPremium}, OptimizeForSpeed: {OptimizeForSpeed}", request.IsPremium, request.OptimizeForSpeed); // Generate QR code var generationStopwatch = Stopwatch.StartNew(); var result = await _qrService.GenerateRapidAsync(request); generationStopwatch.Stop(); if (!result.Success) { _logger.LogError("QR generation failed - Error: {ErrorMessage}, GenerationTime: {GenerationTimeMs}ms", result.ErrorMessage, generationStopwatch.ElapsedMilliseconds); return StatusCode(500, new { error = result.ErrorMessage, success = false }); } _logger.LogInformation("QR code generated successfully - GenerationTime: {GenerationTimeMs}ms, FromCache: {FromCache}, Size: {Size}px", generationStopwatch.ElapsedMilliseconds, result.FromCache, request.Size); // Update counter for free users if (!request.IsPremium && userId != null) { var remaining = await _userService.DecrementDailyQRCountAsync(userId); result.RemainingQRs = remaining; _logger.LogDebug("Updated QR count for free user - Remaining: {RemainingQRs}", remaining); } // Save to history if user is logged in (fire and forget) if (userId != null) { _ = Task.Run(async () => { try { await _userService.SaveQRToHistoryAsync(userId, result); _logger.LogDebug("QR code saved to history successfully for user {UserId}", userId); } catch (Exception ex) { _logger.LogError(ex, "Error saving QR to history for user {UserId}", userId); } }); } stopwatch.Stop(); var totalTimeMs = stopwatch.ElapsedMilliseconds; // Performance logging with structured data using (_logger.BeginScope(new Dictionary { ["TotalRequestTimeMs"] = totalTimeMs, ["QRGenerationTimeMs"] = generationStopwatch.ElapsedMilliseconds, ["ServiceGenerationTimeMs"] = result.GenerationTimeMs, ["FromCache"] = result.FromCache, ["UserType"] = request.IsPremium ? "premium" : "free", ["QRSize"] = request.Size, ["Success"] = true })) { var performanceStatus = totalTimeMs switch { < 500 => "excellent", < 1000 => "good", < 2000 => "acceptable", _ => "slow" }; _logger.LogInformation("QR generation completed - TotalTime: {TotalTimeMs}ms, ServiceTime: {ServiceTimeMs}ms, Performance: {PerformanceStatus}, Cache: {FromCache}", totalTimeMs, result.GenerationTimeMs, performanceStatus, result.FromCache); } return Ok(result); } catch (Exception ex) { stopwatch.Stop(); _logger.LogError(ex, "QR generation failed with exception - RequestTime: {RequestTimeMs}ms, UserId: {UserId}", stopwatch.ElapsedMilliseconds, userId ?? "anonymous"); return StatusCode(500, new { error = "Erro interno do servidor", success = false }); } } } [HttpGet("Download/{qrId}")] public async Task Download(string qrId, string format = "png") { var userId = User?.FindFirst(ClaimTypes.NameIdentifier)?.Value; var stopwatch = Stopwatch.StartNew(); using (_logger.BeginScope(new Dictionary { ["QRId"] = qrId, ["Format"] = format.ToLower(), ["UserId"] = userId ?? "anonymous", ["QRDownload"] = true })) { _logger.LogInformation("QR download requested - QRId: {QRId}, Format: {Format}", qrId, format); try { var qrData = await _userService.GetQRDataAsync(qrId); if (qrData == null) { _logger.LogWarning("QR download failed - QR code not found: {QRId}", qrId); return NotFound(); } var contentType = format.ToLower() switch { "svg" => "image/svg+xml", "pdf" => "application/pdf", _ => "image/png" }; var fileName = $"qrrapido-{DateTime.Now:yyyyMMdd-HHmmss}.{format}"; _logger.LogDebug("Converting QR to format - QRId: {QRId}, Format: {Format}, Size: {Size}", qrId, format, qrData.Size); byte[] fileContent; if (format.ToLower() == "svg") { fileContent = await _qrService.ConvertToSvgAsync(qrData.QRCodeBase64); } else if (format.ToLower() == "pdf") { fileContent = await _qrService.ConvertToPdfAsync(qrData.QRCodeBase64, qrData.Size); } else { fileContent = Convert.FromBase64String(qrData.QRCodeBase64); } stopwatch.Stop(); _logger.LogInformation("QR download completed - QRId: {QRId}, Format: {Format}, Size: {FileSize} bytes, ProcessingTime: {ProcessingTimeMs}ms", qrId, format, fileContent.Length, stopwatch.ElapsedMilliseconds); return File(fileContent, contentType, fileName); } catch (Exception ex) { stopwatch.Stop(); _logger.LogError(ex, "QR download failed - QRId: {QRId}, Format: {Format}, ProcessingTime: {ProcessingTimeMs}ms", qrId, format, stopwatch.ElapsedMilliseconds); return StatusCode(500); } } } [HttpPost("SaveToHistory")] public async Task SaveToHistory([FromBody] SaveToHistoryRequest request) { var userId = User?.FindFirst(ClaimTypes.NameIdentifier)?.Value; using (_logger.BeginScope(new Dictionary { ["QRId"] = request.QrId, ["UserId"] = userId ?? "anonymous", ["SaveToHistory"] = true })) { _logger.LogInformation("Save to history requested - QRId: {QRId}", request.QrId); try { if (string.IsNullOrEmpty(userId)) { _logger.LogWarning("Save to history failed - user not authenticated"); return Unauthorized(); } var qrData = await _userService.GetQRDataAsync(request.QrId); if (qrData == null) { _logger.LogWarning("Save to history failed - QR code not found: {QRId}", request.QrId); return NotFound(); } // QR is already saved when generated, just return success _logger.LogInformation("QR code already saved in history - QRId: {QRId}", request.QrId); return Ok(new { success = true, message = "QR Code salvo no histórico!" }); } catch (Exception ex) { _logger.LogError(ex, "Save to history failed - QRId: {QRId}", request.QrId); return StatusCode(500, new { error = "Erro ao salvar no histórico." }); } } } [HttpPost("GenerateRapidWithLogo")] public async Task GenerateRapidWithLogo([FromForm] QRGenerationRequest request, IFormFile? logo) { var stopwatch = Stopwatch.StartNew(); var requestId = Guid.NewGuid().ToString("N")[..8]; var userId = User?.FindFirst(ClaimTypes.NameIdentifier)?.Value; var isAuthenticated = User?.Identity?.IsAuthenticated ?? false; using (_logger.BeginScope(new Dictionary { ["RequestId"] = requestId, ["UserId"] = userId ?? "anonymous", ["IsAuthenticated"] = isAuthenticated, ["QRType"] = request.Type ?? "unknown", ["ContentLength"] = request.Content?.Length ?? 0, ["QRGeneration"] = true, ["HasLogo"] = logo != null })) { _logger.LogInformation("QR generation with logo request started - Type: {QRType}, ContentLength: {ContentLength}, HasLogo: {HasLogo}", request.Type, request.Content?.Length ?? 0, logo != null); try { // Quick validations if (string.IsNullOrWhiteSpace(request.Content)) { _logger.LogWarning("QR generation failed - empty content provided"); return BadRequest(new { error = "Conteúdo é obrigatório", success = false }); } if (request.Content.Length > 4000) { _logger.LogWarning("QR generation failed - content too long: {ContentLength} characters", request.Content.Length); return BadRequest(new { error = "Conteúdo muito longo. Máximo 4000 caracteres.", success = false }); } // Check user status var user = await _userService.GetUserAsync(userId); // Validate premium status for logo feature if (user?.IsPremium != true) { _logger.LogWarning("Logo upload attempted by non-premium user - UserId: {UserId}", userId ?? "anonymous"); return BadRequest(new { error = "Logo personalizado é exclusivo do plano Premium. Faça upgrade para usar esta funcionalidade.", requiresPremium = true, success = false }); } // Validate premium corner styles if (!string.IsNullOrEmpty(request.CornerStyle) && request.CornerStyle != "square") { _logger.LogInformation("Premium user using custom corner style - UserId: {UserId}, CornerStyle: {CornerStyle}", userId, request.CornerStyle); } // Process logo upload if provided if (logo != null && logo.Length > 0) { // Validate file size (2MB max) if (logo.Length > 2 * 1024 * 1024) { _logger.LogWarning("Logo upload failed - file too large: {FileSize} bytes", logo.Length); return BadRequest(new { error = "Logo muito grande. Máximo 2MB.", success = false }); } // Validate file format var allowedTypes = new[] { "image/png", "image/jpeg", "image/jpg" }; if (!allowedTypes.Contains(logo.ContentType?.ToLower())) { _logger.LogWarning("Logo upload failed - invalid format: {ContentType}", logo.ContentType); return BadRequest(new { error = "Formato inválido. Use PNG ou JPG.", success = false }); } try { // Convert file to byte array using var memoryStream = new MemoryStream(); await logo.CopyToAsync(memoryStream); request.Logo = memoryStream.ToArray(); request.HasLogo = true; _logger.LogInformation("Logo processed successfully - Size: {LogoSize} bytes, Format: {ContentType}", logo.Length, logo.ContentType); } catch (Exception ex) { _logger.LogError(ex, "Error processing logo file"); return BadRequest(new { error = "Erro ao processar logo.", success = false }); } } // Rate limiting for free users (premium users get unlimited) var rateLimitPassed = await CheckRateLimitAsync(userId, user); if (!rateLimitPassed) { _logger.LogWarning("QR generation rate limited - User: {UserId}, IsPremium: {IsPremium}", userId ?? "anonymous", user?.IsPremium ?? false); return StatusCode(429, new { error = "Limite de QR codes atingido", upgradeUrl = "/Premium/Upgrade", success = false }); } // Configure optimizations based on user request.IsPremium = user?.IsPremium == true; request.OptimizeForSpeed = true; _logger.LogDebug("Generating QR code with logo - IsPremium: {IsPremium}, HasLogo: {HasLogo}", request.IsPremium, request.HasLogo); // Generate QR code var generationStopwatch = Stopwatch.StartNew(); var result = await _qrService.GenerateRapidAsync(request); generationStopwatch.Stop(); if (!result.Success) { _logger.LogError("QR generation failed - Error: {ErrorMessage}, GenerationTime: {GenerationTimeMs}ms", result.ErrorMessage, generationStopwatch.ElapsedMilliseconds); return StatusCode(500, new { error = result.ErrorMessage, success = false }); } _logger.LogInformation("QR code with logo generated successfully - GenerationTime: {GenerationTimeMs}ms, FromCache: {FromCache}, HasLogo: {HasLogo}", generationStopwatch.ElapsedMilliseconds, result.FromCache, request.HasLogo); // Save to history if user is logged in (fire and forget) if (userId != null) { _ = Task.Run(async () => { try { await _userService.SaveQRToHistoryAsync(userId, result); _logger.LogDebug("QR code saved to history successfully for user {UserId}", userId); } catch (Exception ex) { _logger.LogError(ex, "Error saving QR to history for user {UserId}", userId); } }); } stopwatch.Stop(); return Ok(result); } catch (Exception ex) { stopwatch.Stop(); _logger.LogError(ex, "QR generation with logo failed with exception - RequestTime: {RequestTimeMs}ms, UserId: {UserId}", stopwatch.ElapsedMilliseconds, userId ?? "anonymous"); return StatusCode(500, new { error = "Erro interno do servidor", success = false }); } } } [HttpGet("History")] public async Task GetHistory(int limit = 20) { try { var userId = User?.FindFirst(ClaimTypes.NameIdentifier)?.Value; if (string.IsNullOrEmpty(userId)) { return Unauthorized(); } var history = await _userService.GetUserQRHistoryAsync(userId, limit); return Ok(history); } catch (Exception ex) { _logger.LogError(ex, "Error getting QR history"); return StatusCode(500); } } private async Task CheckRateLimitAsync(string? userId, Models.User? user) { if (user?.IsPremium == true) return true; var dailyLimit = userId != null ? 50 : 10; // Logged in: 50/day, Anonymous: 10/day var currentCount = await _userService.GetDailyQRCountAsync(userId); return currentCount < dailyLimit; } } public class SaveToHistoryRequest { public string QrId { get; set; } = string.Empty; } }