using Elwig.Documents;
using Elwig.Helpers;
using Elwig.Helpers.Billing;
using Elwig.Models.Dtos;
using Elwig.Models.Entities;
using MailKit.Net.Smtp;
using Microsoft.EntityFrameworkCore;
using Microsoft.Win32;
using MimeKit;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace Elwig.Windows {
    public partial class MailWindow : ContextWindow {

        // used for document sorting while generating!
        public enum DocType { Undefined, Custom, MemberDataSheet, DeliveryConfirmation, CreditNote }

        public class SelectedDoc(DocType type, string name, object? details = null) {
            public DocType Type = type;
            public string Name { get; set; } = name;
            public object? Details = details;
        }

        public class GeneratedDoc {
            public DocType Type;
            public Document Doc;

            public GeneratedDoc(string pdfPath) {
                Type = DocType.Custom;
                Doc = Document.FromPdf(pdfPath);
            }

            public GeneratedDoc(Document doc) {
                Type = doc is MemberDataSheet ? DocType.MemberDataSheet :
                    doc is DeliveryConfirmation ? DocType.DeliveryConfirmation :
                    doc is CreditNote ? DocType.CreditNote : DocType.Undefined;
                Doc = doc;
            }
        }

        public static readonly string[] AvaiableDocuments = [
            MemberDataSheet.Name,
            DeliveryConfirmation.Name,
            CreditNote.Name,
        ];

        public readonly int Year;
        public ObservableCollection<SelectedDoc> SelectedDocs = [];
        public IEnumerable<Member> Recipients = [];

        protected Document? PrintDocument;
        protected Dictionary<Member, List<Document>>? PrintMemberDocuments;
        protected Dictionary<Member, List<Document>>? EmailDocuments;

        private CancellationTokenSource? CancelGeneration;

        public static readonly DependencyProperty PostalAllCountProperty = DependencyProperty.Register(nameof(PostalAllCount), typeof(int), typeof(MailWindow));
        public int PostalAllCount {
            get => (int)GetValue(PostalAllCountProperty);
            private set => SetValue(PostalAllCountProperty, value);
        }

        public static readonly DependencyProperty PostalWishCountProperty = DependencyProperty.Register(nameof(PostalWishCount), typeof(int), typeof(MailWindow));
        public int PostalWishCount {
            get => (int)GetValue(PostalWishCountProperty);
            private set => SetValue(PostalWishCountProperty, value);
        }

        public static readonly DependencyProperty PostalNoEmailCountProperty = DependencyProperty.Register(nameof(PostalNoEmailCount), typeof(int), typeof(MailWindow));
        public int PostalNoEmailCount {
            get => (int)GetValue(PostalNoEmailCountProperty);
            private set => SetValue(PostalNoEmailCountProperty, value);
        }

        public static readonly DependencyProperty EmailAllCountProperty = DependencyProperty.Register(nameof(EmailAllCount), typeof(int), typeof(MailWindow));
        public int EmailAllCount {
            get => (int)GetValue(EmailAllCountProperty);
            private set => SetValue(EmailAllCountProperty, value);
        }

        public static readonly DependencyProperty EmailWishCountProperty = DependencyProperty.Register(nameof(EmailWishCount), typeof(int), typeof(MailWindow));
        public int EmailWishCount {
            get => (int)GetValue(EmailWishCountProperty);
            private set => SetValue(EmailWishCountProperty, value);
        }

        private ICommand? _deleteCommand;
        public ICommand DeleteCommand => _deleteCommand ??= new ActionCommand(() => {
            var idx = SelectedDocumentsList.SelectedIndex;
            if (idx == -1)
                return;
            SelectedDocs.RemoveAt(SelectedDocumentsList.SelectedIndex);
            SelectedDocumentsList.SelectedIndex = idx < SelectedDocumentsList.Items.Count ? idx : idx - 1;
        });

        // powershell -Command "$(Get-WmiObject -Class Win32_Printer | Where-Object {$_.Default -eq $True}).Name"
        public MailWindow(int? year = null) {
            InitializeComponent();
            using (var ctx = new AppDbContext()) {
                Year = year ?? ctx.Seasons.OrderBy(s => s.Year).LastOrDefault()?.Year ?? Utils.Today.Year;
                Title = $"Rundschreiben - Lese {Year} - Elwig";
            }

            AvaiableDocumentsList.ItemsSource = AvaiableDocuments;
            SelectedDocumentsList.ItemsSource = SelectedDocs;

            DocumentNonDeliverersInput.Visibility = Visibility.Hidden;
            DocumentFooterLabel.Visibility = Visibility.Hidden;
            DeliveryConfirmationFooterInput.Visibility = Visibility.Hidden;
            CreditNoteFooterInput.Visibility = Visibility.Hidden;
            RecipientsActiveMembersInput.IsChecked = true;

            MemberOrganicIndifferentInput.IsChecked = true;
            MemberFunktionärIndifferentInput.IsChecked = true;

            DeliveryConfirmationFooterInput.Text = App.Client.TextDeliveryConfirmation;
            CreditNoteFooterInput.Text = App.Client.TextCreditNote;

            DocumentNonDeliverersInput.IsChecked = App.Client.MailIncludeNonDeliverers;
            DoublePagedInput.IsChecked = App.Client.MailDoublePaged;

            switch (App.Client.MailSendPostal) {
                case 0: PostalNobodyInput.IsChecked = true; break;
                case 1: PostalNoEmailInput.IsChecked = true; break;
                case 2: PostalWishInput.IsChecked = true; break;
                case 3: PostalAllInput.IsChecked = true; break;
            }
            switch (App.Client.MailSendEmail) {
                case 0: EmailNobodyInput.IsChecked = true; break;
                case 1: EmailWishInput.IsChecked = true; break;
                case 2: EmailAllInput.IsChecked = true; break;
            }
            switch (App.Client.MailOrdering) {
                case 0: OrderMgNrInput.IsChecked = true; break;
                case 1: OrderNameInput.IsChecked = true; break;
                case 2: OrderPlzInput.IsChecked = true; break;
            }

            PostalSender1.Text = App.Client.Sender1;
            PostalSender2.Text = App.Client.Sender2;
            PostalLocation.Text = App.BranchLocation;
            PostalDate.Text = $"{Utils.Today:dd.MM.yyyy}";
            EmailSubjectInput.Text = App.Client.TextEmailSubject ?? "Rundschreiben";
            EmailBodyInput.Text = App.Client.TextEmailBody ?? "Sehr geehrtes Mitglied,\n\nim Anhang finden Sie das aktuelle Rundschreiben.\n\nIhre Winzergenossenschaft\n";
        }

        protected override async Task OnRenewContext(AppDbContext ctx) {
            var season = await ctx.Seasons.FindAsync(Year);
            var l = new List<string> {
                MemberDataSheet.Name
            };
            if (season != null) {
                l.Add($"{DeliveryConfirmation.Name} {Year}");
                l.AddRange(season.PaymentVariants.OrderBy(v => v.AvNr).Select(v => $"{CreditNote.Name} – {v.Name}"));
            }
            AvaiableDocumentsList.ItemsSource = l;

            ControlUtils.RenewItemsSource(MemberBranchInput, await ctx.Branches
                .Where(b => b.Members.Count != 0)
                .OrderBy(b => b.Name)
                .ToListAsync(), MemberInput_SelectionChanged);
            if (MemberBranchInput.SelectedItems.Count == 0) {
                MemberBranchInput.SelectionChanged -= MemberInput_SelectionChanged;
                MemberBranchInput.SelectAll();
                MemberBranchInput.SelectionChanged += MemberInput_SelectionChanged;
            }
            ControlUtils.RenewItemsSource(MemberKgInput, await ctx.Katastralgemeinden
                .Where(k => k.WbKg!.Members.Count != 0)
                .OrderBy(k => k.Name)
                .ToListAsync(), MemberInput_SelectionChanged);
            if (MemberKgInput.SelectedItems.Count == 0) {
                MemberKgInput.SelectionChanged -= MemberInput_SelectionChanged;
                MemberKgInput.SelectAll();
                MemberKgInput.SelectionChanged += MemberInput_SelectionChanged;
            }
            ControlUtils.RenewItemsSource(MemberAreaComInput, await ctx.AreaCommitmentTypes
                .OrderBy(a => a.VtrgId)
                .ToListAsync(), MemberInput_SelectionChanged);
            if (MemberAreaComInput.SelectedItems.Count == 0) {
                MemberAreaComInput.SelectionChanged -= MemberInput_SelectionChanged;
                MemberAreaComInput.SelectAll();
                MemberAreaComInput.SelectionChanged += MemberInput_SelectionChanged;
            }
            ControlUtils.RenewItemsSource(MemberDeliveryAncmtInput, await ctx.DeliverySchedules
                .Where(s => s.Year == Year)
                .OrderBy(s => s.DateString)
                .ThenBy(s => s.Branch.Name)
                .ThenBy(s => s.Description)
                .ToListAsync(), MemberInput_SelectionChanged);
            if (MemberDeliveryAncmtInput.SelectedItems.Count == 0) {
                MemberDeliveryAncmtInput.SelectionChanged -= MemberInput_SelectionChanged;
                MemberDeliveryAncmtInput.SelectAll();
                MemberDeliveryAncmtInput.SelectionChanged += MemberInput_SelectionChanged;
            }
            ControlUtils.RenewItemsSource(MemberCustomInput, await ctx.Members
                .Where(m => m.IsActive)
                .OrderBy(m => m.Name)
                .ThenBy(m => m.GivenName)
                .Include(m => m.Branch)
                .Include(m => m.DefaultWbKg!.AtKg)
                .Include(m => m.EmailAddresses)
                .Include(m => m.TelephoneNumbers)
                .Include(m => m.PostalDest.AtPlz!.Ort)
                .Include(m => m.PostalDest.AtPlz!.Country)
                .Include(m => m.BillingAddress!.PostalDest.AtPlz!.Ort)
                .Include(m => m.BillingAddress!.PostalDest.AtPlz!.Country)
                .ToListAsync(), MemberInput_SelectionChanged);
            if (MemberCustomInput.SelectedItems.Count == 0) {
                MemberCustomInput.SelectionChanged -= MemberInput_SelectionChanged;
                MemberCustomInput.SelectAll();
                MemberCustomInput.SelectionChanged += MemberInput_SelectionChanged;
            }

            await UpdateRecipients(ctx);
        }

        private void ResetDocuments() {
            DisposeDocs();
            if (IsLoaded) {
                PreviewButton.IsEnabled = false;
                PrintButton.IsEnabled = false;
                EmailButton.IsEnabled = false;
                ProgressBar.Value = 0;
            }
        }

        private void LockInputs() {
            DocumentAddButton.IsEnabled = false;
            DocumentRemoveButton.IsEnabled = false;
            SelectDocumentButton.IsEnabled = false;
            foreach (var tb in ControlUtils.FindAllChildren<TextBox>(this, []))
                tb.IsReadOnly = true;
            foreach (var cb in ControlUtils.FindAllChildren<ComboBox>(this, []))
                cb.IsEnabled = false;
            foreach (var cb in ControlUtils.FindAllChildren<CheckBox>(this, []))
                cb.IsEnabled = false;
            foreach (var lb in ControlUtils.FindAllChildren<ListBox>(this, []))
                lb.IsEnabled = false;
            foreach (var rb in ControlUtils.FindAllChildren<RadioButton>(this, []))
                rb.IsEnabled = false;
        }

        private void UnlockInputs() {
            DocumentAddButton.IsEnabled = true;
            DocumentRemoveButton.IsEnabled = true;
            SelectDocumentButton.IsEnabled = true;
            foreach (var tb in ControlUtils.FindAllChildren<TextBox>(this, []))
                tb.IsReadOnly = false;
            foreach (var cb in ControlUtils.FindAllChildren<ComboBox>(this, []))
                cb.IsEnabled = true;
            foreach (var cb in ControlUtils.FindAllChildren<CheckBox>(this, []))
                cb.IsEnabled = true;
            foreach (var lb in ControlUtils.FindAllChildren<ListBox>(this, []))
                lb.IsEnabled = true;
            foreach (var rb in ControlUtils.FindAllChildren<RadioButton>(this, []))
                rb.IsEnabled = true;
        }

        private void Menu_Help_Log_Click(object sender, RoutedEventArgs evt) {
            var w = new MailLogWindow();
            w.Show();
        }

        private void ContinueButton_Click(object sender, RoutedEventArgs evt) {
            TabControl.SelectedIndex = 1;
            TabControl.AllowDrop = false;
        }

        private void BackButton_Click(object sender, RoutedEventArgs evt) {
            TabControl.SelectedIndex = 0;
            TabControl.AllowDrop = true;
        }

        private async void Document_Drop(object sender, DragEventArgs evt) {
            if (evt.Data.GetDataPresent(DataFormats.FileDrop)) {
                var files = (string[])evt.Data.GetData(DataFormats.FileDrop);
                foreach (var file in files) {
                    if (Path.GetExtension(file) == ".pdf") {
                        SelectedDocs.Add(new(DocType.Custom, Path.GetFileName(file), file));
                    }
                }
                using var ctx = new AppDbContext();
                await UpdateRecipients(ctx);
            }
        }

        private void Document_PreviwDragOver(object sender, DragEventArgs evt) {
            evt.Handled = TabControl.SelectedIndex == 0;
        }

        private void AvaiableDocumentsList_SelectionChanged(object sender, RoutedEventArgs evt) {
            DocumentAddButton.IsEnabled = AvaiableDocumentsList.SelectedIndex != -1;
        }

        private void SelectedDocumentsList_SelectionChanged(object sender, RoutedEventArgs evt) {
            DocumentRemoveButton.IsEnabled = SelectedDocumentsList.SelectedIndex != -1;
            if (SelectedDocumentsList.SelectedItem is SelectedDoc doc) {
                DocumentBox.Header = doc.Name;
                if (doc.Type == DocType.DeliveryConfirmation) {
                    DocumentNonDeliverersInput.Visibility = Visibility.Visible;
                    DocumentFooterLabel.Visibility = Visibility.Visible;
                    DeliveryConfirmationFooterInput.Visibility = Visibility.Visible;
                    CreditNoteFooterInput.Visibility = Visibility.Hidden;
                    DocumentFooterLabel.Margin = new(10, 40, 0, 10);
                } else if (doc.Type == DocType.CreditNote) {
                    DocumentNonDeliverersInput.Visibility = Visibility.Hidden;
                    DocumentFooterLabel.Visibility = Visibility.Visible;
                    DeliveryConfirmationFooterInput.Visibility = Visibility.Hidden;
                    CreditNoteFooterInput.Visibility = Visibility.Visible;
                    DocumentFooterLabel.Margin = new(10, 10, 0, 10);
                } else {
                    DocumentNonDeliverersInput.Visibility = Visibility.Hidden;
                    DocumentFooterLabel.Visibility = Visibility.Hidden;
                    DeliveryConfirmationFooterInput.Visibility = Visibility.Hidden;
                    CreditNoteFooterInput.Visibility = Visibility.Hidden;
                }
            } else {
                DocumentBox.Header = "Dokument";
                DocumentNonDeliverersInput.Visibility = Visibility.Hidden;
                DocumentFooterLabel.Visibility = Visibility.Hidden;
                DeliveryConfirmationFooterInput.Visibility = Visibility.Hidden;
                CreditNoteFooterInput.Visibility = Visibility.Hidden;
            }
        }

        private async void DocumentAddButton_Click(object sender, RoutedEventArgs evt) {
            var idx = AvaiableDocumentsList.SelectedIndex;
            using var ctx = new AppDbContext();
            if (AvaiableDocumentsList.SelectedItem is not string s)
                return;
            if (idx == 0) {
                SelectedDocs.Add(new(DocType.MemberDataSheet, s, null));
            } else if (idx == 1) {
                SelectedDocs.Add(new(DocType.DeliveryConfirmation, s, Year));
                RecipientsDeliveryMembersInput.IsChecked = true;
            } else if (idx >= 2) {
                var name = s.Split(" – ")[^1];
                var pv = await ctx.PaymentVariants.SingleAsync(v => v.Year == Year && v.Name == name)!;
                SelectedDocs.Add(new(DocType.CreditNote, s, (pv.Year, pv.AvNr)));
                RecipientsDeliveryMembersInput.IsChecked = true;
            }
            SelectedDocumentsList.SelectedIndex = SelectedDocs.Count - 1;
            await UpdateRecipients(ctx);
        }

        private async void DocumentRemoveButton_Click(object sender, RoutedEventArgs evt) {
            DeleteCommand.Execute(null);
            using var ctx = new AppDbContext();
            await UpdateRecipients(ctx);
        }

        private async void SelectDocumentButton_Click(object sender, RoutedEventArgs evt) {
            var d = new OpenFileDialog() {
                Title = "Dokument auswählen - Elwig",
                DefaultExt = "pdf",
                Filter = "PDF-Dokument (*.pdf)|*.pdf",
                Multiselect = true,
            };
            if (d.ShowDialog() == true) {
                foreach (var file in d.FileNames) {
                    if (Path.GetExtension(file) == ".pdf") {
                        SelectedDocs.Add(new(DocType.Custom, Path.GetFileName(file), file));
                    }
                }
                using var ctx = new AppDbContext();
                await UpdateRecipients(ctx);
            }
        }

        private async void RecipientsInput_Changed(object sender, RoutedEventArgs evt) {
            var vis = RecipientsCustomInput.IsChecked == true ? Visibility.Hidden : Visibility.Visible;
            MemberBranchLabel.Visibility = vis;
            MemberBranchInput.Visibility = vis;
            MemberKgLabel.Visibility = vis;
            MemberKgInput.Visibility = vis;
            MemberOrganicLabel.Visibility = vis;
            MemberOrganicYesInput.Visibility = vis;
            MemberOrganicNoInput.Visibility = vis;
            MemberOrganicIndifferentInput.Visibility = vis;
            MemberFunktionärLabel.Visibility = vis;
            MemberFunktionärYesInput.Visibility = vis;
            MemberFunktionärNoInput.Visibility = vis;
            MemberFunktionärIndifferentInput.Visibility = vis;
            MemberAreaComInput.Visibility = RecipientsAreaComMembersInput.IsChecked == true ? Visibility.Visible : Visibility.Hidden;
            MemberAreaComLabel.Visibility = RecipientsAreaComMembersInput.IsChecked == true ? Visibility.Visible : Visibility.Hidden;
            MemberDeliveryAncmtInput.Visibility = RecipientsDeliveryAncmtMembersInput.IsChecked == true ? Visibility.Visible : Visibility.Hidden;
            MemberDeliveryAncmtLabel.Visibility = RecipientsDeliveryAncmtMembersInput.IsChecked == true ? Visibility.Visible : Visibility.Hidden;
            MemberCustomInput.Visibility = RecipientsCustomInput.IsChecked == true ? Visibility.Visible : Visibility.Hidden;
            using var ctx = new AppDbContext();
            await UpdateRecipients(ctx);
        }

        private async void MemberInput_SelectionChanged(object sender, SelectionChangedEventArgs evt) {
            using var ctx = new AppDbContext();
            await UpdateRecipients(ctx);
        }

        private async void MemberInput_Checked(object sender, RoutedEventArgs evt) {
            using var ctx = new AppDbContext();
            await UpdateRecipients(ctx);
        }

        private void Date_TextChanged(object sender, RoutedEventArgs evt) {
            Validator.CheckDate((TextBox)sender, true);
            ResetDocuments();
        }

        private void Date_LostFocus(object sender, RoutedEventArgs evt) {
            var res = Validator.CheckDate((TextBox)sender, true);
            if (!res.IsValid) ((TextBox)sender).Text = $"{Utils.Today:dd.MM.yyyy}";
        }

        private async Task UpdateRecipients(AppDbContext ctx) {
            if (RecipientsCustomInput.IsChecked == true) {
                Recipients = MemberCustomInput.SelectedItems.Cast<Member>().ToList();
            } else {
                IQueryable<Member> query = ctx.Members;
                if (MemberBranchInput.SelectedItems.Count != MemberBranchInput.Items.Count) {
                    var zwst = MemberBranchInput.SelectedItems.Cast<Branch>().Select(b => b.ZwstId).ToList();
                    query = query.Where(m => zwst.Contains(m.ZwstId!));
                }
                if (MemberKgInput.SelectedItems.Count != MemberKgInput.Items.Count) {
                    var kgs = MemberKgInput.SelectedItems.Cast<AT_Kg>().Select(k => k.KgNr).ToList();
                    query = query.Where(m => kgs.Contains((int)m.DefaultKgNr!));
                }
                if (MemberOrganicYesInput.IsChecked == true) {
                    query = query.Where(m => m.IsOrganic);
                } else if (MemberOrganicNoInput.IsChecked == true) {
                    query = query.Where(m => !m.IsOrganic);
                }
                if (MemberFunktionärYesInput.IsChecked == true) {
                    query = query.Where(m => m.IsFunktionär);
                } else if (MemberFunktionärNoInput.IsChecked == true) {
                    query = query.Where(m => !m.IsFunktionär);
                }

                if (RecipientsAreaComMembersInput.IsChecked == true) {
                    var vtrg = MemberAreaComInput.SelectedItems.Cast<AreaComType>().Select(a => a.VtrgId).ToList();
                    query = query.Where(m => m.IsActive && m.AreaCommitments.AsQueryable().Where(Utils.ActiveAreaCommitments(Year)).Any(c => vtrg.Contains(c.VtrgId)));
                } else if (RecipientsDeliveryAncmtMembersInput.IsChecked == true) {
                    var dsnrs = MemberDeliveryAncmtInput.SelectedItems.Cast<DeliverySchedule>().Select(s => s.DsNr).ToList();
                    query = query.Where(m => m.Announcements.Any(a => a.Year == Year && dsnrs.Contains(a.DsNr)));
                } else if (RecipientsDeliveryMembersInput.IsChecked == true) {
                    query = query.Where(m => m.Deliveries.Any(d => d.Year == Year));
                } else if (RecipientsNonDeliveryMembersInput.IsChecked == true) {
                    query = query.Where(m => m.IsActive && !m.Deliveries.Any(d => d.Year == Year));
                } else if  (RecipientsActiveMembersInput.IsChecked == true && SelectedDocs.Any(d => d.Type == DocType.DeliveryConfirmation || d.Type == DocType.CreditNote)) {
                    query = query.Where(m => m.IsActive || m.Deliveries.Any(d => d.Year == Year));
                } else {
                    query = query.Where(m => m.IsActive);
                }
                Recipients = await query
                    .Include(m => m.Branch)
                    .Include(m => m.DefaultWbKg!.AtKg)
                    .Include(m => m.EmailAddresses)
                    .Include(m => m.TelephoneNumbers)
                    .Include(m => m.PostalDest.AtPlz!.Ort)
                    .Include(m => m.PostalDest.AtPlz!.Country)
                    .Include(m => m.BillingAddress!.PostalDest.AtPlz!.Ort)
                    .Include(m => m.BillingAddress!.PostalDest.AtPlz!.Country)
                    .ToListAsync();
            }
            UpdatePostalEmailRecipients();
        }

        private void EmailInput_Changed(object sender, RoutedEventArgs evt) {
            UpdatePostalEmailRecipients();
        }

        private void UpdatePostalEmailRecipients() {
            var modeEmail = EmailAllInput.IsChecked == true ? 2 : EmailWishInput.IsChecked == true ? 1 : 0;
            var modePostal = PostalAllInput.IsChecked == true ? 3 : PostalWishInput.IsChecked == true ? 2 : PostalNoEmailInput.IsChecked == true ? 1 : 0;
            EmailAllCount = Recipients.Count(m => m.EmailAddresses.Count > 0);
            EmailWishCount = Recipients.Count(m => m.EmailAddresses.Count > 0 && m.ContactViaEmail);
            PostalAllCount = Recipients.Count();
            PostalWishCount = Recipients.Count(m => m.ContactViaPost);
            var countEmail = (modeEmail == 2 ? EmailAllCount : modeEmail == 1 ? EmailWishCount : 0);
            PostalNoEmailCount = PostalAllCount - countEmail;
            var countPostal = (modePostal == 3 ? PostalAllCount : modePostal == 2 ? PostalWishCount : modePostal == 1 ? PostalNoEmailCount : 0);
            if (IsLoaded) {
                StatusRecipients.Text = $"{Recipients.Count():N0}";
                StatusPostalRecipients.Text = $"{countPostal:N0}";
                StatusEmailRecipients.Text = $"{countEmail:N0}";
            }
            ResetDocuments();
        }

        private async Task UpdateClientParameters() {
            var changed = false;

            var dcInclude = DocumentNonDeliverersInput.IsChecked ?? false;
            if (dcInclude != App.Client.MailIncludeNonDeliverers) {
                App.Client.MailIncludeNonDeliverers = dcInclude;
                changed = true;
            }

            var dcText = DeliveryConfirmationFooterInput.Text.Trim();
            if (dcText.Length == 0) dcText = null;
            if (dcText != App.Client.TextDeliveryConfirmation) {
                App.Client.TextDeliveryConfirmation = dcText;
                changed = true;
            }

            var cdText = CreditNoteFooterInput.Text.Trim();
            if (cdText.Length == 0) cdText = null;
            if (cdText != App.Client.TextCreditNote) {
                App.Client.TextCreditNote = cdText;
                changed = true;
            }

            var sendPostal = 0;
            if (PostalAllInput.IsChecked ?? false) {
                sendPostal = 3;
            } else if (PostalWishInput.IsChecked ?? false) {
                sendPostal = 2;
            } else if (PostalNoEmailInput.IsChecked ?? false) {
                sendPostal = 1;
            }
            if (sendPostal != App.Client.MailSendPostal) {
                App.Client.MailSendPostal = sendPostal;
                changed = true;
            }

            var sendEmail = 0;
            if (EmailAllInput.IsChecked ?? false) {
                sendEmail = 2;
            } else if (EmailWishInput.IsChecked ?? false) {
                sendEmail = 1;
            }
            if (sendEmail != App.Client.MailSendEmail) {
                App.Client.MailSendEmail = sendEmail;
                changed = true;
            }

            var ordering = 0;
            if (OrderNameInput.IsChecked ?? false) {
                ordering = 1;
            } else if (OrderPlzInput.IsChecked ?? false) {
                ordering = 2;
            }
            if (ordering != App.Client.MailOrdering) {
                App.Client.MailOrdering = ordering;
                changed = true;
            }

            var mailDoublePaged = DoublePagedInput.IsChecked ?? false;
            if (mailDoublePaged != App.Client.MailDoublePaged) {
                App.Client.MailDoublePaged = mailDoublePaged;
                changed = true;
            }

            var sender2 = PostalSender2.Text.Trim();
            if (sender2 != App.Client.Sender2) {
                App.Client.Sender2 = sender2;
                changed = true;
            }

            var emailSubject = EmailSubjectInput.Text.Trim();
            if (emailSubject.Length == 0) emailSubject = null;
            if (emailSubject != App.Client.TextEmailSubject) {
                App.Client.TextEmailSubject = emailSubject;
                changed = true;
            }

            var emailBody = EmailBodyInput.Text.Trim();
            if (emailBody.Length == 0) emailBody = null;
            if (emailBody != App.Client.TextEmailBody) {
                App.Client.TextEmailBody = emailBody;
                changed = true;
            }

            if (changed)
                await App.Client.UpdateValues();
        }

        private void DisposeDocs() {
            PrintDocument?.Dispose();
            PrintDocument = null;
            if (EmailDocuments != null) {
                foreach (var (m, docs) in EmailDocuments) {
                    foreach (var d in docs) {
                        d.Dispose();
                    }
                }
                EmailDocuments = null;
            }
        }

        private void Window_Closed(object sender, EventArgs evt) {
            CancelGeneration?.Dispose();
            DisposeDocs();
        }

        private async void AbortButton_Click(object sender, RoutedEventArgs evt) {
            AbortButton.IsEnabled = false;
            CancelGeneration?.Cancel();
        }

        private async void GenerateButton_Click(object sender, RoutedEventArgs evt) {
            LockInputs();
            PreviewButton.IsEnabled = false;
            PrintButton.IsEnabled = false;
            EmailButton.IsEnabled = false;
            Mouse.OverrideCursor = Cursors.Wait;
            AbortButton.IsEnabled = true;
            AbortButton.Visibility = Visibility.Visible;
            GenerateButton.IsEnabled = false;
            GenerateButton.Visibility = Visibility.Hidden;

            DisposeDocs();
            await UpdateClientParameters();

            CancelGeneration?.Dispose();
            CancelGeneration = new();

            using var ctx = new AppDbContext();

            var doublePaged = DoublePagedInput.IsChecked == true;
            var location = PostalLocation.Text.Trim();
            var docs = SelectedDocs.OrderByDescending(d => d.Type).ToList();

            IEnumerable<Member> recipients = Recipients;
            if (OrderMgNrInput.IsChecked == true) {
                recipients = recipients
                    .OrderBy(m => m.MgNr)
                    .ToList();
            } else if (OrderNameInput.IsChecked == true) {
                recipients = recipients
                    .OrderBy(m => m.Name)
                    .ThenBy(m => m.GivenName)
                    .ThenBy(m => m.MgNr)
                    .ToList();
            } else if (OrderPlzInput.IsChecked == true) {
                if (docs.Any(d => d.Type == DocType.DeliveryConfirmation || d.Type == DocType.CreditNote)) {
                    recipients = recipients
                        .OrderBy(m => m.BillingAddress?.PostalDest.AtPlz?.Plz ?? m.PostalDest.AtPlz?.Plz)
                        .ThenBy(m => m.BillingAddress?.PostalDest.AtPlz?.Ort.Name ?? m.PostalDest.AtPlz?.Ort.Name)
                        .ThenBy(m => m.Name)
                        .ThenBy(m => m.GivenName)
                        .ThenBy(m => m.MgNr)
                        .ToList();
                } else {
                    recipients = recipients
                        .OrderBy(m => m.PostalDest.AtPlz?.Plz)
                        .ThenBy(m => m.PostalDest.AtPlz?.Ort.Name)
                        .ThenBy(m => m.Name)
                        .ThenBy(m => m.GivenName)
                        .ThenBy(m => m.MgNr)
                        .ToList();
                }
            }

            Dictionary<int, IDictionary<int, DeliveryConfirmationDeliveryData>> dcData = [];
            Dictionary<(int, int), (IDictionary<int, CreditNoteDeliveryData>, IDictionary<int, PaymentMember>, BillingData)> cnData = [];
            foreach (var doc in docs) {
                if (doc.Type == DocType.DeliveryConfirmation) {
                    var year = (int)doc.Details!;

                    try {
                        var b = new Billing(year);
                        await b.FinishSeason();
                        await b.CalculateBuckets();
                        App.HintContextChange();

                        dcData[year] = await DeliveryConfirmationDeliveryData.ForSeason(ctx.DeliveryParts, year);
                    } catch (Exception exc) {
                        MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
                        UnlockInputs();
                        GenerateButton.IsEnabled = true;
                        GenerateButton.Visibility = Visibility.Visible;
                        AbortButton.IsEnabled = false;
                        AbortButton.Visibility = Visibility.Hidden;
                        Mouse.OverrideCursor = null;
                        return;
                    }
                } else if (doc.Type == DocType.CreditNote) {
                    var details = ((int, int))doc.Details!;
                    var year = details.Item1;
                    var avnr = details.Item2;
                    try {
                        cnData[(year, avnr)] = (
                            await CreditNoteDeliveryData.ForPaymentVariant(ctx.CreditNoteDeliveryRows, ctx.PaymentVariants, year, avnr),
                            await ctx.MemberPayments.Where(p => p.Year == year && p.AvNr == avnr).ToDictionaryAsync(c => c.MgNr),
                            BillingData.FromJson((await ctx.PaymentVariants.FindAsync(year, avnr))!.Data)
                        );
                    } catch (Exception exc) {
                        MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
                        UnlockInputs();
                        GenerateButton.IsEnabled = true;
                        GenerateButton.Visibility = Visibility.Visible;
                        AbortButton.IsEnabled = false;
                        AbortButton.Visibility = Visibility.Hidden;
                        Mouse.OverrideCursor = null;
                        return;
                    }
                    await ctx.GetMemberAreaCommitmentBuckets(year, 0);
                }
            }

            var postalDate = DateOnly.ParseExact(PostalDate.Text, "dd.MM.yyyy");
            var memberDocs = recipients.Select(m => new {
                Member = m,
                Docs = docs.SelectMany<SelectedDoc, GeneratedDoc>(doc => {
                    try {
                        if (doc.Type == DocType.Custom) {
                            return [new GeneratedDoc((string)doc.Details!)];
                        } else if (doc.Type == DocType.MemberDataSheet) {
                            return [new GeneratedDoc(new MemberDataSheet(m, ctx) { Date = postalDate })];
                        } else if (doc.Type == DocType.DeliveryConfirmation) {
                            var year = (int)doc.Details!;
                            DeliveryConfirmationDeliveryData data;
                            if (dcData[year].TryGetValue(m.MgNr, out var d)) {
                                data = d;
                            } else if (App.Client.MailIncludeNonDeliverers) {
                                data = DeliveryConfirmationDeliveryData.CreateEmpty(year, m);
                            } else {
                                return [];
                            }
                            return [new GeneratedDoc(new DeliveryConfirmation(ctx, year, m, data) { Date = postalDate })];
                        } else if (doc.Type == DocType.CreditNote) {
                            var details = ((int, int))doc.Details!;
                            var year = details.Item1;
                            var avnr = details.Item2;
                            var data = cnData[(year, avnr)];
                            try {
                                return [new GeneratedDoc(new CreditNote(
                                    ctx, data.Item2[m.MgNr], data.Item1[m.MgNr],
                                    data.Item3.ConsiderContractPenalties,
                                    data.Item3.ConsiderTotalPenalty,
                                    data.Item3.ConsiderAutoBusinessShares,
                                    data.Item3.ConsiderCustomModifiers,
                                    ctx.GetMemberUnderDelivery(year, m.MgNr).GetAwaiter().GetResult()
                                ) { Date = postalDate })];
                            } catch (Exception) {
                                return [];
                            }
                        } else {
                            throw new NotImplementedException("Invalid DocType");
                        }
                    } catch (Exception exc) {
                        MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
                        return [];
                    }
                }).ToList()
            }).ToList();

            var hasPreviewDocs = memberDocs.Any(m => m.Docs.Any(d => d.Doc.IsPreview));
            if (hasPreviewDocs) {
                var res = MessageBox.Show("Einige der ausgewählten Dokumente sind nur als vorläufig zu betrachten und können daher nicht verschickt/ausgedruckt werden!\n\nDies könnte an einer noch nicht festgesetzen Auszahlungsvariante liegen.\n\nSoll mit dem Generieren fortgefahren werden?",
                    "Vorläufige Dokumente", MessageBoxButton.OKCancel, MessageBoxImage.Warning, MessageBoxResult.Cancel);
                if (res != MessageBoxResult.OK) {
                    UnlockInputs();
                    GenerateButton.IsEnabled = true;
                    GenerateButton.Visibility = Visibility.Visible;
                    AbortButton.IsEnabled = false;
                    AbortButton.Visibility = Visibility.Hidden;
                    Mouse.OverrideCursor = null;
                    return;
                }
            }

            var printMode = PostalAllInput.IsChecked == true ? 3 :
                PostalWishInput.IsChecked == true ? 2 :
                PostalNoEmailInput.IsChecked == true ? 1 : 0;
            var emailMode = EmailAllInput.IsChecked == true ? 2 : EmailWishInput.IsChecked == true ? 1 : 0;

            double printNum = printMode == 3 ? PostalAllCount : printMode == 2 ? PostalWishCount : printMode == 1 ? PostalNoEmailCount : 0;
            double emailNum = emailMode == 2 ? EmailAllCount : emailMode == 1 ? EmailWishCount : 0;
            double totalNum = printNum + emailNum;

            var email = memberDocs
                .Where(d => d.Member.EmailAddresses.Count > 0 && (emailMode == 2 || (emailMode == 1 && d.Member.ContactViaEmail)))
                .ToDictionary(d => d.Member, m => {
                    var docs = m.Docs.Select(d => d.Doc).ToList();
                    foreach (var doc in docs) {
                        doc!.IsDoublePaged = false;
                        if (doc is BusinessDocument b) {
                            b.IncludeSender = false;
                            b.Location = location;
                        }
                    };
                    return docs;
                });
            var emailRecipients = email.Select(d => d.Key.MgNr).ToHashSet();
            try {
                foreach (var item1 in email.Select((e, i) => new { Index = i, e.Key, e.Value })) {
                    foreach (var item2 in item1.Value.Select((d, i) => new { Index = i, Doc = d })) {
                        await item2.Doc.Generate(CancelGeneration.Token, new Progress<double>(v => {
                            ProgressBar.Value = v * (item2.Index + 1) / item1.Value.Count / totalNum + 100.0 * item1.Index / totalNum;
                        }));
                    }
                }
            } catch (Exception exc) {
                MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
                UnlockInputs();
                GenerateButton.IsEnabled = true;
                GenerateButton.Visibility = Visibility.Visible;
                AbortButton.IsEnabled = false;
                AbortButton.Visibility = Visibility.Hidden;
                Mouse.OverrideCursor = null;
                return;
            }
            if (email.Count > 0) {
                EmailDocuments = email;
            }

            var printMemberDocs = memberDocs
                .Where(d =>
                    printMode == 3 ||
                    (printMode == 2 && d.Member.ContactViaPost) ||
                    (printMode == 1 && !emailRecipients.Contains(d.Member.MgNr)))
                .ToList();
            var printDocs = printMemberDocs
                .SelectMany(m => {
                    var docs = m.Docs.Select(d => d.Doc).ToList();
                    if (docs.Count == 0 || m.Docs[0].Type == DocType.Custom) {
                        docs.Insert(0, new Letterhead(m.Member));
                    }
                    docs.ForEach(doc => {
                        doc.IsDoublePaged = doublePaged;
                        if (doc is BusinessDocument b)
                            b.Location = location;
                    });
                    if (docs.Count > 0 && docs[0] is BusinessDocument b)
                        b.IncludeSender = true;
                    return docs;
                })
                .ToList();

            if (printDocs.Count > 0) {
                try {
                    var print = Document.Merge(printDocs);
                    print.IsDoublePaged = doublePaged;
                    await print.Generate(CancelGeneration.Token, new Progress<double>(v => {
                        ProgressBar.Value = 100.0 * emailNum / totalNum + v * printNum / totalNum;
                    }));
                    PrintDocument = print;
                    PrintMemberDocuments = printMemberDocs.ToDictionary(m => m.Member, m => m.Docs.Select(d => d.Doc).ToList());
                } catch (Exception exc) {
                    MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
                    UnlockInputs();
                    GenerateButton.IsEnabled = true;
                    GenerateButton.Visibility = Visibility.Visible;
                    AbortButton.IsEnabled = false;
                    AbortButton.Visibility = Visibility.Hidden;
                    Mouse.OverrideCursor = null;
                    return;
                }
            } else {
                PrintDocument = null;
                PrintMemberDocuments = null;
            }
            ProgressBar.Value = 100.0;

            UnlockInputs();
            GenerateButton.IsEnabled = true;
            GenerateButton.Visibility = Visibility.Visible;
            AbortButton.IsEnabled = false;
            AbortButton.Visibility = Visibility.Hidden;
            Mouse.OverrideCursor = null;
            PreviewButton.IsEnabled = true;
            PrintButton.IsEnabled = PrintDocument != null && !hasPreviewDocs;
            EmailButton.IsEnabled = EmailDocuments != null && !hasPreviewDocs && App.Config.Smtp != null;
        }

        private async void PreviewButton_Click(object sender, RoutedEventArgs evt) {
            var d = new OpenFolderDialog() {
                Title = "Ordner auswählen - Elwig",
            };
            if (d.ShowDialog() == true) {
                Mouse.OverrideCursor = Cursors.Wait;
                await Task.Run(() => {
                    PrintDocument?.SaveTo($"{d.FolderName}/Print.pdf");
                    if (EmailDocuments != null) {
                        foreach (var (m, docs) in EmailDocuments) {
                            var folder = $"{d.FolderName}/E-Mail/{m.AdministrativeName.Trim()}";
                            Directory.CreateDirectory(folder);
                            foreach (var item in docs.Select((d, i) => new { Index = i, Doc = d })) {
                                var doc = item.Doc;
                                var name = Utils.NormalizeFileName(doc.Title);
                                doc.SaveTo($"{folder}/{item.Index + 1:00}.{name}.pdf");
                            }

                        }
                    }
                });
                Mouse.OverrideCursor = null;
                Process.Start("explorer.exe", d.FolderName);
            }
        }

        private async void PrintButton_Click(object sender, RoutedEventArgs evt) {
            if (PrintDocument == null || PrintMemberDocuments == null) return;

            PrintButton.IsEnabled = false;
            GenerateButton.IsEnabled = false;
            LockInputs();

            var res = MessageBox.Show($"Sollen {PrintDocument.Pages} Blätter ({PrintDocument.TotalPages} Seiten) gedruckt werden?",
                "Rundschreiben drucken", MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.No);
            if (res == MessageBoxResult.Yes) {
                Mouse.OverrideCursor = Cursors.Wait;
                await Task.Run(async () => {
                    if (App.Config.Debug) {
                        PrintDocument.Show();
                    } else {
                        await PrintDocument.Print();
                        await Utils.AddSentMails(
                            PrintMemberDocuments.Select(d => (
                                "postal", d.Key.MgNr, d.Key.AdministrativeName,
                                new string[] { d.Value.Select(d => (d as BusinessDocument)?.Address).FirstOrDefault(a => a != null) ?? d.Key.FullAddress },
                                d.Value.Select(d => d.Title).FirstOrDefault("Briefkopf"),
                                d.Value.Select(d => d.Title).ToArray()
                            ))
                        );
                    }
                });
                Mouse.OverrideCursor = null;
            }

            PrintButton.IsEnabled = true;
            GenerateButton.IsEnabled = true;
            UnlockInputs();
        }

        private async void EmailButton_Click(object sender, RoutedEventArgs evt) {
            if (App.Config.Smtp == null || EmailDocuments == null) return;

            EmailButton.IsEnabled = false;
            GenerateButton.IsEnabled = false;
            LockInputs();

            SmtpClient? client = null;
            try {
                Mouse.OverrideCursor = Cursors.Wait;
                client = await Task.Run(Utils.GetSmtpClient);
                Mouse.OverrideCursor = null;

                var res = MessageBox.Show($"Sollen {EmailDocuments.Count:N0} E-Mails verschickt werden?",
                    "Rundschreiben verschicken", MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.No);
                if (res != MessageBoxResult.Yes) {
                    return;
                }

                Mouse.OverrideCursor = Cursors.Wait;
                var subject = EmailSubjectInput.Text;
                var text = EmailBodyInput.Text;
                await Task.Run(async () => {
                    await Utils.AddSentMailBody(subject, text, EmailDocuments.Count);
                    foreach (var (m, docs) in EmailDocuments) {
                        using var msg = new MimeMessage();
                        msg.From.Add(new MailboxAddress(App.Client.NameFull, App.Config.Smtp.Value.From));
                        msg.To.AddRange(m.EmailAddresses.OrderBy(a => a.Nr).Select(a => new MailboxAddress(m.AdministrativeName, a.Address)));
                        msg.Subject = subject;
                        var body = new Multipart("mixed") {
                            new TextPart("plain") { Text = text }
                        };
                        foreach (var doc in docs) {
                            var name = Utils.NormalizeFileName(doc.Title);
                            body.Add(doc.AsEmailAttachment($"{name}.pdf"));
                        }
                        msg.Body = body;
                        await client!.SendAsync(msg);
                        await Utils.AddSentMails([(
                            "email", m.MgNr, m.AdministrativeName,
                            m.EmailAddresses.OrderBy(a => a.Nr).Select(a => a.Address).ToArray(),
                            subject, docs.Select(d => d.Title).ToArray()
                        )]);
                    }
                });

                MessageBox.Show("Erfolgreich alle E-Mails verschickt!\n\nEs kann einige Minuten dauern, bis die E-Mails in den Posteingängen der Empfänger aufscheinen.", "Rundschreiben verschicken",
                    MessageBoxButton.OK, MessageBoxImage.Information);
            } catch (Exception exc) {
                MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
            } finally {
                if (client != null)
                    await client.DisconnectAsync(true);
                client?.Dispose();
                EmailButton.IsEnabled = true;
                GenerateButton.IsEnabled = true;
                UnlockInputs();
                Mouse.OverrideCursor = null;
            }
        }

        public void AddDeliveryConfirmation() {
            if (!GenerateButton.IsEnabled) return;
            AvaiableDocumentsList.SelectedIndex = 1;
            if (AvaiableDocumentsList.SelectedItem is not string s || SelectedDocs.Any(d => d.Type == DocType.DeliveryConfirmation))
                return;
            SelectedDocs.Add(new(DocType.DeliveryConfirmation, s, Year));
            SelectedDocumentsList.SelectedIndex = SelectedDocs.Count - 1;
            RecipientsDeliveryMembersInput.IsChecked = true;
        }

        public void AddCreditNote(int index) {
            if (!GenerateButton.IsEnabled) return;
            AvaiableDocumentsList.SelectedIndex = 2 + index;
            if (AvaiableDocumentsList.SelectedItem is not string s || SelectedDocs.Any(d => d.Type == DocType.CreditNote))
                return;
            var name = s.Split(" – ")[^1];
            using var ctx = new AppDbContext();
            var pv = ctx.PaymentVariants.Single(v => v.Year == Year && v.Name == name)!;
            SelectedDocs.Add(new(DocType.CreditNote, s, (pv.Year, pv.AvNr)));
            SelectedDocumentsList.SelectedIndex = SelectedDocs.Count - 1;
            RecipientsDeliveryMembersInput.IsChecked = true;
        }

        private void DocumentInput_TextChanged(object sender, TextChangedEventArgs evt) {
            ResetDocuments();
        }

        private void PostalLocation_TextChanged(object sender, TextChangedEventArgs evt) {
            ResetDocuments();
        }

        private void PostalInput_Changed(object sender, RoutedEventArgs evt) {
            UpdatePostalEmailRecipients();
        }

        private void OrderInput_Changed(object sender, RoutedEventArgs evt) {
            ResetDocuments();
        }

        private void DoublePagedInput_Changed(object sender, RoutedEventArgs evt) {
            ResetDocuments();
        }

        private void PostalSender_TextChanged(object sender, TextChangedEventArgs evt) {
            ResetDocuments();
        }
    }
}
