using System.Collections.Concurrent; using Documents.Interfaces; using Documents.Models; using Domain.Dtos; // QuoteDto using Domain.Dtos.Sales; // DeliveryNoteDto using Domain.Dtos.Stock; // ExpeditionDto using Transversal.Interfaces; public class DocumentTemplateService : IDocumentTemplateService { private readonly ITemplateRenderer _templateRenderer; private readonly IPdfGeneratorService _pdfGeneratorService; // Cache simple para no leer el logo del disco en cada render private static readonly ConcurrentDictionary _imageCacheBase64 = new(); public DocumentTemplateService(ITemplateRenderer templateRenderer, IPdfGeneratorService pdfGeneratorService) { _templateRenderer = templateRenderer; _pdfGeneratorService = pdfGeneratorService; } public async Task GenerateDocumentAsync(DocumentGenerationRequest request) { if (request is null) throw new ArgumentNullException(nameof(request)); if (request.Model is null) throw new ArgumentNullException(nameof(request.Model)); string? templatePath = null; try { // 1) Elegir plantilla por tipo de documento templatePath = ResolveTemplate(request.DocumentType); // 2) Inyectar logo (si el DTO lo soporta) var logoBase64 = GetImageBase64Cached( Path.Combine(Directory.GetCurrentDirectory(), "Resources", "logo.png")); InjectLogoIfSupported(request.Model, logoBase64); // 3) Render + PDF var html = await _templateRenderer.RenderAsync(templatePath, request.Model); return await _pdfGeneratorService.GeneratePdfFromHtmlAsync(html); } catch (Exception ex) { // Envolvemos con contexto para facilitar el diagnóstico var wrapped = new Exception( $"Document generation failed (DocumentType={request.DocumentType}, Template='{templatePath ?? "?"}', ModelType={request.Model.GetType().FullName}). See inner exception.", ex ); wrapped.Data["DocumentType"] = request.DocumentType.ToString(); if (!string.IsNullOrEmpty(templatePath)) wrapped.Data["TemplatePath"] = templatePath; wrapped.Data["ModelType"] = request.Model.GetType().FullName; throw wrapped; } } private static string ResolveTemplate(DocumentType type) => type switch { DocumentType.Quote => "Quotes/Template_v1.cshtml", DocumentType.DeliveryNote => "DeliveryNotes/Template_v1.cshtml", DocumentType.Expedition => "Expeditions/Template_v1.cshtml", _ => "Shared/Template_Generic.cshtml" }; private static void InjectLogoIfSupported(object model, string base64) { // Inyección “segura”: si el modelo expone LogoBase64, lo seteamos. switch (model) { case QuoteDto q: q.LogoBase64 = base64; break; case ExpeditionDto e: e.LogoBase64 = base64; break; case DeliveryNoteDto d: d.LogoBase64 = base64; break; default: // Si no tiene LogoBase64, no hacemos nada. break; } } private static string GetImageBase64Cached(string imagePath) { if (_imageCacheBase64.TryGetValue(imagePath, out var cached)) return cached; if (!File.Exists(imagePath)) { _imageCacheBase64[imagePath] = ""; return ""; } var base64 = Convert.ToBase64String(File.ReadAllBytes(imagePath)); _imageCacheBase64[imagePath] = base64; return base64; } }