using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using PdfSharpCore;
using PdfSharpCore.Drawing;
using PdfSharpCore.Pdf;

namespace CounterDrone.Core.Reporting
{
    /// <summary>标准 PDF 模板 — A4 排版，中文字体，表格边框，自动分页</summary>
    public class StandardPdfTemplate : IReportTemplate
    {
        private readonly string _fontFamily;
        private XFont _titleFont, _sectionFont, _tableHeaderFont, _tableCellFont, _textFont, _footerFont;

        private const double MarginMm = 20;
        private const double FooterMm = 15;
        private static double MmToPt(double mm) => mm * 72.0 / 25.4;
        private static readonly double MarginPt = MmToPt(MarginMm);
        private static readonly XColor HeaderBgColor = XColor.FromArgb(240, 240, 240);
        private static readonly XColor BorderColor = XColor.FromArgb(180, 180, 180);
        private static readonly XColor TitleColor = XColor.FromArgb(30, 30, 30);
        private static readonly XColor SectionColor = XColor.FromArgb(50, 50, 50);

        public StandardPdfTemplate(string fontPath, string fontFamily)
        {
            CjkFontResolver.Register(fontPath, fontFamily);
            _fontFamily = fontFamily;
            _titleFont = new XFont(_fontFamily, 22, XFontStyle.Bold);
            _sectionFont = new XFont(_fontFamily, 15, XFontStyle.Bold);
            _tableHeaderFont = new XFont(_fontFamily, 11, XFontStyle.Bold);
            _tableCellFont = new XFont(_fontFamily, 11, XFontStyle.Regular);
            _textFont = new XFont(_fontFamily, 12, XFontStyle.Regular);
            _footerFont = new XFont(_fontFamily, 9, XFontStyle.Regular);
        }

        public byte[] Render(ReportData data)
        {
            var doc = new PdfDocument();
            doc.Info.Title = data.Title;

            NewPage(doc);

            DrawTitle(data.Title);

            if (data.Header.Count > 0)
            {
                EnsureSpace(30);
                DrawKeyValueTable(data.Header, "项目", "值");
                _y += 16;  // Header 后空行，与后续段落保持间距
            }

            foreach (var section in data.Sections)
            {
                EnsureSpace(40);
                DrawSectionTitle(section.Title);

                foreach (var block in section.Blocks)
                    RenderBlock(doc, block);

                _y += 16;  // 段落后空行
            }

            DrawFooter();

            using var ms = new MemoryStream();
            doc.Save(ms, false);
            return ms.ToArray();
        }

        // ═════════════════ 页面管理 ═════════════════

        private PdfPage _page = null!;
        private XGraphics _gfx = null!;
        private double _y;
        private double _contentWidth;
        private double _bottomMargin;
        private int _pageNum;

        private void NewPage(PdfDocument doc)
        {
            _page = doc.AddPage();
            _page.Size = PageSize.A4;
            _gfx = XGraphics.FromPdfPage(_page);
            _y = MarginPt;
            _contentWidth = _page.Width.Point - 2 * MarginPt;
            _bottomMargin = _page.Height.Point - MmToPt(FooterMm);
            _pageNum++;
        }

        private void EnsureSpace(double neededPt)
        {
            if (_y + neededPt > _bottomMargin)
            {
                DrawFooter();
                NewPage(_page.Owner);
            }
        }

        private void DrawFooter()
        {
            var footerY = _page.Height.Point - MmToPt(FooterMm / 2);
            var fmt = new XStringFormat { Alignment = XStringAlignment.Center };
            _gfx.DrawString($"第 {_pageNum} 页", _footerFont, XBrushes.Gray,
                new XRect(0, footerY, _page.Width.Point, 20), fmt);
        }

        // ═════════════════ 内容渲染 ═════════════════

        private void DrawTitle(string title)
        {
            var fmt = new XStringFormat { Alignment = XStringAlignment.Center };
            var h = _titleFont.GetHeight() + 10;
            _gfx.DrawString(title, _titleFont, new XSolidBrush(TitleColor),
                new XRect(MarginPt, _y, _contentWidth, h), fmt);
            _y += h + 4;
        }

        private void DrawSectionTitle(string title)
        {
            var h = _sectionFont.GetHeight() + 6;
            _gfx.DrawString(title, _sectionFont, new XSolidBrush(SectionColor),
                new XRect(MarginPt, _y, _contentWidth, h),
                new XStringFormat { Alignment = XStringAlignment.Near, LineAlignment = XLineAlignment.Near });
            _y += h;
        }

