using System.Globalization;
using System.Text.Json;
using System.Text.RegularExpressions;
using Apollinare.Domain.Entities;
using Apollinare.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
using SkiaSharp;

namespace Apollinare.API.Services.Reports;

public class ReportGeneratorService
{
    private readonly AppollinareDbContext _context;
    private readonly ILogger<ReportGeneratorService> _logger;

    private static readonly Regex BindingRegex = new(@"\{\{([^}]+)\}\}", RegexOptions.Compiled);

    // Conversion factor: 1mm = 3.7795275591 px at 96 DPI
    // This matches the frontend MM_TO_PX constant exactly
    private const float MM_TO_PX = 3.7795275591f;

    public ReportGeneratorService(AppollinareDbContext context, ILogger<ReportGeneratorService> logger)
    {
        _context = context;
        _logger = logger;

        // Configure QuestPDF license (Community is free for revenue < $1M)
        QuestPDF.Settings.License = LicenseType.Community;

        // Enable debugging for development
        QuestPDF.Settings.EnableDebugging = true;
    }

    public async Task<byte[]> GeneratePdfAsync(int templateId, Dictionary<string, object> dataContext)
    {
        var template = await _context.ReportTemplates.FindAsync(templateId);
        if (template == null)
            throw new ArgumentException($"Template with ID {templateId} not found");

        var aprt = JsonSerializer.Deserialize<AprtTemplate>(template.TemplateJson)
            ?? throw new InvalidOperationException("Invalid template JSON");

        // Load resources (fonts and images)
        var resources = await LoadResourcesAsync(aprt);

        // Migrate legacy templates without pages
        MigrateTemplatePages(aprt);

        _logger.LogInformation("Generating PDF with {PageCount} pages, {ElementCount} total elements",
            aprt.Pages.Count, aprt.Elements.Count(e => e.Visible));

        // Debug: log all pages and elements pageIds
        _logger.LogInformation("Pages in template: {Pages}", string.Join(", ", aprt.Pages.Select(p => $"{p.Name}({p.Id})")));
        _logger.LogInformation("Elements pageIds: {PageIds}", string.Join(", ", aprt.Elements.Select(e => $"{e.Name ?? e.Id}->'{e.PageId ?? "NULL"}'")));

        const float dpi = 300f;
        var totalPages = aprt.Pages.Count;

        // Pre-render all pages to avoid closure issues in QuestPDF lambda
        var pageRenderData = new List<(float pageWidth, float pageHeight, string? bgColor, byte[] imageBytes)>();

        for (int pageIndex = 0; pageIndex < aprt.Pages.Count; pageIndex++)
        {
            var pageDefinition = aprt.Pages[pageIndex];
            var currentPageNumber = pageIndex + 1;

            // Determine page settings (use page override or template default)
            var pageSize = pageDefinition.PageSize ?? aprt.Meta.PageSize;
            var orientation = pageDefinition.Orientation ?? aprt.Meta.Orientation;
            var pageDimensions = GetPageDimensionsMm(pageSize, orientation);
            var pageWidth = pageDimensions.Width;
            var pageHeight = pageDimensions.Height;

            // Get visible elements for this page
            var pageElements = aprt.Elements
                .Where(e => e.Visible && (e.PageId == pageDefinition.Id ||
                    (string.IsNullOrEmpty(e.PageId) && pageDefinition.Id == aprt.Pages[0].Id)))
                .ToList();

            _logger.LogInformation("Page '{PageName}' ({PageId}): {ElementCount} elements (of {TotalElements} total), Size: {Width}x{Height}mm, Page {CurrentPage}/{TotalPages}",
                pageDefinition.Name, pageDefinition.Id, pageElements.Count, aprt.Elements.Count,
                pageWidth, pageHeight, currentPageNumber, totalPages);
            _logger.LogInformation("  Elements on this page: {Elements}",
                string.Join(", ", pageElements.Select(e => e.Name ?? e.Id)));

            // Render full page to bitmap (element coordinates are relative to entire page)
            // Pass page context for special variables
            var pageContext = new PageContext { PageNumber = currentPageNumber, TotalPages = totalPages };
            var pageImageBytes = RenderContentToBitmap(
                pageElements, pageWidth, pageHeight, dataContext, resources, dpi,
                pageDefinition.BackgroundColor, pageContext);

            pageRenderData.Add((pageWidth, pageHeight, pageDefinition.BackgroundColor, pageImageBytes));
        }

        // Generate PDF using QuestPDF with pre-rendered pages
        var document = Document.Create(container =>
        {
            foreach (var (pageWidth, pageHeight, bgColor, imageBytes) in pageRenderData)
            {
                container.Page(page =>
                {
                    // Set page size - no margins since we render the full page as bitmap
                    page.Size(pageWidth, pageHeight, Unit.Millimetre);
                    page.Margin(0);

                    // Content - use the rendered bitmap for the full page
                    page.Content()
                        .Image(imageBytes)
                        .FitArea();
                });
            }
        });

        return document.GeneratePdf();
    }

    /// <summary>
    /// Context for page-level special variables during rendering
    /// </summary>
    private class PageContext
    {
        public int PageNumber { get; set; }
        public int TotalPages { get; set; }
    }

    /// <summary>
    /// Migrates legacy templates without pages to the new multi-page format.
    /// Creates a default page and assigns all orphan elements to it.
    /// </summary>
    private static void MigrateTemplatePages(AprtTemplate aprt)
    {
        if (aprt.Pages == null || aprt.Pages.Count == 0)
        {
            // Create default page
            aprt.Pages = new List<AprtPage>
            {
                new AprtPage { Id = "page-1", Name = "Pagina 1" }
            };
        }

        // Assign orphan elements (without pageId) to the first page
        var firstPageId = aprt.Pages[0].Id;
        foreach (var element in aprt.Elements.Where(e => string.IsNullOrEmpty(e.PageId)))
        {
            element.PageId = firstPageId;
        }
    }

    /// <summary>
    /// Renders all content to a high-resolution bitmap using SkiaSharp.
    /// This ensures images and all elements are rendered exactly as designed.
    /// </summary>
    private byte[] RenderContentToBitmap(List<AprtElement> elements, float contentWidthMm, float contentHeightMm,
        Dictionary<string, object> dataContext, Dictionary<string, object> resources, float dpi,
        string? backgroundColor = null, PageContext? pageContext = null)
    {
        // Use a SINGLE uniform scale for both axes - critical for correct element positioning
        // With FitArea(), the bitmap will fill the page maintaining proportions
        var scale = dpi / 25.4f; // pixels per mm at target DPI

        var widthPx = (int)Math.Round(contentWidthMm * scale);
        var heightPx = (int)Math.Round(contentHeightMm * scale);

        // Use the SAME scale for both X and Y
        var scaleX = scale;
        var scaleY = scale;

        _logger.LogDebug("Rendering bitmap: {Width}x{Height}px, scale: {Scale} px/mm, page: {PageW}x{PageH}mm",
            widthPx, heightPx, scale, contentWidthMm, contentHeightMm);

        using var surface = SKSurface.Create(new SKImageInfo(widthPx, heightPx, SKColorType.Rgba8888, SKAlphaType.Premul));
        var canvas = surface.Canvas;

        // Page background color (default: white)
        var bgColor = !string.IsNullOrEmpty(backgroundColor) ? ParseColor(backgroundColor) : SKColors.White;
        canvas.Clear(bgColor);

        // Render each element using consistent scale factors
        foreach (var element in elements.OrderBy(e => elements.IndexOf(e)))
        {
            RenderElementToCanvas(canvas, element, scaleX, scaleY, dataContext, resources, pageContext);
        }

        // Encode to PNG
        using var image = surface.Snapshot();
        using var data = image.Encode(SKEncodedImageFormat.Png, 100);
        return data.ToArray();
    }

