using Elwig.Helpers;
using Elwig.Helpers.Billing;
using Elwig.Models.Dtos;
using Elwig.Models.Entities;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;

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

        public readonly int Year;
        public readonly bool SeasonLocked;

        public Dictionary<int, PaymentCustom>? CustomPayments;

        public PaymentAdjustmentWindow(int year) {
            InitializeComponent();
            Year = year;
            using (var ctx = new AppDbContext()) {
                SeasonLocked = ctx.Seasons.Find(Year + 1) != null;
            }
            Title = $"Auszahlung anpassen - Lese {Year} - Elwig";
            AutoAdjustBsButton.IsEnabled = !SeasonLocked;
            UnAdjustBsButton.IsEnabled = !SeasonLocked;
            SaveCustomButton.IsEnabled = !SeasonLocked;
            RemoveCustomButton.IsEnabled = !SeasonLocked;
            CustomAmountInput.IsEnabled = !SeasonLocked;
            CustomCommentInput.IsEnabled = !SeasonLocked;

            AllowanceKgInput.Text = $"{App.Client.AutoAdjustBs.AllowanceKg}";
            AllowanceBsInput.Text = $"{App.Client.AutoAdjustBs.AllowanceBs}";
            AllowanceKgPerBsInput.Text = $"{App.Client.AutoAdjustBs.AllowanceKgPerBs}";
            AllowancePercentInput.Text = $"{App.Client.AutoAdjustBs.AllowancePercent}";
            MinBsInput.Text = $"{App.Client.AutoAdjustBs.MinBs}";
        }

        protected override async Task OnRenewContext(AppDbContext ctx) {
            var members = await ctx.Members
                .Select(m => new {
                    m.MgNr,
                    m.Name,
                    m.GivenName,
                    m.BusinessShares,
                    m.IsActive,
                })
                .OrderBy(m => m.Name)
                .ThenBy(m => m.GivenName)
                .ThenBy(m => m.MgNr)
                .ToListAsync();
            var season = (await ctx.Seasons.FindAsync(Year))!;
            var contracts = await ctx.AreaCommitmentTypes.ToDictionaryAsync(t => t.VtrgId, t => t);

            var tbl1 = await OverUnderDeliveryData.ForSeason(ctx.OverUnderDeliveryRows, Year);
            var tbl2 = await AreaComUnderDeliveryData.ForSeason(ctx.AreaComUnderDeliveryRows, Year);
            var weight = tbl1.Rows.ToDictionary(r => r.MgNr, r => r.Weight);
            var areaComs = tbl2.Rows.ToDictionary(r => r.MgNr, r => r.VtrgIds.Zip(r.UnderDeliveries).ToDictionary(r => r.First, r => r.Second));
            CustomPayments = await ctx.CustomPayments.Where(p => p.Year == Year).ToDictionaryAsync(p => p.MgNr, p => p);

            var history = await ctx.MemberHistory
                .Where(h => h.DateString.CompareTo($"{Year}-01-01") >= 0 && h.DateString.CompareTo($"{Year}-12-31") <= 0 && h.Type == "auto" && h.BusinessShares > 0)
                .GroupBy(h => h.Member)
                .ToDictionaryAsync(h => h.Key.MgNr, h => h.Sum(g => g.BusinessShares));

            var list = members
                .Select(m => new {
                    m.MgNr, m.Name, m.GivenName,
                    m.IsActive,
                    BusinessShares = m.BusinessShares - history.GetValueOrDefault(m.MgNr, 0),
                    DeliveryObligation = (m.BusinessShares - history.GetValueOrDefault(m.MgNr, 0)) * season.MinKgPerBusinessShare,
                    DeliveryRight = (m.BusinessShares - history.GetValueOrDefault(m.MgNr, 0)) * season.MaxKgPerBusinessShare,
                    Adjust = history.TryGetValue(m.MgNr, out int v2) ? (int?)v2 : null,
                })
                .Select(m => new {
                    m.MgNr, m.Name, m.GivenName,
                    m.BusinessShares,
                    Weight = weight.GetValueOrDefault(m.MgNr, 0),
                    OverUnder = weight.TryGetValue(m.MgNr, out int v1) ?
                        (v1 < m.DeliveryObligation ? (int?)v1 - m.DeliveryObligation :
                         v1 > m.DeliveryRight ? (int?)v1 - m.DeliveryRight : null)
                        : (m.IsActive ? -m.DeliveryObligation : null),
                    m.Adjust,
                    AdjustAmount = m.Adjust * -season.BusinessShareValue,
                })
                .Select(m => new {
                    m.MgNr, m.Name, m.GivenName,
                    m.BusinessShares, m.Weight, m.OverUnder,
                    PenaltyBs = m.OverUnder != null && m.OverUnder < 0 ?
                        (season.PenaltyPerKg * m.OverUnder ?? 0) +
                        (-season.PenaltyAmount ?? 0) +
                        (season.PenaltyPerBsAmount * Math.Floor(m.OverUnder / season.MinKgPerBusinessShare ?? 0m) ?? 0) +
                        (m.Weight == 0 ? (-season.PenaltyNone ?? 0) + (-season.PenaltyPerBsNone * m.BusinessShares ?? 0) : 0)
                        : (decimal?)null,
                    PenaltyAc = areaComs.TryGetValue(m.MgNr, out var c) ? c.Select(r => {
                            var con = contracts[r.Key];
                            return (r.Value.Kg * con.PenaltyPerKg ?? 0) + (r.Value.Kg < 0 ? con.PenaltyAmount ?? 0 : 0) + (r.Value.Percent == -100 ? con.PenaltyNone ?? 0 : 0);
                        }).Sum() : (decimal?)null,
                    m.Adjust,
                    m.AdjustAmount,
                    Custom = CustomPayments!.GetValueOrDefault(m.MgNr, null),
                })
                .Select(m => new {
                    m.MgNr, m.Name, m.GivenName,
                    m.BusinessShares, m.Weight, m.OverUnder,
                    PenaltyBs = m.PenaltyBs == null || m.PenaltyBs == 0 ? (decimal?)null : Math.Round((decimal)m.PenaltyBs, 2),
                    PenaltyAc = m.PenaltyAc == null ? (decimal?)null : Math.Round((decimal)m.PenaltyAc, 2),
                    m.Adjust,
                    AdjustAmount = m.AdjustAmount == null ? (decimal?)null : Math.Round((decimal)m.AdjustAmount, 2),
                    CustomAmount = m.Custom?.Amount,
                    ModAbs = m.Custom?.ModAbs,
                    ModRel = m.Custom?.ModRel,
                })
                .Select(m => new {
                    m.MgNr, m.Name, m.GivenName,
                    m.BusinessShares, m.Weight, m.OverUnder,
                    m.PenaltyBs, m.PenaltyAc, m.Adjust, m.AdjustAmount, m.CustomAmount, m.ModAbs, m.ModRel,
                    Total = (m.PenaltyBs ?? 0) + (m.PenaltyAc ?? 0) + (m.AdjustAmount ?? 0) + (m.CustomAmount ?? 0),
                })
                .Select(m => new {
                    m.MgNr, m.Name, m.GivenName,
                    m.BusinessShares, m.Weight, m.OverUnder,
                    m.PenaltyBs, m.PenaltyAc, m.Adjust, m.AdjustAmount, m.CustomAmount, m.ModAbs, m.ModRel,
                    m.Total,
                    Background = m.Weight == 0 ? Brushes.Orange : m.Weight / 2 < -m.Total ? Brushes.Red : Brushes.White,
                    Foreground = m.Total == 0 ? Brushes.Gray : Brushes.Black,
                })
                .Where(m => m.OverUnder != null || m.Adjust != null || m.PenaltyBs != null || m.PenaltyAc != null || m.CustomAmount != null || m.ModAbs != null || m.ModRel != null)
                .OrderByDescending(m => m.OverUnder ?? 0)
                .ThenBy(m => m.Name)
                .ThenBy(m => m.GivenName)
                .ThenBy(m => m.MgNr)
                .ToList();

            MemberList.ItemsSource = list;

            var sym = season.Currency.Symbol ?? season.Currency.Code;
            PenaltyBusinessShares.Text = $"{list.Count(r => r.PenaltyBs != null && r.PenaltyBs != 0)} Mg. / {list.Sum(r => r.PenaltyBs):N2} {sym}";
            PenaltyAreaCommitments.Text = $"{list.Count(r => r.PenaltyAc != null && r.PenaltyAc != 0)} Mg. / {list.Sum(r => r.PenaltyAc):N2} {sym}";
            AutoBusinessShareAdjustment.Text = $"{list.Count(r => r.Adjust > 0)} Mg. / {list.Sum(r => r.Adjust)} GA / {list.Sum(r => r.AdjustAmount):N2} {sym}";
            CustomModifiers.Text = $"{list.Count(r => r.CustomAmount != null)} Mg. / {list.Sum(r => r.CustomAmount):N2} {sym}";
            TotalModifiers.Text = $"{list.Count(r => r.Total != 0)} Mg. / {list.Sum(r => r.Total):N2} {sym}";
            NonDeliveries.Text = $"{list.Count(r => r.Weight == 0):N0}";

            ControlUtils.RenewItemsSource(MemberInput, await ctx.Members
                .OrderBy(m => m.Name)
                .ThenBy(m => m.GivenName)
                .ThenBy(m => m.MgNr)
                .ToListAsync());
            CustomAmountInput.Unit = sym;
        }

        private async void AutoAdjustBsButton_Click(object sender, RoutedEventArgs evt) {
            Mouse.OverrideCursor = Cursors.Wait;
            try {
                int? kg = AllowanceKgInput.Text == "" ? null : int.Parse(AllowanceKgInput.Text);
                double? bs = AllowanceBsInput.Text == "" ? null : double.Parse(AllowanceBsInput.Text);
                int? kgPerBs = AllowanceKgPerBsInput.Text == "" ? null : int.Parse(AllowanceKgPerBsInput.Text);
                double? percent = AllowancePercentInput.Text == "" ? null : double.Parse(AllowancePercentInput.Text);
                int? minBs = MinBsInput.Text == "" ? null : int.Parse(MinBsInput.Text);

                App.Client.AutoAdjustBs.AllowanceKg = kg;
                App.Client.AutoAdjustBs.AllowanceBs = bs;
                App.Client.AutoAdjustBs.AllowanceKgPerBs = kgPerBs;
                App.Client.AutoAdjustBs.AllowancePercent = percent;
                App.Client.AutoAdjustBs.MinBs = minBs;

                await Task.Run(async () => {
                    await App.Client.UpdateValues();
                    var b = new Billing(Year);
                    await b.AutoAdjustBusinessShares(new DateOnly(Year, 11, 30), kg ?? default, bs ?? default, kgPerBs ?? default, percent / 100.0 ?? default, minBs ?? default);
                });
                App.HintContextChange();
            } catch (Exception exc) {
                var str = "Der Eintrag konnte nicht in der Datenbank aktualisiert werden!\n\n" + exc.Message;
                if (exc.InnerException != null) str += "\n\n" + exc.InnerException.Message;
                MessageBox.Show(str, "GA Nachzeichnen", MessageBoxButton.OK, MessageBoxImage.Error);
            }
            Mouse.OverrideCursor = null;
        }

        private async void UnAdjustBsButton_Click(object sender, RoutedEventArgs evt) {
            Mouse.OverrideCursor = Cursors.Wait;
            try {
                await Task.Run(async () => {
                    var b = new Billing(Year);
                    await b.UnAdjustBusinessShares();
                });
                App.HintContextChange();
            } catch (Exception exc) {
                var str = "Der Eintrag konnte nicht in der Datenbank aktualisiert werden!\n\n" + exc.Message;
                if (exc.InnerException != null) str += "\n\n" + exc.InnerException.Message;
                MessageBox.Show(str, "GA Nachzeichnen", MessageBoxButton.OK, MessageBoxImage.Error);
            }
            Mouse.OverrideCursor = null;
        }

        private void SeasonButton_Click(object sender, RoutedEventArgs evt) {
            App.FocusBaseDataSeason(Year);
        }

        private void KgInput_TextChanged(object sender, TextChangedEventArgs evt) {
            Validator.CheckInteger((TextBox)sender, false, 6);
        }

        private void BsInput_TextChanged(object sender, TextChangedEventArgs evt) {
            Validator.CheckInteger((TextBox)sender, false, 3);
        }

        private void PercentInput_TextChanged(object sender, TextChangedEventArgs evt) {
            Validator.CheckDecimal((TextBox)sender, false, 3, 2);
        }

        private void MemberList_SelectionChanged(object sender, SelectionChangedEventArgs evt) {
            if (MemberList.SelectedItem != null) {
                var i = MemberList.SelectedItem;
                var t = i.GetType();
                MgNrInput.Text = t.GetProperty("MgNr")?.GetValue(i)?.ToString();
            }
        }

        private void MgNrInput_TextChanged(object sender, TextChangedEventArgs evt) {
            var res = Validator.CheckMgNr((TextBox)sender, true);
            ControlUtils.SelectItemWithPk(MemberInput, res.IsValid ? int.Parse(MgNrInput.Text) : null);
        }

        private void MgNrInput_LostFocus(object sender, RoutedEventArgs evt) {
            var res = Validator.CheckMgNr((TextBox)sender, true);
            ControlUtils.SelectItemWithPk(MemberInput, res.IsValid ? int.Parse(MgNrInput.Text) : null);
        }

        private void MemberInput_SelectionChanged(object sender, SelectionChangedEventArgs evt) {
            if (MemberInput.SelectedItem is Member m) {
                MgNrInput.Text = m.MgNr.ToString();
                MemberList.SelectedItem = MemberList.ItemsSource.Cast<object>().FirstOrDefault(i => {
                    var t = i.GetType();
                    return (int?)t.GetProperty("MgNr")?.GetValue(i) == m.MgNr;
                });
                if (MemberList.SelectedItem != null)
                    MemberList.ScrollIntoView(MemberList.SelectedItem);

                if (CustomPayments?.TryGetValue(m.MgNr, out var p) == true) {
                    CustomModAbsInput.Text = $"{p.ModAbs:N2}";
                    CustomModRelInput.Text = $"{p.ModRel * 100:N2}";
                    CustomModCommentInput.Text = p.ModComment ?? "";
                    CustomAmountInput.Text = $"{p.Amount:N2}";
                    CustomCommentInput.Text = p.Comment ?? "";
                } else {
                    CustomModAbsInput.Text = "";
                    CustomModRelInput.Text = "";
                    CustomModCommentInput.Text = "";
                    CustomAmountInput.Text = "";
                    CustomCommentInput.Text = "";
                }
            } else {
                CustomModAbsInput.Text = "";
                CustomModRelInput.Text = "";
                CustomModCommentInput.Text = "";
                CustomAmountInput.Text = "";
                CustomCommentInput.Text = "";
            }
        }

        private void MemberReferenceButton_Click(object sender, RoutedEventArgs evt) {
            if (MemberInput.SelectedItem is not Member m) return;
            App.FocusMember(m.MgNr);
        }

        private void CustomModAbsInput_TextChanged(object sender, TextChangedEventArgs evt) {
            Validator.CheckDecimal((TextBox)sender, false, 4, 2, true);
        }

        private void CustomModRelInput_TextChanged(object sender, TextChangedEventArgs evt) {
            Validator.CheckDecimal((TextBox)sender, false, 3, 2, true);
        }

        private void CustomAmountInput_TextChanged(object sender, TextChangedEventArgs evt) {
            Validator.CheckDecimal((TextBox)sender, false, 4, 2, true);
        }

        private async void CustomButton_Click(object sender, RoutedEventArgs evt) {
            if (MemberInput.SelectedItem is not Member m) return;
            Mouse.OverrideCursor = Cursors.Wait;
            try {
                var modAbs = decimal.TryParse(CustomModAbsInput.Text, out var n1) ? (decimal?)n1 : null;
                var modRel = decimal.TryParse(CustomModRelInput.Text, out var n2) ? (decimal?)n2 / 100 : null;
                var amount = decimal.TryParse(CustomAmountInput.Text, out var n3) ? (decimal?)n3 : null;
                var modText = CustomModCommentInput.Text.Trim();
                var text = CustomCommentInput.Text.Trim();
                await Task.Run(async () => {
                    using var ctx = new AppDbContext();
                    if (CustomPayments?.TryGetValue(m.MgNr, out var p) == true) {
                        ctx.Remove(p);
                    }
                    if (sender == SaveCustomButton) {
                        ctx.Add(new PaymentCustom {
                            MgNr = m.MgNr,
                            Year = Year,
                            ModAbs = modAbs,
                            ModRel = modRel,
                            ModComment = modText == "" ? null : modText,
                            Amount = amount,
                            Comment = text == "" ? null : text,
                        });
                    }
                    await ctx.SaveChangesAsync();
                });
                App.HintContextChange();
            } catch (Exception exc) {
                var str = "Der Eintrag konnte nicht in der Datenbank aktualisiert werden!\n\n" + exc.Message;
                if (exc.InnerException != null) str += "\n\n" + exc.InnerException.Message;
                MessageBox.Show(str, "Benutzerdefinierten Zu-/Abschlag speichern", MessageBoxButton.OK, MessageBoxImage.Error);
            }
            Mouse.OverrideCursor = null;
        }
    }
}