        private void RenderBlock(PdfDocument doc, ReportBlock block)
        {
            switch (block)
            {
                case KeyValueBlock kv:
                    DrawKeyValueTable(kv.Items, kv.LabelHeader, kv.ValueHeader);
                    _y += 4;
                    break;
                case TableBlock tb:
                    if (!string.IsNullOrEmpty(tb.Caption))
                    {
                        EnsureSpace(20);
                        _gfx.DrawString(tb.Caption, _textFont, XBrushes.Black,
                            new XRect(MarginPt, _y, _contentWidth, 16),
                            new XStringFormat { Alignment = XStringAlignment.Near, LineAlignment = XLineAlignment.Near });
                        _y += 18;
                    }
                    DrawTable(tb.Headers, tb.Rows);
                    _y += 4;
                    break;
                case TextBlock text:
                    RenderTextBlock(text);
                    _y += 4;
                    break;
            }
        }

        private void DrawKeyValueTable(List<MetaItem> items, string labelHeader, string valueHeader)
        {
            double labelW = _contentWidth * 0.35;
            double valueW = _contentWidth - labelW;
            double rowH = 22;
            double x = MarginPt;

            // header row
            EnsureSpace(rowH);
            _gfx.DrawRectangle(new XSolidBrush(HeaderBgColor), x, _y, _contentWidth, rowH);
            _gfx.DrawCellText(labelHeader, _tableHeaderFont, x + 6, _y, labelW, rowH);
            _gfx.DrawCellText(valueHeader, _tableHeaderFont, x + labelW + 6, _y, valueW, rowH);
            _gfx.DrawRectangle(new XPen(BorderColor), x, _y, _contentWidth, rowH);
            _gfx.DrawLine(new XPen(BorderColor), x + labelW, _y, x + labelW, _y + rowH);
            _y += rowH;

            // data rows
            foreach (var item in items)
            {
                var wrapped = WrapText(item.Value, valueW - 12, _tableCellFont);
                double lineHeight = _tableCellFont.GetHeight();
                rowH = Math.Max(22, wrapped.Count * lineHeight + 8);

                EnsureSpace(rowH);
                _gfx.DrawCellText(item.Label, _tableCellFont, x + 6, _y, labelW, rowH, true);
                int lines = wrapped.Count;
                if (lines == 1)
                {
                    _gfx.DrawCellText(wrapped[0], _tableCellFont, x + labelW + 6, _y, valueW, rowH, true);
                }
                else
                {
                    double yOffset = (rowH - lines * lineHeight) / 2;
                    for (int i = 0; i < lines; i++)
                        _gfx.DrawCellText(wrapped[i], _tableCellFont, x + labelW + 6,
                            _y + yOffset + i * lineHeight, valueW, lineHeight, false);
                }
                _gfx.DrawRectangle(new XPen(BorderColor), x, _y, _contentWidth, rowH);
                _gfx.DrawLine(new XPen(BorderColor), x + labelW, _y, x + labelW, _y + rowH);
                _y += rowH;
            }
        }

        private void DrawTable(string[] headers, List<string[]> rows)
        {
            double x = MarginPt;
            var colWidths = CalculateColumnWidths(headers, rows, _contentWidth);
            double headerH = 22;

            EnsureSpace(headerH);
            _gfx.DrawRectangle(new XSolidBrush(HeaderBgColor), x, _y, _contentWidth, headerH);
            double cx = x;
            for (int i = 0; i < headers.Length; i++)
            {
                _gfx.DrawCellText(headers[i], _tableHeaderFont, cx + 4, _y, colWidths[i], headerH);
                cx += colWidths[i];
            }
            _gfx.DrawRectangle(new XPen(BorderColor), x, _y, _contentWidth, headerH);
            cx = x;
            for (int i = 0; i < headers.Length - 1; i++)
            {
                cx += colWidths[i];
                _gfx.DrawLine(new XPen(BorderColor), cx, _y, cx, _y + headerH);
            }
            _y += headerH;

            foreach (var row in rows)
            {
                var wrappedCells = new List<string>[row.Length];
                int maxLines = 1;
                for (int i = 0; i < row.Length; i++)
                {
                    wrappedCells[i] = WrapText(row[i], colWidths[i] - 8, _tableCellFont);
                    maxLines = Math.Max(maxLines, wrappedCells[i].Count);
                }
                double lineHeight = _tableCellFont.GetHeight();
                double rowH = Math.Max(22, maxLines * lineHeight + 8);

                EnsureSpace(rowH);
                cx = x;
                for (int i = 0; i < row.Length; i++)
                {
                    int lines = wrappedCells[i].Count;
                    if (lines == 1)
                    {
                        _gfx.DrawCellText(wrappedCells[i][0], _tableCellFont, cx + 4, _y, colWidths[i], rowH, true);
                    }
                    else
                    {
                        double yOffset = (rowH - lines * lineHeight) / 2;
                        for (int l = 0; l < lines; l++)
                            _gfx.DrawCellText(wrappedCells[i][l], _tableCellFont, cx + 4,
                                _y + yOffset + l * lineHeight, colWidths[i], lineHeight, false);
                    }
                    cx += colWidths[i];
                }
                _gfx.DrawRectangle(new XPen(BorderColor), x, _y, _contentWidth, rowH);
                cx = x;
                for (int i = 0; i < headers.Length - 1; i++)
                {
                    cx += colWidths[i];
                    _gfx.DrawLine(new XPen(BorderColor), cx, _y, cx, _y + rowH);
                }
                _y += rowH;
            }
        }