    /// <summary>
    /// Renders a single element to the SkiaSharp canvas.
    ///
    /// IMPORTANT: Fabric.js coordinate system explanation:
    /// - With originX='left' and originY='top' (default), left/top represent the top-left corner
    ///   of the object's bounding box BEFORE rotation
    /// - When rotated, the object rotates around its center
    /// - The saved left/top values are the position of the top-left corner AFTER rotation
    ///   (i.e., the rotated origin point's position on canvas)
    ///
    /// To render correctly:
    /// 1. Calculate the center of the object using the rotated origin formula
    /// 2. Rotate canvas around this center
    /// 3. Draw the element centered on this point (offset by -width/2, -height/2)
    /// </summary>
    private void RenderElementToCanvas(SKCanvas canvas, AprtElement element, float scaleX, float scaleY,
        Dictionary<string, object> dataContext, Dictionary<string, object> resources, PageContext? pageContext = null)
    {
        // Convert mm to pixels
        var left = element.Position.X * scaleX;
        var top = element.Position.Y * scaleY;
        var width = element.Position.Width * scaleX;
        var height = element.Position.Height * scaleY;
        var angleRad = element.Position.Rotation * (float)Math.PI / 180f;

        // Calculate the center of the object in canvas coordinates
        // In Fabric.js with originX='left', originY='top':
        // The saved (left, top) is the position of the top-left corner after rotation
        // Center = left + rotated(width/2, height/2)
        var halfWidth = width / 2;
        var halfHeight = height / 2;
        var cos = (float)Math.Cos(angleRad);
        var sin = (float)Math.Sin(angleRad);

        var centerX = left + halfWidth * cos - halfHeight * sin;
        var centerY = top + halfWidth * sin + halfHeight * cos;

        // Calculate where to draw the element (top-left of unrotated rect centered on centerX/Y)
        var drawX = centerX - halfWidth;
        var drawY = centerY - halfHeight;

        _logger.LogDebug("Rendering element {Id} type={Type}: left={Left}, top={Top}, size={W}x{H}, angle={Angle}° -> center=({CX},{CY}), draw=({DX},{DY})",
            element.Id, element.Type,
            left, top, width, height, element.Position.Rotation,
            centerX, centerY, drawX, drawY);

        // Save canvas state for rotation
        canvas.Save();

        // Apply rotation around the center of the object
        if (element.Position.Rotation != 0)
        {
            canvas.RotateDegrees(element.Position.Rotation, centerX, centerY);
        }

        var style = element.Style;

        switch (element.Type.ToLower())
        {
            case "shape":
                RenderShapeToCanvas(canvas, drawX, drawY, width, height, style);
                break;

            case "line":
                RenderLineToCanvas(canvas, drawX, drawY, width, height, style);
                break;

            case "text":
                RenderTextToCanvas(canvas, element, drawX, drawY, width, height, scaleX, scaleY, dataContext, pageContext);
                break;

            case "image":
                RenderImageToCanvas(canvas, element, drawX, drawY, width, height, resources);
                break;

            case "table":
                RenderTableToCanvas(canvas, element, drawX, drawY, width, height, scaleX, scaleY, dataContext);
                break;
        }

        // Restore canvas state
        canvas.Restore();
    }

    private void RenderShapeToCanvas(SKCanvas canvas, float x, float y, float width, float height, AprtStyle? style)
    {
        var rect = new SKRect(x, y, x + width, y + height);

        // Fill
        var bgColor = style?.BackgroundColor ?? "transparent";
        if (!string.IsNullOrEmpty(bgColor) && bgColor.ToLower() != "transparent")
        {
            using var fillPaint = new SKPaint
            {
                Style = SKPaintStyle.Fill,
                Color = ParseColor(bgColor),
                IsAntialias = true
            };
            canvas.DrawRect(rect, fillPaint);
        }

        // Stroke
        var strokeWidth = style?.BorderWidth ?? 0;
        if (strokeWidth > 0)
        {
            using var strokePaint = new SKPaint
            {
                Style = SKPaintStyle.Stroke,
                Color = ParseColor(style?.BorderColor ?? "#000000"),
                StrokeWidth = strokeWidth,
                IsAntialias = true
            };
            canvas.DrawRect(rect, strokePaint);
        }
    }

    private void RenderLineToCanvas(SKCanvas canvas, float x, float y, float width, float height, AprtStyle? style)
    {
        var lineY = y + height / 2;
        var lineWidth = Math.Max(1f, style?.BorderWidth ?? 1);

        using var paint = new SKPaint
        {
            Style = SKPaintStyle.Stroke,
            Color = ParseColor(style?.Color ?? "#000000"),
            StrokeWidth = lineWidth,
            IsAntialias = true
        };
        canvas.DrawLine(x, lineY, x + width, lineY, paint);
    }

    private void RenderTextToCanvas(SKCanvas canvas, AprtElement element, float x, float y, float width, float height,
        float scaleX, float scaleY, Dictionary<string, object> dataContext, PageContext? pageContext = null)
    {
        var style = element.Style;
        var text = ResolveContent(element.Content, dataContext, pageContext);
        if (string.IsNullOrEmpty(text)) return;

        // Use average scale for font size to maintain proportions
        var avgScale = (scaleX + scaleY) / 2f;

        // Font size: template stores screen pixels at 96 DPI
        // Convert: fontSize (screen px) -> mm -> render px
        // 1 screen px at 96 DPI = 25.4/96 = 0.2646 mm
        var fontSizePx = (style?.FontSize ?? 12) * 0.2646f * avgScale;

        // Background
        if (!string.IsNullOrEmpty(style?.BackgroundColor) && style.BackgroundColor.ToLower() != "transparent")
        {
            using var bgPaint = new SKPaint
            {
                Style = SKPaintStyle.Fill,
                Color = ParseColor(style.BackgroundColor),
                IsAntialias = true
            };
            canvas.DrawRect(x, y, width, height, bgPaint);
        }

        // Border
        if ((style?.BorderWidth ?? 0) > 0)
        {
            using var borderPaint = new SKPaint
            {
                Style = SKPaintStyle.Stroke,
                Color = ParseColor(style!.BorderColor ?? "#000000"),
                StrokeWidth = style.BorderWidth * 0.2646f * avgScale,
                IsAntialias = true
            };
            canvas.DrawRect(x, y, width, height, borderPaint);
        }

        // Text
        using var textPaint = new SKPaint
        {
            Style = SKPaintStyle.Fill,
            Color = ParseColor(style?.Color ?? "#000000"),
            TextSize = fontSizePx,
            IsAntialias = true,
            Typeface = SKTypeface.FromFamilyName(
                style?.FontFamily ?? "Helvetica",
                style?.FontWeight?.ToLower() == "bold" ? SKFontStyleWeight.Bold : SKFontStyleWeight.Normal,
                SKFontStyleWidth.Normal,
                style?.FontStyle?.ToLower() == "italic" ? SKFontStyleSlant.Italic : SKFontStyleSlant.Upright
            )
        };

        // Calculate text position based on alignment
        float textX = style?.TextAlign?.ToLower() switch
        {
            "center" => x + width / 2,
            "right" => x + width,
            _ => x
        };

        textPaint.TextAlign = style?.TextAlign?.ToLower() switch
        {
            "center" => SKTextAlign.Center,
            "right" => SKTextAlign.Right,
            _ => SKTextAlign.Left
        };

        // Vertical alignment
        var metrics = textPaint.FontMetrics;
        float textY = style?.VerticalAlign?.ToLower() switch
        {
            "middle" => y + height / 2 - (metrics.Ascent + metrics.Descent) / 2,
            "bottom" => y + height - metrics.Descent,
            _ => y - metrics.Ascent // top
        };

        canvas.DrawText(text, textX, textY, textPaint);
    }

