using Elwig.Documents;
using Elwig.Helpers;
using Elwig.Helpers.Billing;
using Elwig.Helpers.Export;
using Elwig.Models.Dtos;
using Elwig.Models.Entities;
using Elwig.ViewModels;
using Microsoft.EntityFrameworkCore;
using Microsoft.Win32;
using System;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;

namespace Elwig.Services {
    public static class PaymentVariantService {

        private static readonly JsonSerializerOptions JsonOpt = new() { WriteIndented = true };

        public static void ClearInputs(this PaymentVariantsViewModel vm) {
            vm.IsPaymentVariantSelected = false;
            vm.EditText = "Bearbeiten";
            vm.SaveIsEnabled = false;
            vm.DeleteIsEnabled = false;
            vm.CalculateIsEnabled = false;
            vm.CommitIsEnabled = false;
            vm.CommitVisibility = Visibility.Visible;
            vm.RevertIsEnabled = false;
            vm.RevertVisibility = Visibility.Hidden;
            vm.Arrow = "\xF0AF";
            vm.ExportIsEnabled = false;

            vm.IsReadOnly = true;
            vm.DataIsReadOnly = true;
            vm.BillingData = null;
            vm.Name = "";
            vm.Comment = "";
            vm.Date = null;
            vm.TransferDate = null;
            vm.WeightModifier = null;
            vm.ConsiderModifiers = false;
            vm.ConsiderPenalties = false;
            vm.ConsiderPenalty = false;
            vm.ConsiderAuto = false;
            vm.ConsiderCustom = false;
            vm.IsEnabled = false;
            vm.Data = "";

            vm.StatusModifierSum = "-";
            vm.StatusTotalSum = "-";
            vm.StatusVatSum = "-";
            vm.StatusDeductionSum = "-";
            vm.StatusPaymentSum = "-";
        }

        public static void FillInputs(this PaymentVariantsViewModel vm, PaymentVar v) {
            var locked = !v.TestVariant;
            vm.IsPaymentVariantSelected = true;
            vm.SaveIsEnabled = !locked;
            vm.DeleteIsEnabled = !locked;
            vm.CalculateIsEnabled = !locked;
            vm.CommitIsEnabled = !locked && !vm.SeasonLocked;
            vm.CommitVisibility = !locked ? Visibility.Visible : Visibility.Hidden;
            vm.RevertIsEnabled = locked && !vm.SeasonLocked;
            vm.RevertVisibility = locked ? Visibility.Visible : Visibility.Hidden;
            vm.Arrow = locked ? "\xF0B0" : "\xF0AF";
            vm.EditText = locked ? "Ansehen" : "Bearbeiten";
            vm.ExportIsEnabled = locked;

            vm.IsReadOnly = false;
            vm.DataIsReadOnly = locked;
            vm.Name = v.Name;
            vm.Comment = v.Comment ?? "";
            vm.Date = v.Date;
            vm.TransferDate = v.TransferDate;
            try {
                vm.BillingData = BillingData.FromJson(v.Data);
                vm.ConsiderModifiers = vm.BillingData.ConsiderDelieryModifiers;
                vm.ConsiderPenalties = vm.BillingData.ConsiderContractPenalties;
                vm.ConsiderPenalty = vm.BillingData.ConsiderTotalPenalty;
                vm.ConsiderAuto = vm.BillingData.ConsiderAutoBusinessShares;
                vm.ConsiderCustom = vm.BillingData.ConsiderCustomModifiers;
                if (vm.BillingData.NetWeightModifier != 0) {
                    vm.WeightModifier = Math.Round(vm.BillingData.NetWeightModifier * 100.0, 8);
                } else if (vm.BillingData.GrossWeightModifier != 0) {
                    vm.WeightModifier = Math.Round(vm.BillingData.GrossWeightModifier * 100.0, 8);
                } else {
                    vm.WeightModifier = null;
                }
                vm.Data = JsonSerializer.Serialize(vm.BillingData.Data, JsonOpt);
            } catch {
                vm.BillingData = null;
                vm.ConsiderModifiers = false;
                vm.ConsiderPenalties = false;
                vm.ConsiderPenalty = false;
                vm.ConsiderAuto = false;
                vm.ConsiderCustom = false;
                vm.WeightModifier = null;
                vm.Data = v.Data;
            }
            vm.IsEnabled = !locked;

            vm.StatusModifierSum = "...";
            vm.StatusTotalSum = "...";
            vm.StatusVatSum = "...";
            vm.StatusDeductionSum = "...";
            vm.StatusPaymentSum = "...";
            Utils.RunBackground("Variantendaten laden", async () => {
                await vm.UpdateSums(v);
            });
        }