        private void RenderTextBlock(TextBlock block)
        {
            var font = block.Bold
                ? new XFont(_fontFamily, 12, XFontStyle.Bold)
                : _textFont;

            var lines = block.Text.Split('\n');
            foreach (var line in lines)
            {
                var wrapped = WrapText(line, _contentWidth, font);
                foreach (var wl in wrapped)
                {
                    EnsureSpace(16);
                    _gfx.DrawString(wl, font, XBrushes.Black,
                        MarginPt, _y + 12);
                    _y += 14;
                }
            }
            _y += 2;
        }

        // ═════════════════ 辅助方法 ═════════════════

        private double[] CalculateColumnWidths(string[] headers, List<string[]> rows, double totalWidth)
        {
            int n = headers.Length;
            var maxW = new double[n];
            for (int i = 0; i < n; i++)
                maxW[i] = _gfx.MeasureString(headers[i], _tableHeaderFont).Width;
            foreach (var row in rows)
                for (int i = 0; i < n && i < row.Length; i++)
                    maxW[i] = Math.Max(maxW[i], _gfx.MeasureString(row[i], _tableCellFont).Width);

            const double padding = 8;
            for (int i = 0; i < n; i++)
                maxW[i] += padding * 2;

            double sum = maxW.Sum();
            if (sum > totalWidth)
            {
                double scale = totalWidth / sum;
                for (int i = 0; i < n; i++)
                    maxW[i] *= scale;
            }
            return maxW;
        }

        private List<string> WrapText(string text, double maxWidth, XFont font)
        {
            var result = new List<string>();
            if (string.IsNullOrEmpty(text))
            {
                result.Add("");
                return result;
            }

            var paragraphs = text.Split('\n');
            foreach (var para in paragraphs)
            {
                if (string.IsNullOrEmpty(para))
                {
                    result.Add("");
                    continue;
                }

                var words = para.Split(' ');
                var current = "";
                foreach (var word in words)
                {
                    var test = current == "" ? word : current + " " + word;
                    if (_gfx.MeasureString(test, font).Width <= maxWidth)
                        current = test;
                    else
                    {
                        if (current != "") result.Add(current);
                        if (_gfx.MeasureString(word, font).Width > maxWidth)
                        {
                            current = "";
                            foreach (var ch in word)
                            {
                                var test2 = current + ch;
                                if (_gfx.MeasureString(test2, font).Width <= maxWidth)
                                    current = test2;
                                else
                                {
                                    result.Add(current);
                                    current = ch.ToString();
                                }
                            }
                        }
                        else
                            current = word;
                    }
                }
                if (current != "") result.Add(current);
            }
            return result;
        }
    }

    internal static class GfxExtensions
    {
        public static void DrawCellText(this XGraphics gfx, string text, XFont font,
            double x, double y, double width, double height, bool centerVertical = true)
        {
            var rect = new XRect(x, y, width, height);
            var fmt = new XStringFormat
            {
                Alignment = XStringAlignment.Near,
                LineAlignment = centerVertical ? XLineAlignment.Center : XLineAlignment.Near,
            };
            gfx.DrawString(text, font, XBrushes.Black, rect, fmt);
        }
    }
}