    private void RenderImageToCanvas(SKCanvas canvas, AprtElement element, float x, float y, float width, float height,
        Dictionary<string, object> resources)
    {
        SKBitmap? bitmap = null;

        try
        {
            // Try to load image from various sources
            if (element.ImageSettings?.Src != null)
            {
                var src = element.ImageSettings.Src;

                if (src.StartsWith("data:image"))
                {
                    // Data URI - extract base64 data
                    var commaIndex = src.IndexOf(',');
                    if (commaIndex > 0)
                    {
                        var base64Data = src.Substring(commaIndex + 1);
                        var imageBytes = Convert.FromBase64String(base64Data);
                        bitmap = SKBitmap.Decode(imageBytes);
                        _logger.LogDebug("Image {ElementId}: Decoded from data URI ({Bytes} bytes)", element.Id, imageBytes.Length);
                    }
                }
                else if (src.StartsWith("/api/report-resources/images/"))
                {
                    // API URL - load from resources
                    var match = Regex.Match(src, @"/api/report-resources/images/(\d+)");
                    if (match.Success)
                    {
                        var imageId = match.Groups[1].Value;
                        if (resources.TryGetValue($"image_{imageId}", out var imgData) && imgData is byte[] bytes)
                        {
                            bitmap = SKBitmap.Decode(bytes);
                            _logger.LogDebug("Image {ElementId}: Loaded from resources (ID: {ImageId})", element.Id, imageId);
                        }
                    }
                }
            }

            // Fallback: Check Content.ResourceId
            if (bitmap == null && element.Content?.ResourceId != null)
            {
                if (resources.TryGetValue($"image_{element.Content.ResourceId}", out var resourceData) && resourceData is byte[] bytes)
                {
                    bitmap = SKBitmap.Decode(bytes);
                    _logger.LogDebug("Image {ElementId}: Loaded from Content.ResourceId", element.Id);
                }
            }

            if (bitmap != null)
            {
                var destRect = new SKRect(x, y, x + width, y + height);

                // Apply flip transformations if needed
                if (element.ImageSettings?.FlipHorizontal == true || element.ImageSettings?.FlipVertical == true)
                {
                    canvas.Save();

                    if (element.ImageSettings.FlipHorizontal == true)
                    {
                        canvas.Scale(-1, 1, x + width / 2, y + height / 2);
                    }
                    if (element.ImageSettings.FlipVertical == true)
                    {
                        canvas.Scale(1, -1, x + width / 2, y + height / 2);
                    }
                }

                using var paint = new SKPaint
                {
                    IsAntialias = true,
                    FilterQuality = SKFilterQuality.High
                };

                canvas.DrawBitmap(bitmap, destRect, paint);

                if (element.ImageSettings?.FlipHorizontal == true || element.ImageSettings?.FlipVertical == true)
                {
                    canvas.Restore();
                }

                _logger.LogDebug("Image {ElementId}: Rendered at ({X}, {Y}) size ({Width}x{Height})",
                    element.Id, x, y, width, height);
            }
            else
            {
                // No image found - render placeholder
                _logger.LogWarning("Image {ElementId}: No source found, rendering placeholder", element.Id);
                RenderImagePlaceholder(canvas, x, y, width, height);
            }
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to render image {ElementId}", element.Id);
            RenderImagePlaceholder(canvas, x, y, width, height);
        }
        finally
        {
            bitmap?.Dispose();
        }
    }

    private void RenderImagePlaceholder(SKCanvas canvas, float x, float y, float width, float height)
    {
        // Draw placeholder rectangle
        using var fillPaint = new SKPaint
        {
            Style = SKPaintStyle.Fill,
            Color = new SKColor(240, 240, 240),
            IsAntialias = true
        };
        canvas.DrawRect(x, y, width, height, fillPaint);

        using var strokePaint = new SKPaint
        {
            Style = SKPaintStyle.Stroke,
            Color = new SKColor(200, 200, 200),
            StrokeWidth = 1,
            PathEffect = SKPathEffect.CreateDash(new[] { 4f, 4f }, 0),
            IsAntialias = true
        };
        canvas.DrawRect(x, y, width, height, strokePaint);

        // Draw "Image" text
        using var textPaint = new SKPaint
        {
            Style = SKPaintStyle.Fill,
            Color = new SKColor(150, 150, 150),
            TextSize = Math.Min(width, height) * 0.15f,
            TextAlign = SKTextAlign.Center,
            IsAntialias = true
        };
        canvas.DrawText("[Immagine]", x + width / 2, y + height / 2, textPaint);
    }

    private void RenderTableToCanvas(SKCanvas canvas, AprtElement element, float x, float y, float width, float height,
        float scaleX, float scaleY, Dictionary<string, object> dataContext)
    {
        if (element.Columns == null || element.Columns.Count == 0) return;

        var items = ResolveTableDataSource(element.DataSource, dataContext)?.ToList() ?? new List<object>();

        // Use average scale for consistent proportions
        var avgScale = (scaleX + scaleY) / 2f;

        // Table dimensions (values are in mm, convert to pixels)
        var rowHeight = 5f * scaleY;
        var headerHeight = 6f * scaleY;
        var fontSize = 2.5f * avgScale;
        var padding = 1f * scaleX;

        var currentY = y;

        // Calculate column widths
        var totalColumnWidth = element.Columns.Sum(c => c.Width);
        var colScale = width / totalColumnWidth;

        // Header background
        using var headerBgPaint = new SKPaint { Style = SKPaintStyle.Fill, Color = new SKColor(224, 224, 224) };
        canvas.DrawRect(x, currentY, width, headerHeight, headerBgPaint);

        // Header text
        var currentX = x;
        using var headerTextPaint = new SKPaint
        {
            Style = SKPaintStyle.Fill,
            Color = SKColors.Black,
            TextSize = fontSize,
            IsAntialias = true,
            Typeface = SKTypeface.FromFamilyName("Helvetica", SKFontStyleWeight.Bold, SKFontStyleWidth.Normal, SKFontStyleSlant.Upright)
        };

        foreach (var col in element.Columns)
        {
            var colWidth = col.Width * colScale;
            headerTextPaint.TextAlign = col.Align switch
            {
                "center" => SKTextAlign.Center,
                "right" => SKTextAlign.Right,
                _ => SKTextAlign.Left
            };
            var textX = col.Align switch
            {
                "center" => currentX + colWidth / 2,
                "right" => currentX + colWidth - padding,
                _ => currentX + padding
            };
            canvas.DrawText(col.Header ?? "", textX, currentY + headerHeight / 2 + fontSize / 3, headerTextPaint);
            currentX += colWidth;
        }

        currentY += headerHeight;

        // Data rows
        using var rowTextPaint = new SKPaint
        {
            Style = SKPaintStyle.Fill,
            Color = SKColors.Black,
            TextSize = fontSize,
            IsAntialias = true
        };
        using var rowBgPaint = new SKPaint { Style = SKPaintStyle.Fill };
        using var linePaint = new SKPaint { Style = SKPaintStyle.Stroke, Color = new SKColor(224, 224, 224), StrokeWidth = 0.5f };

        for (int rowIndex = 0; rowIndex < items.Count; rowIndex++)
        {
            var item = items[rowIndex];
            currentX = x;

            // Row background
            rowBgPaint.Color = rowIndex % 2 == 0 ? SKColors.White : new SKColor(248, 248, 248);
            canvas.DrawRect(x, currentY, width, rowHeight, rowBgPaint);

            foreach (var col in element.Columns)
            {
                var colWidth = col.Width * colScale;

                var fieldPath = col.Field.Trim();
                if (fieldPath.StartsWith("{{") && fieldPath.EndsWith("}}"))
                    fieldPath = fieldPath.Substring(2, fieldPath.Length - 4).Trim();

                var value = GetPropertyValue(item, fieldPath);
                var formattedValue = FormatValue(value, col.Format) ?? "";

                rowTextPaint.TextAlign = col.Align switch
                {
                    "center" => SKTextAlign.Center,
                    "right" => SKTextAlign.Right,
                    _ => SKTextAlign.Left
                };
                var textX = col.Align switch
                {
                    "center" => currentX + colWidth / 2,
                    "right" => currentX + colWidth - padding,
                    _ => currentX + padding
                };
                canvas.DrawText(formattedValue, textX, currentY + rowHeight / 2 + fontSize / 3, rowTextPaint);
                currentX += colWidth;
            }

            // Row border
            canvas.DrawLine(x, currentY + rowHeight, x + width, currentY + rowHeight, linePaint);
            currentY += rowHeight;
        }

        // Table border
        using var borderPaint = new SKPaint { Style = SKPaintStyle.Stroke, Color = new SKColor(200, 200, 200), StrokeWidth = 1 };
        var tableHeight = headerHeight + items.Count * rowHeight;
        canvas.DrawRect(x, y, width, tableHeight, borderPaint);
    }

    /// <summary>
    /// Parses a CSS color string to SKColor
    /// </summary>
    private static SKColor ParseColor(string color)
    {
        if (string.IsNullOrEmpty(color) || color.ToLower() == "transparent")
            return SKColors.Transparent;

        if (color.StartsWith("#"))
        {
            // Hex color
            var hex = color.TrimStart('#');
            if (hex.Length == 3)
            {
                // Short form #RGB -> #RRGGBB
                hex = $"{hex[0]}{hex[0]}{hex[1]}{hex[1]}{hex[2]}{hex[2]}";
            }
            if (hex.Length == 6)
            {
                return new SKColor(
                    Convert.ToByte(hex.Substring(0, 2), 16),
                    Convert.ToByte(hex.Substring(2, 2), 16),
                    Convert.ToByte(hex.Substring(4, 2), 16)
                );
            }
            if (hex.Length == 8)
            {
                // RGBA
                return new SKColor(
                    Convert.ToByte(hex.Substring(0, 2), 16),
                    Convert.ToByte(hex.Substring(2, 2), 16),
                    Convert.ToByte(hex.Substring(4, 2), 16),
                    Convert.ToByte(hex.Substring(6, 2), 16)
                );
            }
        }

        // Try named colors
        return color.ToLower() switch
        {
            "black" => SKColors.Black,
            "white" => SKColors.White,
            "red" => SKColors.Red,
            "green" => SKColors.Green,
            "blue" => SKColors.Blue,
            "yellow" => SKColors.Yellow,
            "gray" or "grey" => SKColors.Gray,
            _ => SKColors.Black
        };
    }

