using Elwig.Dialogs;
using Elwig.Helpers;
using Elwig.Models.Entities;
using Elwig.Services;
using Elwig.ViewModels;
using Microsoft.EntityFrameworkCore;
using System;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace Elwig.Windows {
    public partial class AreaComAdminWindow : AdministrationWindow {

        public AreaComAdminViewModel ViewModel => (AreaComAdminViewModel)DataContext;

        private readonly RoutedCommand CtrlF = new("CtrlF", typeof(AreaComAdminWindow), [new KeyGesture(Key.F, ModifierKeys.Control)]);

        public AreaComAdminWindow(int mgnr) {
            InitializeComponent();
            CommandBindings.Add(new CommandBinding(CtrlF, FocusSearchInput));
            using var ctx = new AppDbContext();
            ViewModel.FilterMember = ctx.Members.Find(mgnr) ?? throw new ArgumentException("MgNr argument has invalid value");
            ViewModel.Title = $"Flächenbindungen - {ViewModel.FilterMember.AdministrativeName} - Elwig";
            ExemptInputs = [
                MgNrInput, AreaCommitmentList, NewAreaCommitmentButton, 
                EditAreaCommitmentButton, DeleteAreaCommitmentButton, SaveButton,
                ResetButton, CancelButton, SearchInput, ActiveAreaCommitmentInput
            ];
            RequiredInputs = [
                FbNrInput, YearFromInput, KgInput, RdInput,
                GstNrInput, AreaInput, AreaComTypeInput, WineCultivationInput
            ];

            ControlUtils.InitializeDelayTimer(SearchInput, SearchInput_TextChanged);
            SearchInput.TextChanged -= SearchInput_TextChanged;
            ActiveAreaCommitmentInput.Content = ((string)ActiveAreaCommitmentInput.Content).Replace("2020", $"{Utils.CurrentLastSeason}");
        }

        private void Window_Loaded(object sender, RoutedEventArgs e) {
            ActiveAreaCommitmentInput.IsChecked = true;
            LockInputs();
        }

        private void FocusSearchInput(object sender, RoutedEventArgs evt) {
            if (!IsEditing && !IsCreating) {
                SearchInput.Focus();
                SearchInput.SelectAll();
            }
        }

        private async Task RefreshList(bool updateSort = false) {
            using var ctx = new AppDbContext();
            var (_, areaComQuery, filter) = await ViewModel.GetFilters(ctx);
            var areaComs = await areaComQuery
                .Include(a => a.Kg.AtKg)
                .Include(a => a.Rd!.Kg.AtKg)
                .Include(a => a.WineCult)
                .Include(a => a.AreaComType.WineAttr)
                .Include(a => a.AreaComType.WineVar)
                .ToListAsync();

            if (filter.Count > 0 && areaComs.Count > 0) {
                var dict = areaComs.AsParallel()
                    .ToDictionary(d => d, d => d.SearchScore(ViewModel.TextFilter))
                    .OrderByDescending(c => c.Value);
                var threshold = dict.Select(a => a.Value).Max() * 3 / 4;
                areaComs = dict
                    .Where(a => a.Value > threshold)
                    .Select(a => a.Key)
                    .ToList();
            }

            ControlUtils.RenewItemsSource(AreaCommitmentList, areaComs,
                AreaCommitmentList_SelectionChanged, filter.Count > 0 ? ControlUtils.RenewSourceDefault.IfOnly : ControlUtils.RenewSourceDefault.None, !updateSort);
            RefreshInputs();

            if (filter.Count == 0) {
                ViewModel.StatusAreaCommitments = $"{await areaComQuery.CountAsync():N0}";
                var s = await ctx.Seasons.FindAsync(await ctx.Seasons.MaxAsync(s => s.Year));
                var (text, gridData) = await AreaComService.GenerateToolTipData(areaComQuery, s?.MaxKgPerHa ?? 10_000);
                ViewModel.StatusArea = text;
                ViewModel.StatusAreaToolTip = AreaComService.GenerateToolTip(gridData);
            } else {
                ViewModel.StatusAreaCommitments = $"{areaComs.Count:N0}";
                ViewModel.StatusArea = $"{areaComs.Select(a => a.Area).Sum():N0} m²";
                ViewModel.StatusAreaToolTip = null;
            }
            var groups = areaComs.GroupBy(a => $"{a.AreaComType.SortId}{a.AreaComType.AttrId}").Select(a => (a.Key, a.Sum(b => b.Area))).OrderByDescending(a => a.Item2).ToList();
            ViewModel.StatusContracts = $"{groups.Count:N0}";
            if (groups.Count > 0)
                ViewModel.StatusContracts += $" ({string.Join(", ", groups.Select(g => g.Key))})";
        }

        private void RefreshInputs(bool validate = false) {
            ClearInputStates();
            if (AreaCommitmentList.SelectedItem is AreaCom a) {
                EditAreaCommitmentButton.IsEnabled = true;
                DeleteAreaCommitmentButton.IsEnabled = true;
                FillInputs(a);
            } else {
                EditAreaCommitmentButton.IsEnabled = false;
                DeleteAreaCommitmentButton.IsEnabled = false;
                ClearOriginalValues();
                ClearDefaultValues();
                ClearInputs(validate);
                ClearInputStates();
            }
            GC.Collect();
        }

        private void FillInputs(AreaCom a) {
            ClearOriginalValues();
            ClearDefaultValues();
            ViewModel.FillInputs(a);
            FinishInputFilling();
        }

        private async Task InitInputs() {
            ClearOriginalValues();
            ClearDefaultValues();
            await ViewModel.InitInputs();
            SetDefaultValue(FbNrInput);
            ValidateRequiredInputs();
        }

        protected override async Task OnRenewContext(AppDbContext ctx) {
            await base.OnRenewContext(ctx);

            if (await ctx.Members.FindAsync(ViewModel.FilterMember.MgNr) is not Member m) {
                Close();
                return;
            }
            ViewModel.FilterMember = m;
            ViewModel.Title = $"Flächenbindungen - {ViewModel.FilterMember.AdministrativeName} - Elwig";

            ControlUtils.RenewItemsSource(KgInput, await ctx.WbKgs
                .Include(k => k.AtKg.WbKg!.Rds)
                .Select(k => k.AtKg)
                .OrderBy(k => k.Name)
                .ToListAsync());
            ControlUtils.RenewItemsSource(AreaComTypeInput, await ctx.AreaCommitmentTypes
                .Include(c => c.WineVar)
                .Include(c => c.WineAttr)
                .OrderBy(v => v.VtrgId)
                .ToListAsync());
            var cultList = await ctx.WineCultivations
                .OrderBy(c => c.Name)
                .Cast<object>().ToListAsync();
            cultList.Insert(0, new NullItem());
            ControlUtils.RenewItemsSource(WineCultivationInput, cultList, null, ControlUtils.RenewSourceDefault.First);
            await RefreshList();
        }

        protected override void ShortcutNew() {
            if (!NewAreaCommitmentButton.IsEnabled || NewAreaCommitmentButton.Visibility != Visibility.Visible)
                return;
            NewAreaCommitmentButton_Click(null, null);
        }

        private async void NewAreaCommitmentButton_Click(object? sender, RoutedEventArgs? evt) {
            IsCreating = true;
            AreaCommitmentList.IsEnabled = false;
            AreaCommitmentList.SelectedItem = null;
            HideNewEditDeleteButtons();
            ShowSaveResetCancelButtons();
            UnlockInputs();
            await InitInputs();
            LockSearchInputs();
        }


        protected override void ShortcutEdit() {
            if (!EditAreaCommitmentButton.IsEnabled || EditAreaCommitmentButton.Visibility != Visibility.Visible)
                return;
            EditAreaCommitmentButton_Click(null, null);
        }

        private void EditAreaCommitmentButton_Click(object? sender, RoutedEventArgs? evt) {
            if (AreaCommitmentList.SelectedItem == null)
                return;

            IsEditing = true;
            AreaCommitmentList.IsEnabled = false;

            HideNewEditDeleteButtons();
            ShowSaveResetCancelButtons();
            UnlockInputs();
            LockSearchInputs();
        }

        protected override void ShortcutDelete() {
            if (!DeleteAreaCommitmentButton.IsEnabled || DeleteAreaCommitmentButton.Visibility != Visibility.Visible)
                return;
            DeleteAreaCommitmentButton_Click(null, null);
        }

        private async void DeleteAreaCommitmentButton_Click(object? sender, RoutedEventArgs? evt) {
            if (AreaCommitmentList.SelectedItem is not AreaCom a)
                return;

            var d = new AreaComModifyDialog(a.YearFrom, a.YearTo, a.Area, true);
            if (d.ShowDialog() != true)
                return;

            Mouse.OverrideCursor = Cursors.Wait;
            try {
                if (d.YearTo is int yearTo) {
                    ViewModel.YearTo = yearTo;
                    await ViewModel.UpdateAreaCommitment((AreaCommitmentList.SelectedItem as AreaCom)?.FbNr);
                } else {
                    await AreaComService.DeleteAreaCom(a.FbNr);
                }
                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, "Flächenbindung löschen", MessageBoxButton.OK, MessageBoxImage.Error);
            }
            Mouse.OverrideCursor = null;
        }

        protected override void ShortcutSave() {
            if (!SaveButton.IsEnabled || SaveButton.Visibility != Visibility.Visible)
                return;
            SaveButton_Click(null, null);
        }

        private async void SaveButton_Click(object? sender, RoutedEventArgs? evt) {
            SaveButton.IsEnabled = false;

            int? yearTo = null;
            if (InputHasChanged(AreaInput) || InputHasChanged(AreaComTypeInput)) {
                var a = (AreaCommitmentList.SelectedItem as AreaCom)!;
                var d = new AreaComModifyDialog(a.YearFrom, a.YearTo, a.Area, false);
                if (d.ShowDialog() != true)
                    return;
                yearTo = d.YearTo;
            }

            int fbnr;
            Mouse.OverrideCursor = Cursors.Wait;
            try {
                AreaCom? temp = null;
                if (yearTo != null && (!ViewModel.YearTo.HasValue || yearTo < ViewModel.YearTo)) {
                    temp = new AreaCom {
                        FbNr = ViewModel.FbNr!.Value,
                        MgNr = ViewModel.MgNr!.Value,
                        YearFrom = ViewModel.YearFrom,
                        YearTo = ViewModel.YearTo,
                        VtrgId = ViewModel.AreaComType!.VtrgId,
                        CultId = ViewModel.WineCult?.CultId,
                        Comment = ViewModel.Comment,
                        KgNr = ViewModel.Kg!.KgNr,
                        RdNr = ViewModel.Rd?.RdNr,
                        GstNr = ViewModel.GstNr?.Trim() ?? "-",
                        Area = ViewModel.Area!.Value,
                    };
                    RefreshInputs();
                    ViewModel.YearTo = yearTo;
                }
                fbnr = await ViewModel.UpdateAreaCommitment((AreaCommitmentList.SelectedItem as AreaCom)?.FbNr);
                if (temp is AreaCom t) {
                    await ViewModel.InitInputs();
                    t.FbNr = ViewModel.FbNr!.Value;
                    t.YearFrom = yearTo + 1;
                    ViewModel.FillInputs(t);
                    fbnr = await ViewModel.UpdateAreaCommitment(null);
                }
                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, "Flächenbindung aktualisieren", MessageBoxButton.OK, MessageBoxImage.Error);
                SaveButton.IsEnabled = true;
                Mouse.OverrideCursor = null;
                return;
            }

            IsEditing = false;
            IsCreating = false;
            AreaCommitmentList.IsEnabled = true;
            HideSaveResetCancelButtons();
            ShowNewEditDeleteButtons();
            LockInputs();
            UnlockSearchInputs();
            FinishInputFilling();
            await RefreshList();
            RefreshInputs();
            Mouse.OverrideCursor = null;
            ViewModel.SearchQuery = "";
            ControlUtils.SelectItem(AreaCommitmentList, AreaCommitmentList.ItemsSource.Cast<AreaCom>().Where(a => a.FbNr == fbnr).FirstOrDefault());
        }

        protected override void ShortcutReset() {
            if (!ResetButton.IsEnabled || ResetButton.Visibility != Visibility.Visible)
                return;
            ResetButton_Click(null, null);
        }

        private async void ResetButton_Click(object? sender, RoutedEventArgs? evt) {
            if (IsEditing) {
                RefreshInputs();
            } else if (IsCreating) {
                await InitInputs();
            }
            UpdateButtons();
        }

        private void CancelButton_Click(object sender, RoutedEventArgs evt) {
            IsEditing = false;
            IsCreating = false;
            AreaCommitmentList.IsEnabled = true;
            HideSaveResetCancelButtons();
            ShowNewEditDeleteButtons();
            RefreshInputs();
            ClearInputStates();
            LockInputs();
            UnlockSearchInputs();
        }

        override protected void UpdateButtons() {
            if (!IsEditing && !IsCreating) return;
            bool ch = HasChanged, v = IsValid;
            ResetButton.IsEnabled = (ch);
            SaveButton.IsEnabled = (ch && v);
        }

        private void DisableNewEditDeleteButtons() {
            NewAreaCommitmentButton.IsEnabled = false;
            EditAreaCommitmentButton.IsEnabled = false;
            DeleteAreaCommitmentButton.IsEnabled = false;
        }

        private void EnableNewEditDeleteButtons() {
            NewAreaCommitmentButton.IsEnabled = true;
            EditAreaCommitmentButton.IsEnabled = AreaCommitmentList.SelectedItem != null;
            DeleteAreaCommitmentButton.IsEnabled = AreaCommitmentList.SelectedItem != null;
        }

        private void ShowSaveResetCancelButtons() {
            SaveButton.IsEnabled = false;
            ResetButton.IsEnabled = false;
            CancelButton.IsEnabled = true;
            SaveButton.Visibility = Visibility.Visible;
            ResetButton.Visibility = Visibility.Visible;
            CancelButton.Visibility = Visibility.Visible;
        }

        private void HideSaveResetCancelButtons() {
            SaveButton.IsEnabled = false;
            ResetButton.IsEnabled = false;
            CancelButton.IsEnabled = false;
            SaveButton.Visibility = Visibility.Hidden;
            ResetButton.Visibility = Visibility.Hidden;
            CancelButton.Visibility = Visibility.Hidden;
        }

        private void ShowNewEditDeleteButtons() {
            EnableNewEditDeleteButtons();
            NewAreaCommitmentButton.Visibility = Visibility.Visible;
            EditAreaCommitmentButton.Visibility = Visibility.Visible;
            DeleteAreaCommitmentButton.Visibility = Visibility.Visible;
        }

        private void HideNewEditDeleteButtons() {
            DisableNewEditDeleteButtons();
            NewAreaCommitmentButton.Visibility = Visibility.Hidden;
            EditAreaCommitmentButton.Visibility = Visibility.Hidden;
            DeleteAreaCommitmentButton.Visibility = Visibility.Hidden;
        }

        private void LockSearchInputs() {
            SearchInput.IsEnabled = false;
            ActiveAreaCommitmentInput.IsEnabled = false;
        }

        private void UnlockSearchInputs() {
            SearchInput.IsEnabled = true;
            ActiveAreaCommitmentInput.IsEnabled = true;
        }

        private void AreaCommitmentList_SelectionChanged(object sender, SelectionChangedEventArgs evt) {
            if (AreaCommitmentList.SelectedItem != null)
                AreaCommitmentList.ScrollIntoView(AreaCommitmentList.SelectedItem);
            RefreshInputs();
        }

        private void AttributesInput_SelectionChanged(object sender, SelectionChangedEventArgs evt) {

        }

        private async void ActiveAreaCommitmentInput_Changed(object sender, RoutedEventArgs evt) {
            await RefreshList();
        }

        private async void SearchInput_TextChanged(object sender, RoutedEventArgs evt) {
            var binding = ((TextBox)sender).GetBindingExpression(TextBox.TextProperty);
            binding?.UpdateSource();
            await RefreshList(true);
        }

        private void KgInput_SelectionChanged(object sender, SelectionChangedEventArgs evt) {
            if (KgInput.SelectedItem is AT_Kg kg) {
                var rdList = kg.WbKg!.Rds.OrderBy(r => r.Name).Cast<object>().ToList();
                rdList.Insert(0, new NullItem());
                ControlUtils.RenewItemsSource(RdInput, rdList, null, ControlUtils.RenewSourceDefault.First);
            } else {
                var rdList = new object[] { new NullItem() };
                ControlUtils.RenewItemsSource(RdInput, rdList, null, ControlUtils.RenewSourceDefault.First);
            }
            ComboBox_SelectionChanged(sender, evt);
        }

        private void KgDetailsButton_Click(object sender, RoutedEventArgs evt) {
            if (KgInput.SelectedItem is AT_Kg kg) {
                App.FocusOriginHierarchyKg(kg.KgNr);
            } else {
                App.FocusOriginHierarchy();
            }
        }

        private void RdInput_SelectionChanged(object sender, SelectionChangedEventArgs evt) {
            RdAddButton.IsEnabled = RdInput.SelectedIndex == -1;
        }

        private void RdAddButton_Click(object sender, RoutedEventArgs evt) {
            if (KgInput.SelectedItem is not AT_Kg kg) return;
            string name = RdInput.Text.Trim();
            if (name.Length == 0) return;
            var s = RdInput.ItemsSource.Cast<object?>();
            RdInput.ItemsSource = s.Append(new WbRd {
                KgNr = kg.KgNr,
                RdNr = 0,
                Name = name,
            });
            RdInput.SelectedIndex = s.Count();
        }

        protected void InputTextChanged(TextBox input, Func<TextBox, bool, AreaCom?, ValidationResult> checker) {
            InputTextChanged(input, checker(input, SenderIsRequired(input), (AreaCom)AreaCommitmentList.SelectedItem));
        }

        protected void InputLostFocus(TextBox input, Func<TextBox, bool, AreaCom?, ValidationResult> checker, string? msg = null) {
            InputLostFocus(input, checker(input, SenderIsRequired(input), (AreaCom)AreaCommitmentList.SelectedItem), msg);
        }

        private void FbNrInput_TextChanged(object sender, RoutedEventArgs evt) {
            InputTextChanged((TextBox)sender, Validator.CheckFbNr);
        }

        private void FbNrInput_LostFocus(object sender, RoutedEventArgs evt) {
            InputLostFocus((TextBox)sender, Validator.CheckFbNr);
        }
    }
}