        private static async Task UpdateSums(this PaymentVariantsViewModel vm, PaymentVar v) {
            if (App.MainDispatcher == null)
                return;
            var modText = "-";
            var totalText = "-";
            var vatText = "-";
            var deductionText = "-";
            var paymentText = "-";

            using var ctx = new AppDbContext();
            var sym = v.Season.Currency.Symbol ?? v.Season.Currency.Code;

            var modSum = await ctx.PaymentDeliveryParts
                .Where(p => p.Year == v.Year && p.AvNr == v.AvNr)
                .SumAsync(p => p.AmountValue - p.NetAmountValue);
            modText = $"{v.Season.DecFromDb(modSum):N2} {sym}";

            var totalSum = await ctx.MemberPayments
                .Where(p => p.Year == v.Year && p.AvNr == v.AvNr)
                .SumAsync(p => p.AmountValue);
            totalText = $"{v.Season.DecFromDb(totalSum):N2} {sym}";

            await App.MainDispatcher.BeginInvoke(() => {
                if (vm.SelectedPaymentVariant != v)
                    return;
                vm.StatusModifierSum = modText;
                vm.StatusTotalSum = totalText;
            });

            var credits = ctx.Credits.Where(c => c.Year == v.Year && c.AvNr == v.AvNr);
            if (!credits.Any()) {
                long lastTotalSum = 0;
                decimal vatSum = 0;
                var currentPayments = await ctx.MemberPayments
                        .Where(p => p.Year == v.Year && p.AvNr == v.AvNr)
                        .ToDictionaryAsync(p => p.MgNr);
                var lastV = await ctx.PaymentVariants
                    .Where(l => l.Year == v.Year && !l.TestVariant)
                    .OrderByDescending(l => l.TransferDateString ?? l.DateString)
                    .ThenByDescending(l => l.AvNr)
                    .FirstOrDefaultAsync();
                if (lastV != null) {
                    var lastPayments = await ctx.MemberPayments
                        .Where(p => p.Year == v.Year && p.AvNr == lastV.AvNr)
                        .ToDictionaryAsync(p => p.MgNr);
                    lastTotalSum = lastPayments.Sum(e => e.Value.AmountValue);
                    foreach (int mgnr in currentPayments.Keys) {
                        var c = currentPayments[mgnr];
                        var l = lastPayments[mgnr];
                        vatSum += (c.Amount - l.Amount) * (decimal)(c.Member.IsBuchführend ? v.Season.VatNormal : v.Season.VatFlatrate);
                    }
                } else {
                    vatSum = currentPayments.Sum(e => e.Value.Amount * (decimal)(e.Value.Member.IsBuchführend ? v.Season.VatNormal : v.Season.VatFlatrate));
                }
                vatText = $"~{vatSum:N2} {sym}";
                deductionText = $"- {sym}";
                paymentText = $"~{v.Season.DecFromDb(totalSum - lastTotalSum) + vatSum:N2} {sym}";
            } else {
                // all values in the credit table are stored with precision 2!
                totalText = $"{Utils.DecFromDb(await credits.SumAsync(c => c.NetAmountValue), 2):N2} {sym}";
                vatText = $"{Utils.DecFromDb(await credits.SumAsync(c => c.VatAmountValue), 2):N2} {sym}";
                deductionText = $"{-Utils.DecFromDb(await credits.SumAsync(c => c.ModifiersValue ?? 0), 2):N2} {sym}";
                paymentText = $"{Utils.DecFromDb(await credits.SumAsync(c => c.AmountValue), 2):N2} {sym}";
            }

            await App.MainDispatcher.BeginInvoke(() => {
                if (vm.SelectedPaymentVariant != v)
                    return;
                vm.StatusModifierSum = modText;
                vm.StatusTotalSum = totalText;
                vm.StatusVatSum = vatText;
                vm.StatusDeductionSum = deductionText;
                vm.StatusPaymentSum = paymentText;
            });
        }