    /// <summary>
    /// Generates SVG content with absolute positioned elements.
    /// All positions in the template are in mm, relative to the content area (inside margins).
    /// </summary>
    private string GenerateSvgContent(List<AprtElement> elements, float contentWidthMm, float contentHeightMm,
        Dictionary<string, object> dataContext, Dictionary<string, object> resources)
    {
        // SVG viewBox in mm - this ensures 1 unit = 1mm in our coordinate system
        var svgBuilder = new System.Text.StringBuilder();

        // Use mm as the unit system in SVG via viewBox
        // The viewBox maps the coordinate space, so positions in mm work directly
        svgBuilder.AppendLine($"<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" " +
            $"width=\"{contentWidthMm}mm\" height=\"{contentHeightMm}mm\" " +
            $"viewBox=\"0 0 {contentWidthMm.ToString(CultureInfo.InvariantCulture)} {contentHeightMm.ToString(CultureInfo.InvariantCulture)}\">");

        // Add style definitions
        svgBuilder.AppendLine("<defs>");
        svgBuilder.AppendLine("<style type=\"text/css\">");
        svgBuilder.AppendLine("text { font-family: 'Helvetica', 'Arial', sans-serif; }");
        svgBuilder.AppendLine("</style>");
        svgBuilder.AppendLine("</defs>");

        // Render each element at its absolute position (positions are already in mm)
        foreach (var element in elements.OrderBy(e => elements.IndexOf(e)))
        {
            // Positions from the template are in mm, relative to content area
            // Apply the same Fabric.js coordinate transformation as in RenderElementToCanvas
            var left = element.Position.X;
            var top = element.Position.Y;
            var width = element.Position.Width;
            var height = element.Position.Height;
            var angleRad = element.Position.Rotation * (float)Math.PI / 180f;

            // Calculate center using Fabric.js formula (originX='left', originY='top')
            var halfWidth = width / 2;
            var halfHeight = height / 2;
            var cos = (float)Math.Cos(angleRad);
            var sin = (float)Math.Sin(angleRad);

            var centerX = left + halfWidth * cos - halfHeight * sin;
            var centerY = top + halfWidth * sin + halfHeight * cos;

            // Calculate draw position (top-left of unrotated rect centered on center)
            var drawX = centerX - halfWidth;
            var drawY = centerY - halfHeight;

            _logger.LogDebug("SVG element {ElementId} type={Type}: left={Left}, top={Top}, angle={Angle}° -> center=({CX},{CY}), draw=({DX},{DY})",
                element.Id, element.Type, left, top, element.Position.Rotation, centerX, centerY, drawX, drawY);

            svgBuilder.AppendLine(RenderElementToSvg(element, drawX, drawY, width, height, centerX, centerY, dataContext, resources));
        }

        svgBuilder.AppendLine("</svg>");
        return svgBuilder.ToString();
    }

