using System;
using System.Threading.Tasks;
using System.IO;
using Elwig.Helpers;
using System.Collections.Generic;
using System.Linq;
using Elwig.Helpers.Printing;
using MimeKit;
using System.Threading;

namespace Elwig.Documents {
    public abstract partial class Document : IDisposable {

        public static string Name => "Dokument";

        protected static readonly double GenerationProportion = 0.125;

        protected TempFile? _pdfFile = null;
        protected string? _pdfPath;
        protected string? PdfPath => _pdfPath ?? _pdfFile?.FilePath;
        public int? TotalPages { get; private set; }
        public int? Pages => TotalPages / (IsDoublePaged ? 2 : 1);

        public bool ShowFoldMarks = App.Config.Debug;
        public bool IsDoublePaged = false;
        public bool IsPreview = false;

        public string DocumentsPath;
        public int CurrentNextSeason;
        public string? DocumentId;
        public string Title;
        public string Author;
        public string Header;
        public string Footer;
        public DateOnly Date;

        public Document(string title) {
            var c = App.Client;
            DocumentsPath = App.DocumentsPath;
            CurrentNextSeason = Utils.CurrentNextSeason;
            Title = title;
            Author = c.NameFull;
            Header = "";
            Footer = Utils.GenerateFooter("<br/>", " \u00b7 ").Item(c.NameFull).ToString();
            Date = DateOnly.FromDateTime(Utils.Today);
        }

        ~Document() {
            Dispose();
        }

        public void Dispose() {
            _pdfFile?.Dispose();
            _pdfFile = null;
            GC.SuppressFinalize(this);
        }

        public static Document Merge(IEnumerable<Document> docs) {
            return new MergedDocument(docs);
        }

        public static Document FromPdf(string path) {
            return new PdfDocument(path);
        }

        private async Task<string> Render() {
            string name;
            if (this is BusinessLetter) {
                name = "BusinessLetter";
            } else if (this is DeliveryNote) {
                name = "DeliveryNote";
            } else if (this is CreditNote) {
                name = "CreditNote";
            } else if (this is DeliveryJournal) {
                name = "DeliveryJournal";
            } else if (this is DeliveryDepreciationList) {
                name = "DeliveryDepreciationList";
            } else if (this is Letterhead) {
                name = "Letterhead";
            } else if (this is DeliveryConfirmation) {
                name = "DeliveryConfirmation";
            } else if (this is MemberDataSheet) {
                name = "MemberDataSheet";
            } else if (this is MemberList) {
                name = "MemberList";
            } else if (this is WineQualityStatistics) {
                name = "WineQualityStatistics";
            } else if (this is PaymentVariantSummary) {
                name = "PaymentVariantSummary";
            } else if (this is DeliveryAncmtList) {
                name = "DeliveryAncmtList";
            } else {
                throw new InvalidOperationException("Invalid document object");
            }
            return await Render(name);
        }

        private async Task<string> Render(string name) {
            return await Html.CompileRenderAsync(name, this); ;
        }

        public async Task Generate(CancellationToken? cancelToken = null, IProgress<double>? progress = null) {
            if (_pdfFile != null)
                return;
            progress?.Report(0.0);
            if (this is PdfDocument) {
                // nothing to do
            } else if (this is MergedDocument m) {
                var pdf = new TempFile("pdf");
                var tmpHtmls = new List<TempFile>();
                var tmpFiles = new List<string>();
                try {
                    var n = m.Documents.Count();
                    int i = 0;
                    foreach (var doc in m.Documents) {
                        if (doc is PdfDocument) {
                            tmpFiles.Add(doc.PdfPath!);
                            continue;
                        }
                        if (cancelToken?.IsCancellationRequested ?? false)
                            throw new OperationCanceledException("Dokumentenerzeugung abgebrochen!");
                        var tmpHtml = new TempFile("html");
                        await File.WriteAllTextAsync(tmpHtml.FilePath, await doc.Render(), Utils.UTF8);
                        tmpHtmls.Add(tmpHtml);
                        tmpFiles.Add((doc is Letterhead ? "#" : "") + tmpHtml.FileName);
                        i++;
                        progress?.Report(GenerationProportion * 100 * i / n);
                    }
                    progress?.Report(GenerationProportion * 100);
                    var pages = await Pdf.Convert(tmpFiles, pdf.FileName, IsDoublePaged, cancelToken, new Progress<double>(v => progress?.Report(GenerationProportion * 100 + v * (1 - GenerationProportion))));
                    TotalPages = pages.Pages;
                    _pdfFile = pdf;
                } catch {
                    pdf.Dispose();
                    throw;
                } finally {
                    foreach (var tmp in tmpHtmls) {
                        tmp.Dispose();
                    }
                }
            } else {
                if (cancelToken?.IsCancellationRequested ?? false)
                    throw new OperationCanceledException("Dokumentenerzeugung abgebrochen!");
                var pdf = new TempFile("pdf");
                try {
                    using var tmpHtml = new TempFile("html");
                    await File.WriteAllTextAsync(tmpHtml.FilePath, await Render(), Utils.UTF8);
                    progress?.Report(50.0);
                    var pages = await Pdf.Convert(tmpHtml.FilePath, pdf.FilePath, IsDoublePaged, cancelToken);
                    TotalPages = pages.Pages;
                    _pdfFile = pdf;
                } catch {
                    pdf.Dispose();
                    throw;
                }
            }
            progress?.Report(100.0);
        }

        public void SaveTo(string pdfPath) {
            if (PdfPath == null) throw new InvalidOperationException("Pdf file has not been generated yet");
            File.Copy(PdfPath, pdfPath, true);
        }

        public async Task Print(int copies = 1) {
            if (PdfPath == null) throw new InvalidOperationException("Pdf file has not been generated yet");
            await Pdf.Print(PdfPath, copies, IsDoublePaged);
        }

        public void Show() {
            if (_pdfFile == null) throw new InvalidOperationException("Pdf file has not been generated yet");
            Pdf.Show(_pdfFile.NewReference(), Title + (this is BusinessDocument b ? $" - {b.Member.FullName}" : ""));
        }

        public MimePart AsEmailAttachment(string filename) {
            if (PdfPath == null) throw new InvalidOperationException("Pdf file has not been generated yet");
            return new("application", "pdf") {
                Content = new MimeContent(File.OpenRead(PdfPath)),
                ContentDisposition = new ContentDisposition(ContentDisposition.Attachment),
                ContentTransferEncoding = ContentEncoding.Base64,
                FileName = filename
            };
        }

        private class MergedDocument(IEnumerable<Document> docs) : Document("Mehrere Dokumente") {
            public IEnumerable<Document> Documents = docs;
        }

        private class PdfDocument : Document {
            public PdfDocument(string pdfPath) :
                base(Path.GetFileNameWithoutExtension(pdfPath)) {
                _pdfPath = pdfPath;
            }
        }
    }
}