        public static async Task GenerateSummary(PaymentVar v, ExportMode mode) {
            if (mode == ExportMode.SaveList) {
                var d = new SaveFileDialog() {
                    FileName = $"Variantendaten-{v.Name.Trim().Replace(' ', '-')}.ods",
                    DefaultExt = "ods",
                    Filter = "OpenDocument Format Spreadsheet (*.ods)|*.ods",
                    Title = $"Variantendaten {v.Name} speichern unter - Elwig"
                };
                if (d.ShowDialog() == false)
                    return;
                Mouse.OverrideCursor = Cursors.Wait;
                await Task.Run(async () => {
                    try {
                        using var ctx = new AppDbContext();
                        var data = await PaymentVariantSummaryData.ForPaymentVariant(v, ctx.PaymentVariantSummaryRows);
                        using var ods = new OdsFile(d.FileName);
                        await ods.AddTable(data);
                    } catch (Exception exc) {
                        MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
                    }
                });
                Mouse.OverrideCursor = null;
            } else {
                Mouse.OverrideCursor = Cursors.Wait;
                await Task.Run(async () => {
                    try {
                        using var ctx = new AppDbContext();
                        var data = await PaymentVariantSummaryData.ForPaymentVariant(v, ctx.PaymentVariantSummaryRows);
                        using var doc = new PaymentVariantSummary((await ctx.PaymentVariants.FindAsync(v.Year, v.AvNr))!, data);
                        await Utils.ExportDocument(doc, mode);
                    } catch (Exception exc) {
                        MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
                    }
                });
                Mouse.OverrideCursor = null;
            }
        }

        public static async Task GenerateEbics(int year, int avnr) {
            using var ctx = new AppDbContext();
            var v = (await ctx.PaymentVariants.FindAsync(year, avnr))!;

            var withoutIban = v.Credits.Count(c => c.Member.Iban == null);
            if (withoutIban > 0) {
                var r = MessageBox.Show($"Achtung: Für {withoutIban:N0} Mitglieder ist kein IBAN hinterlegt.\n\nDiese werden NICHT exportiert.",
                    "Mitglieder ohne IBAN", MessageBoxButton.OKCancel, MessageBoxImage.Warning, MessageBoxResult.Cancel);
                if (r != MessageBoxResult.OK) return;
            }
            var withNegAmount = v.Credits.Count(c => c.Amount <= 0);
            if (withNegAmount > 0) {
                var r = MessageBox.Show($"Achtung: Es gibt {withNegAmount:N0} Traubengutschriften mit negativem Betrag.\n\nDiese werden NICHT exportiert.",
                    "Traubengutschriften mit negativem Betrag", MessageBoxButton.OKCancel, MessageBoxImage.Warning, MessageBoxResult.OK);
                if (r != MessageBoxResult.OK) return;
            }

            var d = new SaveFileDialog() {
                FileName = $"{App.Client.NameToken}-Überweisungsdaten-{v.Year}-{v.Name.Trim().Replace(' ', '-')}.{Ebics.FileExtension}",
                DefaultExt = Ebics.FileExtension,
                Filter = "EBICS-Datei (*.xml)|*.xml",
                Title = $"Überweisungsdaten speichern unter - Elwig",
            };
            if (d.ShowDialog() == true) {
                Mouse.OverrideCursor = Cursors.Wait;
                await Task.Run(async () => {
                    try {
                        using var e = new Ebics(v, d.FileName, App.Client.ExportEbicsVersion, (Ebics.AddressMode)App.Client.ExportEbicsAddress);
                        await e.ExportAsync(Transaction.FromPaymentVariant(v));
                    } catch (Exception exc) {
                        MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
                    }
                });
                Mouse.OverrideCursor = null;
            }
        }

        public static async Task GenerateAccountingList(int year, int avnr, string name) {
            var d = new SaveFileDialog() {
                FileName = $"{App.Client.NameToken}-Buchungsliste-{year}-{name.Trim().Replace(' ', '-')}.ods",
                DefaultExt = "ods",
                Filter = "OpenDocument Format Spreadsheet (*.ods)|*.ods",
                Title = $"Buchungsliste speichern unter - Elwig"
            };
            if (d.ShowDialog() == true) {
                Mouse.OverrideCursor = Cursors.Wait;
                await Task.Run(async () => {
                    try {
                        using var ctx = new AppDbContext();
                        var tbl = await CreditNoteData.ForPaymentVariant(ctx, year, avnr);
                        using var ods = new OdsFile(d.FileName);
                        await ods.AddTable(tbl);
                    } catch (Exception exc) {
                        MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
                    }
                });
                Mouse.OverrideCursor = null;
            }
        }

