using Microsoft.Extensions.Caching.Distributed; using QRCoder; using QRRapidoApp.Models.ViewModels; using System.Diagnostics; using System.Drawing; using System.Security.Cryptography; using System.Text; using System.Text.Json; namespace QRRapidoApp.Services { public class QRRapidoService : IQRCodeService { private readonly IDistributedCache _cache; private readonly IConfiguration _config; private readonly ILogger _logger; private readonly SemaphoreSlim _semaphore; public QRRapidoService(IDistributedCache cache, IConfiguration config, ILogger logger) { _cache = cache; _config = config; _logger = logger; // Limit simultaneous generations to maintain performance var maxConcurrent = _config.GetValue("Performance:MaxConcurrentGenerations", 100); _semaphore = new SemaphoreSlim(maxConcurrent, maxConcurrent); } public async Task GenerateRapidAsync(QRGenerationRequest request) { var stopwatch = Stopwatch.StartNew(); try { await _semaphore.WaitAsync(); // Cache key based on content and settings var cacheKey = GenerateCacheKey(request); var cached = await _cache.GetStringAsync(cacheKey); if (!string.IsNullOrEmpty(cached)) { stopwatch.Stop(); _logger.LogInformation($"QR code served from cache in {stopwatch.ElapsedMilliseconds}ms"); var cachedResult = JsonSerializer.Deserialize(cached); if (cachedResult != null) { cachedResult.GenerationTimeMs = stopwatch.ElapsedMilliseconds; cachedResult.FromCache = true; return cachedResult; } } // Optimized generation var qrCode = await GenerateQRCodeOptimizedAsync(request); var base64 = Convert.ToBase64String(qrCode); var result = new QRGenerationResult { QRCodeBase64 = base64, QRId = Guid.NewGuid().ToString(), GenerationTimeMs = stopwatch.ElapsedMilliseconds, FromCache = false, Size = qrCode.Length, RequestSettings = request, Success = true }; // Cache for configurable time var cacheExpiration = TimeSpan.FromMinutes(_config.GetValue("Performance:CacheExpirationMinutes", 60)); await _cache.SetStringAsync(cacheKey, JsonSerializer.Serialize(result), new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = cacheExpiration }); stopwatch.Stop(); _logger.LogInformation($"QR code generated in {stopwatch.ElapsedMilliseconds}ms for type {request.Type}"); return result; } catch (Exception ex) { _logger.LogError(ex, $"Error in rapid QR code generation: {ex.Message}"); return new QRGenerationResult { Success = false, ErrorMessage = ex.Message, GenerationTimeMs = stopwatch.ElapsedMilliseconds }; } finally { _semaphore.Release(); } } private async Task GenerateQRCodeOptimizedAsync(QRGenerationRequest request) { return await Task.Run(() => { using var qrGenerator = new QRCodeGenerator(); using var qrCodeData = qrGenerator.CreateQrCode(request.Content, GetErrorCorrectionLevel(request)); // Optimized settings for speed using var qrCode = new PngByteQRCode(qrCodeData); // Apply optimizations based on user type var pixelsPerModule = request.IsPremium ? GetOptimalPixelsPerModule(request.Size) : Math.Max(8, request.Size / 40); // Lower quality for free users, but faster var primaryColorBytes = ColorToBytes(ParseHtmlColor(request.PrimaryColor)); var backgroundColorBytes = ColorToBytes(ParseHtmlColor(request.BackgroundColor)); return qrCode.GetGraphic(pixelsPerModule, primaryColorBytes, backgroundColorBytes); }); } private QRCodeGenerator.ECCLevel GetErrorCorrectionLevel(QRGenerationRequest request) { // Lower error correction = faster generation if (request.OptimizeForSpeed) { return QRCodeGenerator.ECCLevel.L; // ~7% correction } return request.HasLogo ? QRCodeGenerator.ECCLevel.H : // ~30% correction for logos QRCodeGenerator.ECCLevel.M; // ~15% correction default } private int GetOptimalPixelsPerModule(int targetSize) { // Optimized algorithm for best quality/speed ratio return targetSize switch { <= 200 => 8, <= 300 => 12, <= 500 => 16, <= 800 => 20, _ => 24 }; } private string GenerateCacheKey(QRGenerationRequest request) { var keyData = $"{request.Content}|{request.Type}|{request.Size}|{request.PrimaryColor}|{request.BackgroundColor}|{request.QuickStyle}|{request.CornerStyle}|{request.Margin}"; using var sha256 = SHA256.Create(); var hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(keyData)); return $"qr_rapid_{Convert.ToBase64String(hash)[..16]}"; } public async Task ConvertToSvgAsync(string qrCodeBase64) { return await Task.Run(() => { // Convert PNG to SVG (simplified implementation) var svgContent = $@" "; return Encoding.UTF8.GetBytes(svgContent); }); } public async Task ConvertToPdfAsync(string qrCodeBase64, int size = 300) { return await Task.Run(() => { // Simplified PDF generation - in real implementation, use iTextSharp or similar var pdfHeader = "%PDF-1.4\n"; var pdfBody = $"1 0 obj<>endobj\n" + $"2 0 obj<>endobj\n" + $"3 0 obj<>endobj\n" + $"xref\n0 4\n0000000000 65535 f \n0000000010 00000 n \n0000000053 00000 n \n0000000125 00000 n \n"; var pdfContent = pdfBody + $"trailer<>\nstartxref\n{pdfHeader.Length + pdfBody.Length}\n%%EOF"; return Encoding.UTF8.GetBytes(pdfHeader + pdfContent); }); } public async Task GenerateDynamicQRAsync(QRGenerationRequest request, string userId) { // For premium users only - dynamic QR codes that can be edited var dynamicId = Guid.NewGuid().ToString(); var dynamicUrl = $"https://qrrapido.site/d/{dynamicId}"; // Store mapping in cache/database var cacheKey = $"dynamic_qr_{dynamicId}"; await _cache.SetStringAsync(cacheKey, request.Content, new DistributedCacheEntryOptions { SlidingExpiration = TimeSpan.FromDays(365) // Long-lived for premium users }); return dynamicId; } public async Task UpdateDynamicQRAsync(string qrId, string newContent) { try { var cacheKey = $"dynamic_qr_{qrId}"; await _cache.SetStringAsync(cacheKey, newContent, new DistributedCacheEntryOptions { SlidingExpiration = TimeSpan.FromDays(365) }); return true; } catch (Exception ex) { _logger.LogError(ex, $"Failed to update dynamic QR {qrId}: {ex.Message}"); return false; } } private System.Drawing.Color ParseHtmlColor(string htmlColor) { if (string.IsNullOrEmpty(htmlColor) || !htmlColor.StartsWith("#")) { return System.Drawing.Color.Black; } try { if (htmlColor.Length == 7) // #RRGGBB { var r = Convert.ToByte(htmlColor.Substring(1, 2), 16); var g = Convert.ToByte(htmlColor.Substring(3, 2), 16); var b = Convert.ToByte(htmlColor.Substring(5, 2), 16); return System.Drawing.Color.FromArgb(r, g, b); } else if (htmlColor.Length == 4) // #RGB { var r = Convert.ToByte(htmlColor.Substring(1, 1) + htmlColor.Substring(1, 1), 16); var g = Convert.ToByte(htmlColor.Substring(2, 1) + htmlColor.Substring(2, 1), 16); var b = Convert.ToByte(htmlColor.Substring(3, 1) + htmlColor.Substring(3, 1), 16); return System.Drawing.Color.FromArgb(r, g, b); } } catch { return System.Drawing.Color.Black; } return System.Drawing.Color.Black; } private byte[] ColorToBytes(System.Drawing.Color color) { return new byte[] { color.R, color.G, color.B }; } } }