    /// <summary>
    /// Renders a single element to SVG. All dimensions are in mm.
    /// x, y are the draw coordinates (top-left of the unrotated element)
    /// centerX, centerY are the center coordinates for rotation
    /// </summary>
    private string RenderElementToSvg(AprtElement element, float x, float y, float width, float height,
        float centerX, float centerY, Dictionary<string, object> dataContext, Dictionary<string, object> resources)
    {
        var svg = new System.Text.StringBuilder();
        var style = element.Style;

        // Apply rotation transform if needed (rotate around the calculated center)
        var transform = element.Position.Rotation != 0
            ? $" transform=\"rotate({Fmt(element.Position.Rotation)} {Fmt(centerX)} {Fmt(centerY)})\""
            : "";

        switch (element.Type.ToLower())
        {
            case "shape":
                var bgColor = style?.BackgroundColor ?? "transparent";
                var strokeColor = style?.BorderColor ?? "#000000";
                var strokeWidth = style?.BorderWidth ?? 0;

                svg.AppendLine($"<rect x=\"{Fmt(x)}\" y=\"{Fmt(y)}\" width=\"{Fmt(width)}\" height=\"{Fmt(height)}\" " +
                    $"fill=\"{EscapeSvgColor(bgColor)}\" stroke=\"{EscapeSvgColor(strokeColor)}\" stroke-width=\"{Fmt(strokeWidth)}\"{transform} />");
                break;

            case "line":
                var lineColor = style?.Color ?? "#000000";
                var lineWidth = Math.Max(0.3f, style?.BorderWidth ?? 1); // Minimum line width for visibility
                // Draw horizontal line in the middle of the element height
                var lineY = y + height / 2;
                svg.AppendLine($"<line x1=\"{Fmt(x)}\" y1=\"{Fmt(lineY)}\" x2=\"{Fmt(x + width)}\" y2=\"{Fmt(lineY)}\" " +
                    $"stroke=\"{EscapeSvgColor(lineColor)}\" stroke-width=\"{Fmt(lineWidth)}\"{transform} />");
                break;

            case "text":
                var text = ResolveContent(element.Content, dataContext);
                var escapedText = System.Security.SecurityElement.Escape(text) ?? "";

                // Font size in mm (converted from the style which is in points/pixels)
                // The template stores fontSize that looks correct on screen at 96 DPI
                // In our mm-based SVG, we need to convert: fontSize (screen px) → mm
                // At 96 DPI: 1px = 0.2646mm, so fontSize_mm = fontSize_px * 0.2646
                var fontSizeMm = (style?.FontSize ?? 12) * 0.2646f;
                var fontColor = style?.Color ?? "#000000";
                var fontWeight = style?.FontWeight?.ToLower() == "bold" ? "bold" : "normal";
                var fontStyle = style?.FontStyle?.ToLower() == "italic" ? "italic" : "normal";
                var fontFamily = style?.FontFamily ?? "Helvetica";

                // Calculate text anchor based on alignment
                var textAnchor = style?.TextAlign?.ToLower() switch
                {
                    "center" => "middle",
                    "right" => "end",
                    _ => "start"
                };

                // Calculate x position based on alignment
                var textX = style?.TextAlign?.ToLower() switch
                {
                    "center" => x + width / 2,
                    "right" => x + width,
                    _ => x
                };

                // Background for text element
                if (!string.IsNullOrEmpty(style?.BackgroundColor) && style.BackgroundColor != "transparent")
                {
                    svg.AppendLine($"<rect x=\"{Fmt(x)}\" y=\"{Fmt(y)}\" width=\"{Fmt(width)}\" height=\"{Fmt(height)}\" " +
                        $"fill=\"{EscapeSvgColor(style.BackgroundColor)}\" />");
                }

                // Border for text element
                if ((style?.BorderWidth ?? 0) > 0)
                {
                    svg.AppendLine($"<rect x=\"{Fmt(x)}\" y=\"{Fmt(y)}\" width=\"{Fmt(width)}\" height=\"{Fmt(height)}\" " +
                        $"fill=\"none\" stroke=\"{EscapeSvgColor(style!.BorderColor ?? "#000000")}\" stroke-width=\"{Fmt(style.BorderWidth)}\" />");
                }

                // Text baseline adjustment
                // SVG text y coordinate is the baseline. For top-aligned text in a box:
                // baseline = top + fontSizeMm * 0.85 (approximate ascender height)
                var textY = style?.VerticalAlign?.ToLower() switch
                {
                    "middle" => y + height / 2 + fontSizeMm * 0.35f,
                    "bottom" => y + height - fontSizeMm * 0.2f,
                    _ => y + fontSizeMm * 0.85f // top (default)
                };

                svg.AppendLine($"<text x=\"{Fmt(textX)}\" y=\"{Fmt(textY)}\" font-size=\"{Fmt(fontSizeMm)}\" " +
                    $"font-family=\"{fontFamily}\" fill=\"{EscapeSvgColor(fontColor)}\" font-weight=\"{fontWeight}\" " +
                    $"font-style=\"{fontStyle}\" text-anchor=\"{textAnchor}\"{transform}>{escapedText}</text>");
                break;

            case "image":
                var imageSvg = RenderImageToSvg(element, x, y, width, height, resources, transform);
                if (!string.IsNullOrEmpty(imageSvg))
                    svg.AppendLine(imageSvg);
                break;

            case "table":
                var tableSvg = RenderTableToSvg(element, x, y, width, height, dataContext);
                if (!string.IsNullOrEmpty(tableSvg))
                    svg.AppendLine(tableSvg);
                break;

            default:
                // Unknown type - render placeholder box
                svg.AppendLine($"<rect x=\"{Fmt(x)}\" y=\"{Fmt(y)}\" width=\"{Fmt(width)}\" height=\"{Fmt(height)}\" " +
                    $"fill=\"#f0f0f0\" stroke=\"#cccccc\" stroke-width=\"0.3\" />");
                svg.AppendLine($"<text x=\"{Fmt(x + width / 2)}\" y=\"{Fmt(y + height / 2)}\" font-size=\"3\" " +
                    $"fill=\"#999999\" text-anchor=\"middle\">[{element.Type}]</text>");
                break;
        }

        return svg.ToString();
    }

    /// <summary>
    /// Renders image element to SVG. Handles data URIs, API URLs, and resource references.
    /// </summary>
    private string RenderImageToSvg(AprtElement element, float x, float y, float width, float height,
        Dictionary<string, object> resources, string transform)
    {
        string? imageHref = null;

        // Priority 1: Check imageSettings.src for data URI or URL
        if (element.ImageSettings?.Src != null)
        {
            var src = element.ImageSettings.Src;

            if (src.StartsWith("data:image"))
            {
                // Already a data URI - use directly
                imageHref = src;
                _logger.LogDebug("Image element {ElementId}: Using embedded data URI", element.Id);
            }
            else if (src.StartsWith("/api/report-resources/images/"))
            {
                // API URL - try to load from resources or database
                var match = Regex.Match(src, @"/api/report-resources/images/(\d+)");
                if (match.Success)
                {
                    var imageId = match.Groups[1].Value;
                    if (resources.TryGetValue($"image_{imageId}", out var imgData) && imgData is byte[] bytes)
                    {
                        var mimeType = GuessMimeType(bytes);
                        imageHref = $"data:{mimeType};base64,{Convert.ToBase64String(bytes)}";
                        _logger.LogDebug("Image element {ElementId}: Loaded from resources (ID: {ImageId})", element.Id, imageId);
                    }
                }
            }
            else if (src.StartsWith("http://") || src.StartsWith("https://"))
            {
                // External URL - use directly (browser/PDF renderer will fetch)
                imageHref = src;
                _logger.LogDebug("Image element {ElementId}: Using external URL", element.Id);
            }
        }

        // Priority 2: Check Content.ResourceId
        if (imageHref == null && element.Content?.ResourceId != null)
        {
            if (resources.TryGetValue($"image_{element.Content.ResourceId}", out var resourceData) && resourceData is byte[] bytes)
            {
                var mimeType = GuessMimeType(bytes);
                imageHref = $"data:{mimeType};base64,{Convert.ToBase64String(bytes)}";
                _logger.LogDebug("Image element {ElementId}: Loaded from Content.ResourceId", element.Id);
            }
        }

        if (imageHref != null)
        {
            // Determine preserveAspectRatio based on fitMode
            var preserveAspectRatio = element.ImageSettings?.FitMode switch
            {
                "cover" => "xMidYMid slice",
                "fill" => "none",
                "none" => "xMinYMin",
                _ => "xMidYMid meet" // "contain" is default
            };

            return $"<image x=\"{Fmt(x)}\" y=\"{Fmt(y)}\" width=\"{Fmt(width)}\" height=\"{Fmt(height)}\" " +
                $"href=\"{imageHref}\" preserveAspectRatio=\"{preserveAspectRatio}\"{transform} />";
        }

        // No image found - render placeholder
        _logger.LogWarning("Image element {ElementId}: No image source found, rendering placeholder", element.Id);
        return $"<rect x=\"{Fmt(x)}\" y=\"{Fmt(y)}\" width=\"{Fmt(width)}\" height=\"{Fmt(height)}\" " +
            $"fill=\"#f0f0f0\" stroke=\"#cccccc\" stroke-width=\"0.3\" stroke-dasharray=\"2,2\" />" +
            $"<text x=\"{Fmt(x + width / 2)}\" y=\"{Fmt(y + height / 2)}\" font-size=\"3\" " +
            $"fill=\"#999999\" text-anchor=\"middle\">[Immagine]</text>";
    }

    /// <summary>
    /// Guesses MIME type from image bytes by checking magic numbers
    /// </summary>
    private static string GuessMimeType(byte[] bytes)
    {
        if (bytes.Length < 4) return "image/png";

        // PNG: 89 50 4E 47
        if (bytes[0] == 0x89 && bytes[1] == 0x50 && bytes[2] == 0x4E && bytes[3] == 0x47)
            return "image/png";

        // JPEG: FF D8 FF
        if (bytes[0] == 0xFF && bytes[1] == 0xD8 && bytes[2] == 0xFF)
            return "image/jpeg";

        // GIF: 47 49 46
        if (bytes[0] == 0x47 && bytes[1] == 0x49 && bytes[2] == 0x46)
            return "image/gif";

        // WebP: 52 49 46 46 ... 57 45 42 50
        if (bytes.Length >= 12 && bytes[0] == 0x52 && bytes[1] == 0x49 && bytes[2] == 0x46 && bytes[3] == 0x46
            && bytes[8] == 0x57 && bytes[9] == 0x45 && bytes[10] == 0x42 && bytes[11] == 0x50)
            return "image/webp";

        return "image/png"; // Default
    }

    /// <summary>
    /// Renders table element to SVG. All dimensions in mm.
    /// </summary>
    private string RenderTableToSvg(AprtElement element, float x, float y, float width, float height,
        Dictionary<string, object> dataContext)
    {
        if (element.Columns == null || element.Columns.Count == 0)
            return string.Empty;

        var svg = new System.Text.StringBuilder();
        var items = ResolveTableDataSource(element.DataSource, dataContext)?.ToList() ?? new List<object>();

        // Table dimensions in mm
        var rowHeightMm = 5f;
        var headerHeightMm = 6f;
        var fontSizeMm = 2.5f;
        var paddingMm = 1f;

        var currentY = y;

        // Calculate column widths proportionally to fit the table width
        var totalColumnWidth = element.Columns.Sum(c => c.Width);
        var scale = width / totalColumnWidth;

        // Draw header row background
        svg.AppendLine($"<rect x=\"{Fmt(x)}\" y=\"{Fmt(currentY)}\" width=\"{Fmt(width)}\" height=\"{Fmt(headerHeightMm)}\" fill=\"#e0e0e0\" />");

        // Draw header text
        var currentX = x;
        foreach (var col in element.Columns)
        {
            var colWidth = col.Width * scale;

            var textX = col.Align switch
            {
                "center" => currentX + colWidth / 2,
                "right" => currentX + colWidth - paddingMm,
                _ => currentX + paddingMm
            };
            var anchor = col.Align switch
            {
                "center" => "middle",
                "right" => "end",
                _ => "start"
            };

            svg.AppendLine($"<text x=\"{Fmt(textX)}\" y=\"{Fmt(currentY + headerHeightMm / 2 + fontSizeMm / 3)}\" " +
                $"font-size=\"{Fmt(fontSizeMm)}\" font-weight=\"bold\" fill=\"#000000\" text-anchor=\"{anchor}\">" +
                $"{System.Security.SecurityElement.Escape(col.Header)}</text>");

            currentX += colWidth;
        }

        currentY += headerHeightMm;

        // Draw data rows
        for (int rowIndex = 0; rowIndex < items.Count; rowIndex++)
        {
            var item = items[rowIndex];
            currentX = x;

            // Row background (alternating)
            var rowBg = rowIndex % 2 == 0 ? "#ffffff" : "#f8f8f8";
            svg.AppendLine($"<rect x=\"{Fmt(x)}\" y=\"{Fmt(currentY)}\" width=\"{Fmt(width)}\" height=\"{Fmt(rowHeightMm)}\" fill=\"{rowBg}\" />");

            foreach (var col in element.Columns)
            {
                var colWidth = col.Width * scale;

                // Get field value
                var fieldPath = col.Field.Trim();
                if (fieldPath.StartsWith("{{") && fieldPath.EndsWith("}}"))
                    fieldPath = fieldPath.Substring(2, fieldPath.Length - 4).Trim();

                var value = GetPropertyValue(item, fieldPath);
                var formattedValue = FormatValue(value, col.Format);
                var escapedValue = System.Security.SecurityElement.Escape(formattedValue) ?? "";

                var textX = col.Align switch
                {
                    "center" => currentX + colWidth / 2,
                    "right" => currentX + colWidth - paddingMm,
                    _ => currentX + paddingMm
                };
                var anchor = col.Align switch
                {
                    "center" => "middle",
                    "right" => "end",
                    _ => "start"
                };

                svg.AppendLine($"<text x=\"{Fmt(textX)}\" y=\"{Fmt(currentY + rowHeightMm / 2 + fontSizeMm / 3)}\" " +
                    $"font-size=\"{Fmt(fontSizeMm)}\" fill=\"#000000\" text-anchor=\"{anchor}\">{escapedValue}</text>");

                currentX += colWidth;
            }

            // Row border
            svg.AppendLine($"<line x1=\"{Fmt(x)}\" y1=\"{Fmt(currentY + rowHeightMm)}\" x2=\"{Fmt(x + width)}\" y2=\"{Fmt(currentY + rowHeightMm)}\" stroke=\"#e0e0e0\" stroke-width=\"0.2\" />");

            currentY += rowHeightMm;
        }

        // Table border
        var tableHeight = headerHeightMm + items.Count * rowHeightMm;
        svg.AppendLine($"<rect x=\"{Fmt(x)}\" y=\"{Fmt(y)}\" width=\"{Fmt(width)}\" height=\"{Fmt(tableHeight)}\" fill=\"none\" stroke=\"#cccccc\" stroke-width=\"0.3\" />");

        return svg.ToString();
    }

    /// <summary>
    /// Formats a float for SVG with invariant culture and reasonable precision
    /// </summary>
    private static string Fmt(float value) => value.ToString("F2", CultureInfo.InvariantCulture);

    /// <summary>
    /// Escapes color for SVG (handles 'transparent')
    /// </summary>
    private static string EscapeSvgColor(string color)
    {
        if (string.IsNullOrEmpty(color) || color.ToLower() == "transparent")
            return "none";
        return color;
    }

    private static (float Width, float Height) GetPageDimensionsMm(string size, string orientation)
    {
        var (width, height) = size.ToUpper() switch
        {
            "A3" => (297f, 420f),
            "A4" => (210f, 297f),
            "A5" => (148f, 210f),
            "LETTER" => (216f, 279f),
            "LEGAL" => (216f, 356f),
            _ => (210f, 297f) // Default A4
        };

        return orientation.ToLower() == "landscape" ? (height, width) : (width, height);
    }

    public async Task<byte[]> GenerateEventoPdfAsync(int eventoId, int? templateId = null)
    {
        // Load evento with all related data
        var evento = await _context.Eventi
            .Include(e => e.Cliente)
            .Include(e => e.Location)
            .Include(e => e.TipoEvento)
            .Include(e => e.DettagliOspiti).ThenInclude(d => d.TipoOspite)
            .Include(e => e.DettagliPrelievo).ThenInclude(d => d.Articolo)
            .Include(e => e.DettagliRisorse).ThenInclude(d => d.Risorsa)
            .Include(e => e.Acconti)
            .Include(e => e.AltriCosti)
            .FirstOrDefaultAsync(e => e.Id == eventoId);

        if (evento == null)
            throw new ArgumentException($"Evento with ID {eventoId} not found");

        // If no template specified, use default or generate basic PDF
        if (templateId == null)
        {
            return GenerateDefaultEventoPdf(evento);
        }

        var dataContext = new Dictionary<string, object>
        {
            ["evento"] = evento,
            ["cliente"] = evento.Cliente ?? new Cliente(),
            ["location"] = evento.Location ?? new Location(),
            ["ospiti"] = evento.DettagliOspiti.ToList(),
            ["prelievo"] = evento.DettagliPrelievo.ToList(),
            ["risorse"] = evento.DettagliRisorse.ToList(),
            ["acconti"] = evento.Acconti.ToList(),
            ["altriCosti"] = evento.AltriCosti.ToList()
        };

        return await GeneratePdfAsync(templateId.Value, dataContext);
    }

    private byte[] GenerateDefaultEventoPdf(Evento evento)
    {
        var document = Document.Create(container =>
        {
            container.Page(page =>
            {
                page.Size(PageSizes.A4);
                page.Margin(15, Unit.Millimetre);
                page.DefaultTextStyle(x => x.FontSize(10));

                // Header
                page.Header().Element(header =>
                {
                    header.Row(row =>
                    {
                        row.RelativeItem().Column(col =>
                        {
                            col.Item().Text("SCHEDA EVENTO").Bold().FontSize(20).FontColor(Colors.Blue.Darken2);
                            col.Item().Text($"Codice: {evento.Codice ?? $"EVT-{evento.Id:D5}"}").FontSize(12);
                        });

                        row.ConstantItem(150).Column(col =>
                        {
                            col.Item().AlignRight().Text($"Data: {evento.DataEvento:dd/MM/yyyy}").Bold();
                            col.Item().AlignRight().Text(GetStatoLabel(evento.Stato)).FontColor(GetStatoColor(evento.Stato));
                        });
                    });
                });

                // Content
                page.Content().PaddingVertical(10).Column(content =>
                {
                    // Client info
                    content.Item().Background(Colors.Grey.Lighten4).Padding(10).Column(clientSection =>
                    {
                        clientSection.Item().Text("CLIENTE").Bold().FontSize(12).FontColor(Colors.Blue.Darken2);
                        clientSection.Item().PaddingTop(5).Row(row =>
                        {
                            row.RelativeItem().Column(col =>
                            {
                                col.Item().Text(evento.Cliente?.RagioneSociale ?? "Non specificato").Bold();
                                if (!string.IsNullOrEmpty(evento.Cliente?.Indirizzo))
                                    col.Item().Text($"{evento.Cliente.Indirizzo}, {evento.Cliente.Citta} ({evento.Cliente.Provincia})");
                                if (!string.IsNullOrEmpty(evento.Cliente?.Telefono))
                                    col.Item().Text($"Tel: {evento.Cliente.Telefono}");
                            });
                            row.RelativeItem().Column(col =>
                            {
                                if (!string.IsNullOrEmpty(evento.Cliente?.Email))
                                    col.Item().Text($"Email: {evento.Cliente.Email}");
                                if (!string.IsNullOrEmpty(evento.Cliente?.PartitaIva))
                                    col.Item().Text($"P.IVA: {evento.Cliente.PartitaIva}");
                            });
                        });
                    });

                    content.Item().PaddingVertical(5);

                    // Location info
                    content.Item().Background(Colors.Grey.Lighten4).Padding(10).Column(locSection =>
                    {
                        locSection.Item().Text("LOCATION").Bold().FontSize(12).FontColor(Colors.Blue.Darken2);
                        locSection.Item().PaddingTop(5).Row(row =>
                        {
                            row.RelativeItem().Column(col =>
                            {
                                col.Item().Text(evento.Location?.Nome ?? "Non specificata").Bold();
                                if (!string.IsNullOrEmpty(evento.Location?.Indirizzo))
                                    col.Item().Text($"{evento.Location.Indirizzo}, {evento.Location.Citta} ({evento.Location.Provincia})");
                            });
                            row.RelativeItem().Column(col =>
                            {
                                if (evento.OraInizio.HasValue)
                                    col.Item().Text($"Ora: {evento.OraInizio:hh\\:mm} - {evento.OraFine:hh\\:mm}");
                                if (evento.Location?.DistanzaKm > 0)
                                    col.Item().Text($"Distanza: {evento.Location.DistanzaKm} km");
                            });
                        });
                    });

                    content.Item().PaddingVertical(5);

                    // Event details
                    content.Item().Row(row =>
                    {
                        row.RelativeItem().Background(Colors.Grey.Lighten4).Padding(10).Column(col =>
                        {
                            col.Item().Text("TIPO EVENTO").Bold().FontSize(12).FontColor(Colors.Blue.Darken2);
                            col.Item().PaddingTop(5).Text(evento.TipoEvento?.Descrizione ?? "Non specificato").Bold();
                        });
                        row.ConstantItem(10);
                        row.RelativeItem().Background(Colors.Grey.Lighten4).Padding(10).Column(col =>
                        {
                            col.Item().Text("OSPITI").Bold().FontSize(12).FontColor(Colors.Blue.Darken2);
                            col.Item().PaddingTop(5).Text($"{evento.NumeroOspiti ?? 0} totali").Bold();
                        });
                    });

                    content.Item().PaddingVertical(10);

                    // Guests table
                    if (evento.DettagliOspiti.Any())
                    {
                        content.Item().Text("DETTAGLIO OSPITI").Bold().FontSize(12).FontColor(Colors.Blue.Darken2);
                        content.Item().PaddingTop(5).Table(table =>
                        {
                            table.ColumnsDefinition(columns =>
                            {
                                columns.RelativeColumn(3);
                                columns.RelativeColumn(1);
                                columns.RelativeColumn(1);
                                columns.RelativeColumn(1);
                                columns.RelativeColumn(1);
                            });

                            table.Header(header =>
                            {
                                header.Cell().Background(Colors.Blue.Darken2).Padding(5).Text("Tipo").FontColor(Colors.White).Bold();
                                header.Cell().Background(Colors.Blue.Darken2).Padding(5).AlignRight().Text("Numero").FontColor(Colors.White).Bold();
                                header.Cell().Background(Colors.Blue.Darken2).Padding(5).AlignRight().Text("Costo Unit.").FontColor(Colors.White).Bold();
                                header.Cell().Background(Colors.Blue.Darken2).Padding(5).AlignRight().Text("Sconto").FontColor(Colors.White).Bold();
                                header.Cell().Background(Colors.Blue.Darken2).Padding(5).AlignRight().Text("Totale").FontColor(Colors.White).Bold();
                            });

                            foreach (var ospite in evento.DettagliOspiti.OrderBy(o => o.Ordine))
                            {
                                var totale = ospite.Numero * (ospite.CostoUnitario ?? 0) * (1 - (ospite.Sconto ?? 0) / 100);

                                table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2).Padding(5)
                                    .Text(ospite.TipoOspite?.Descrizione ?? "N/D");
                                table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2).Padding(5)
                                    .AlignRight().Text(ospite.Numero.ToString());
                                table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2).Padding(5)
                                    .AlignRight().Text(FormatCurrency(ospite.CostoUnitario ?? 0));
                                table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2).Padding(5)
                                    .AlignRight().Text($"{ospite.Sconto ?? 0}%");
                                table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2).Padding(5)
                                    .AlignRight().Text(FormatCurrency(totale)).Bold();
                            }
                        });
                    }

                    content.Item().PaddingVertical(10);

                    // Other costs table
                    if (evento.AltriCosti.Any())
                    {
                        content.Item().Text("ALTRI COSTI").Bold().FontSize(12).FontColor(Colors.Blue.Darken2);
                        content.Item().PaddingTop(5).Table(table =>
                        {
                            table.ColumnsDefinition(columns =>
                            {
                                columns.RelativeColumn(4);
                                columns.RelativeColumn(1);
                                columns.RelativeColumn(1);
                                columns.RelativeColumn(1);
                                columns.RelativeColumn(1);
                            });

                            table.Header(header =>
                            {
                                header.Cell().Background(Colors.Orange.Darken2).Padding(5).Text("Descrizione").FontColor(Colors.White).Bold();
                                header.Cell().Background(Colors.Orange.Darken2).Padding(5).AlignRight().Text("Costo").FontColor(Colors.White).Bold();
                                header.Cell().Background(Colors.Orange.Darken2).Padding(5).AlignRight().Text("Qtà").FontColor(Colors.White).Bold();
                                header.Cell().Background(Colors.Orange.Darken2).Padding(5).AlignRight().Text("IVA").FontColor(Colors.White).Bold();
                                header.Cell().Background(Colors.Orange.Darken2).Padding(5).AlignRight().Text("Totale").FontColor(Colors.White).Bold();
                            });

                            foreach (var costo in evento.AltriCosti.OrderBy(c => c.Ordine))
                            {
                                var totale = costo.CostoUnitario * costo.Quantita;
                                if (costo.ApplicaIva)
                                    totale *= (1 + costo.AliquotaIva / 100);

                                table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2).Padding(5).Text(costo.Descrizione);
                                table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2).Padding(5).AlignRight().Text(FormatCurrency(costo.CostoUnitario));
                                table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2).Padding(5).AlignRight().Text(costo.Quantita.ToString("N2"));
                                table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2).Padding(5).AlignRight().Text(costo.ApplicaIva ? $"{costo.AliquotaIva}%" : "-");
                                table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2).Padding(5).AlignRight().Text(FormatCurrency(totale)).Bold();
                            }
                        });
                    }

                    content.Item().PaddingVertical(10);

                    // Resources table
                    if (evento.DettagliRisorse.Any())
                    {
                        content.Item().Text("RISORSE ASSEGNATE").Bold().FontSize(12).FontColor(Colors.Blue.Darken2);
                        content.Item().PaddingTop(5).Table(table =>
                        {
                            table.ColumnsDefinition(columns =>
                            {
                                columns.RelativeColumn(3);
                                columns.RelativeColumn(2);
                                columns.RelativeColumn(1);
                                columns.RelativeColumn(1);
                                columns.RelativeColumn(1);
                            });

                            table.Header(header =>
                            {
                                header.Cell().Background(Colors.Green.Darken2).Padding(5).Text("Risorsa").FontColor(Colors.White).Bold();
                                header.Cell().Background(Colors.Green.Darken2).Padding(5).Text("Ruolo").FontColor(Colors.White).Bold();
                                header.Cell().Background(Colors.Green.Darken2).Padding(5).AlignCenter().Text("Orario").FontColor(Colors.White).Bold();
                                header.Cell().Background(Colors.Green.Darken2).Padding(5).AlignRight().Text("Ore").FontColor(Colors.White).Bold();
                                header.Cell().Background(Colors.Green.Darken2).Padding(5).AlignRight().Text("Costo").FontColor(Colors.White).Bold();
                            });

                            foreach (var risorsa in evento.DettagliRisorse)
                            {
                                table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2).Padding(5)
                                    .Text($"{risorsa.Risorsa?.Nome} {risorsa.Risorsa?.Cognome}".Trim());
                                table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2).Padding(5)
                                    .Text(risorsa.Ruolo ?? "-");
                                table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2).Padding(5)
                                    .AlignCenter().Text($"{risorsa.OraInizio:hh\\:mm} - {risorsa.OraFine:hh\\:mm}");
                                table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2).Padding(5)
                                    .AlignRight().Text(risorsa.OreLavoro?.ToString("N1") ?? "-");
                                table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2).Padding(5)
                                    .AlignRight().Text(FormatCurrency(risorsa.Costo ?? 0));
                            }
                        });
                    }

                    content.Item().PaddingVertical(10);

                    // Totals summary
                    content.Item().Background(Colors.Blue.Lighten5).Padding(10).Column(totals =>
                    {
                        totals.Item().Text("RIEPILOGO COSTI").Bold().FontSize(12).FontColor(Colors.Blue.Darken2);
                        totals.Item().PaddingTop(10).Row(row =>
                        {
                            row.RelativeItem().Column(col =>
                            {
                                col.Item().Text("Costo Totale:").Bold();
                                col.Item().Text("Totale Acconti:");
                                col.Item().Text("Saldo da Pagare:").Bold().FontColor(Colors.Red.Darken2);
                            });
                            row.ConstantItem(150).Column(col =>
                            {
                                col.Item().AlignRight().Text(FormatCurrency(evento.CostoTotale ?? 0)).FontSize(14).Bold();
                                col.Item().AlignRight().Text(FormatCurrency(evento.TotaleAcconti ?? 0));
                                col.Item().AlignRight().Text(FormatCurrency(evento.Saldo ?? 0)).FontSize(14).Bold().FontColor(Colors.Red.Darken2);
                            });
                        });
                    });

                    // Notes
                    if (!string.IsNullOrEmpty(evento.NoteCliente) || !string.IsNullOrEmpty(evento.NoteInterne))
                    {
                        content.Item().PaddingVertical(10);
                        content.Item().Text("NOTE").Bold().FontSize(12).FontColor(Colors.Blue.Darken2);

                        if (!string.IsNullOrEmpty(evento.NoteCliente))
                        {
                            content.Item().PaddingTop(5).Text("Note Cliente:").Bold();
                            content.Item().Text(evento.NoteCliente);
                        }

                        if (!string.IsNullOrEmpty(evento.NoteInterne))
                        {
                            content.Item().PaddingTop(5).Text("Note Interne:").Bold();
                            content.Item().Text(evento.NoteInterne);
                        }
                    }
                });

                // Footer
                page.Footer().AlignCenter().Text(text =>
                {
                    text.Span("Pagina ");
                    text.CurrentPageNumber();
                    text.Span(" di ");
                    text.TotalPages();
                    text.Span($" - Generato il {DateTime.Now:dd/MM/yyyy HH:mm}");
                });
            });
        });

        return document.GeneratePdf();
    }

    private IEnumerable<object>? ResolveTableDataSource(string? dataSource, Dictionary<string, object> dataContext)
    {
        if (string.IsNullOrEmpty(dataSource)) return null;

        // Check if it's a path like "dataset.collection" (e.g., "evento.dettagliOspiti")
        if (dataSource.Contains('.'))
        {
            var parts = dataSource.Split('.', 2);
            var datasetId = parts[0];
            var collectionPath = parts[1];

            if (dataContext.TryGetValue(datasetId, out var dataset))
            {
                var collectionValue = GetPropertyValue(dataset, collectionPath);
                if (collectionValue is IEnumerable<object> collection)
                {
                    return collection;
                }
                // Handle IEnumerable that's not IEnumerable<object>
                if (collectionValue is System.Collections.IEnumerable enumerable)
                {
                    return enumerable.Cast<object>();
                }
            }
            return null;
        }

        // Direct dataset reference
        if (dataContext.TryGetValue(dataSource, out var data))
        {
            if (data is IEnumerable<object> items)
            {
                return items;
            }
            // Handle IEnumerable that's not IEnumerable<object>
            if (data is System.Collections.IEnumerable enumerable && data is not string)
            {
                return enumerable.Cast<object>();
            }
            // Single object - wrap in array
            return new[] { data };
        }

        return null;
    }

    private string ResolveContent(AprtContent? content, Dictionary<string, object> dataContext, PageContext? pageContext = null)
    {
        if (content == null) return string.Empty;

        var result = content.Type?.ToLower() switch
        {
            "static" => content.Value ?? string.Empty,
            "binding" => ResolveBindingWithFormat(content.Expression ?? content.Value ?? string.Empty, dataContext, content.Format, pageContext),
            "expression" => ResolveExpression(content.Value ?? string.Empty, dataContext, pageContext),
            _ => content.Value ?? string.Empty
        };

        return result;
    }

    private string ResolveBindingWithFormat(string expression, Dictionary<string, object> dataContext, string? format, PageContext? pageContext = null)
    {
        return BindingRegex.Replace(expression, match =>
        {
            var path = match.Groups[1].Value.Trim();
            var value = ResolveBindingPath(path, dataContext, pageContext);

            if (value != null && !string.IsNullOrEmpty(format))
            {
                return FormatValue(value, format);
            }

            return value?.ToString() ?? string.Empty;
        });
    }

    private string ResolveBinding(string expression, Dictionary<string, object> dataContext, PageContext? pageContext = null)
    {
        return BindingRegex.Replace(expression, match =>
        {
            var path = match.Groups[1].Value.Trim();
            var value = ResolveBindingPath(path, dataContext, pageContext);
            return value?.ToString() ?? string.Empty;
        });
    }

    private object? ResolveBindingPath(string path, Dictionary<string, object> dataContext, PageContext? pageContext = null)
    {
        // Handle special variables
        if (path.StartsWith("$"))
        {
            return path switch
            {
                "$pageNumber" => pageContext?.PageNumber.ToString() ?? "1",
                "$totalPages" => pageContext?.TotalPages.ToString() ?? "1",
                "$date" => DateTime.Now.ToString("dd/MM/yyyy"),
                "$time" => DateTime.Now.ToString("HH:mm"),
                "$datetime" => DateTime.Now.ToString("dd/MM/yyyy HH:mm"),
                _ => path
            };
        }

        var parts = path.Split('.');
        object? current = null;

        // Try standard path resolution: datasetId.propertyPath
        if (parts.Length > 0 && dataContext.TryGetValue(parts[0], out var root))
        {
            current = root;
            for (int i = 1; i < parts.Length && current != null; i++)
            {
                current = GetPropertyValue(current, parts[i]);
            }
        }

        // Fallback: if not found and path has no dots, search across all datasets
        // This handles legacy bindings like {{dataEvento}} instead of {{evento.dataEvento}}
        if (current == null && parts.Length == 1)
        {
            foreach (var kvp in dataContext)
            {
                var foundValue = GetPropertyValue(kvp.Value, path);
                if (foundValue != null)
                {
                    current = foundValue;
                    break;
                }
            }
        }

        return current;
    }

    private string ResolveExpression(string expression, Dictionary<string, object> dataContext, PageContext? pageContext = null)
    {
        return ResolveBinding(expression, dataContext, pageContext);
    }

    private object? GetPropertyValue(object obj, string propertyPath)
    {
        var parts = propertyPath.Split('.');
        object? current = obj;

        foreach (var part in parts)
        {
            if (current == null) return null;

            var type = current.GetType();
            var prop = type.GetProperty(part, System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.IgnoreCase);

            if (prop != null)
            {
                current = prop.GetValue(current);
            }
            else
            {
                return null;
            }
        }

        return current;
    }

    private string FormatValue(object? value, string? format)
    {
        if (value == null) return string.Empty;

        return format?.ToLower() switch
        {
            "currency" => FormatCurrency(Convert.ToDecimal(value)),
            "date" => value is DateTime dt ? dt.ToString("dd/MM/yyyy") : value.ToString() ?? string.Empty,
            "datetime" => value is DateTime dtt ? dtt.ToString("dd/MM/yyyy HH:mm") : value.ToString() ?? string.Empty,
            "number" => Convert.ToDecimal(value).ToString("N2", CultureInfo.GetCultureInfo("it-IT")),
            "percent" => $"{Convert.ToDecimal(value):N2}%",
            _ => value.ToString() ?? string.Empty
        };
    }

    private static string FormatCurrency(decimal value)
    {
        return value.ToString("C2", CultureInfo.GetCultureInfo("it-IT"));
    }

    private static PageSize GetPageSize(string size, string orientation)
    {
        var pageSize = size.ToUpper() switch
        {
            "A3" => PageSizes.A3,
            "A4" => PageSizes.A4,
            "A5" => PageSizes.A5,
            "LETTER" => PageSizes.Letter,
            "LEGAL" => PageSizes.Legal,
            _ => PageSizes.A4
        };

        return orientation.ToLower() == "landscape" ? pageSize.Landscape() : pageSize.Portrait();
    }

    private async Task<Dictionary<string, object>> LoadResourcesAsync(AprtTemplate template)
    {
        var resources = new Dictionary<string, object>();

        // Load images from template resources
        foreach (var img in template.Resources.Images)
        {
            if (!string.IsNullOrEmpty(img.Data))
            {
                resources[$"image_{img.Id}"] = Convert.FromBase64String(img.Data);
            }
            else if (img.Url?.StartsWith("/api/") == true)
            {
                var match = Regex.Match(img.Url, @"/api/report-resources/images/(\d+)");
                if (match.Success && int.TryParse(match.Groups[1].Value, out var imageId))
                {
                    var dbImage = await _context.ReportImages.FindAsync(imageId);
                    if (dbImage != null)
                    {
                        resources[$"image_{img.Id}"] = dbImage.ImageData;
                    }
                }
            }
        }

        // Also preload any images referenced in elements by API URL
        foreach (var element in template.Elements.Where(e => e.Type == "image"))
        {
            if (element.ImageSettings?.Src?.StartsWith("/api/report-resources/images/") == true)
            {
                var match = Regex.Match(element.ImageSettings.Src, @"/api/report-resources/images/(\d+)");
                if (match.Success && int.TryParse(match.Groups[1].Value, out var imageId))
                {
                    var key = $"image_{imageId}";
                    if (!resources.ContainsKey(key))
                    {
                        var dbImage = await _context.ReportImages.FindAsync(imageId);
                        if (dbImage != null)
                        {
                            resources[key] = dbImage.ImageData;
                        }
                    }
                }
            }
        }

        return resources;
    }

    private static string GetStatoLabel(Apollinare.Domain.Enums.StatoEvento stato)
    {
        return stato switch
        {
            Apollinare.Domain.Enums.StatoEvento.Scheda => "SCHEDA",
            Apollinare.Domain.Enums.StatoEvento.Preventivo => "PREVENTIVO",
            Apollinare.Domain.Enums.StatoEvento.Confermato => "CONFERMATO",
            _ => stato.ToString()
        };
    }

    private static string GetStatoColor(Apollinare.Domain.Enums.StatoEvento stato)
    {
        return stato switch
        {
            Apollinare.Domain.Enums.StatoEvento.Scheda => Colors.Grey.Medium,
            Apollinare.Domain.Enums.StatoEvento.Preventivo => Colors.Orange.Medium,
            Apollinare.Domain.Enums.StatoEvento.Confermato => Colors.Green.Medium,
            _ => Colors.Grey.Medium
        };
    }
}