        public static async Task<PaymentVar> CreatePaymentVariant(int year) {
            return await Task.Run(async () => {
                using var ctx = new AppDbContext();
                var v = new PaymentVar {
                    Year = year,
                    AvNr = await ctx.NextAvNr(year),
                    Name = "Neue Auszahlungsvariante",
                    TestVariant = true,
                    DateString = $"{DateTime.Today:yyyy-MM-dd}",
                    Data = "{\"mode\": \"elwig\", \"version\": 1, \"payment\": {}, \"curves\": []}",
                };
                ctx.Add(v);
                await ctx.SaveChangesAsync();
                return v;
            });
        }

        public static async Task<PaymentVar> Duplicate(this PaymentVar orig) {
            return await Task.Run(async () => {
                using var ctx = new AppDbContext();
                var n = new PaymentVar {
                    Year = orig.Year,
                    AvNr = await ctx.NextAvNr(orig.Year),
                    Name = $"{orig.Name} (Kopie)",
                    TestVariant = true,
                    DateString = $"{DateTime.Today:yyyy-MM-dd}",
                    Data = orig.Data,
                };
                ctx.Add(n);
                await ctx.SaveChangesAsync();
                return n;
            });
        }

        public static async Task<(int, int)> UpdatePaymentVariant(this PaymentVariantsViewModel vm, int? oldYear, int? oldAvNr) {
            var year = oldYear ?? Utils.CurrentYear;
            int avnr = 0;
            await Task.Run(async () => {
                var d = App.Config.Debug ? BillingData.FromJson(vm.Data ?? "{}") : vm.BillingData!;
                d.ConsiderDelieryModifiers = vm.ConsiderModifiers;
                d.ConsiderContractPenalties = vm.ConsiderPenalties;
                d.ConsiderTotalPenalty = vm.ConsiderPenalty;
                d.ConsiderAutoBusinessShares = vm.ConsiderAuto;
                d.ConsiderCustomModifiers = vm.ConsiderCustom;
                var modVal = vm.WeightModifier ?? 0;
                d.NetWeightModifier = modVal > 0 ? modVal / 100.0 : 0;
                d.GrossWeightModifier = modVal < 0 ? modVal / 100.0 : 0;

                using var ctx = new AppDbContext();
                var v = new PaymentVar {
                    Year = year,
                    AvNr = oldAvNr ?? await ctx.NextAvNr(year),
                    Name = vm.Name!,
                    DateString = $"{vm.Date!.Value:yyyy-MM-dd}",
                    TransferDate = vm.TransferDate,
                    TestVariant = vm.SelectedPaymentVariant?.TestVariant ?? true,
                    CalcTimeUnix = vm.SelectedPaymentVariant?.CalcTimeUnix,
                    Comment = string.IsNullOrEmpty(vm.Comment) ? null : vm.Comment,
                    Data = JsonSerializer.Serialize(d.Data),
                };
                avnr = v.AvNr;
                ctx.Update(v);
                await ctx.SaveChangesAsync();
            });
            vm.WeightModifierChanged = false;
            return (year, avnr);
        }

        public static async Task DeletePaymentVariant(int year, int avnr) {
            await Task.Run(async () => {
                using var ctx = new AppDbContext();
                var v = (await ctx.PaymentVariants.FindAsync(year, avnr))!;
                ctx.Remove(v);
                await ctx.SaveChangesAsync();
            });
        }

        public static async Task Calculate(int year, int avnr) {
            await Task.Run(async () => {
                var b = new BillingVariant(year, avnr);
                await b.Calculate();
            });
        }

        public static async Task Commit(int year, int avnr) {
            await Task.Run(async () => {
                var b = new BillingVariant(year, avnr);
                await b.Commit();
            });
        }

        public static async Task Revert(int year, int avnr) {
            await Task.Run(async () => {
                var b = new BillingVariant(year, avnr);
                await b.Revert();
            });
        }
    }
}
