using Microsoft.EntityFrameworkCore;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using Elwig.Helpers;
using Elwig.Models.Entities;
using System;
using System.Threading.Tasks;
using System.Collections.Generic;
using Xceed.Wpf.Toolkit.Primitives;

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

        public int MgNr => Member.MgNr;

        private readonly Member Member;
        private List<string> TextFilter = [];

        public AreaComAdminWindow(int mgnr) {
            InitializeComponent();
            using var ctx = new AppDbContext();
            Member = ctx.Members.Find(mgnr) ?? throw new ArgumentException("MgNr argument has invalid value");
            Title = $"Flächenbindungen - {Member.AdministrativeName} - Elwig";
            ExemptInputs = [
                MgNrInput, AreaCommitmentList, NewAreaCommitmentButton, 
                EditAreaCommitmentButton, DeleteAreaCommitmentButton, AreaCommitmentSaveButton,
                AreaCommitmentResetButton, AreaCommitmentCancelButton, SearchInput, ActiveAreaCommitmentInput
            ];
            RequiredInputs = [
                FbNrInput, YearFromInput, KgInput, RdInput,
                GstNrInput, AreaInput.TextBox, AreaComTypeInput, WineCultivationInput
            ];
        }

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

        private async Task RefreshAreaCommitmentList() {
            await RefreshAreaCommitmentListQuery();
        }

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

            if (filter.Count > 0 && areaComs.Count > 0) {
                var dict = areaComs.AsParallel()
                    .ToDictionary(d => d, d => d.SearchScore(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) {
                StatusAreaCommitments.Text = $"Flächenbindungen: {await areaComQuery.CountAsync()}";
                StatusArea.Text = $"Fläche: {await areaComQuery.Select(a => a.Area).SumAsync():N0} m²";
            } else {
                StatusAreaCommitments.Text = $"Flächenbindungen: {areaComs.Count}";
                StatusArea.Text = $"Fläche: {areaComs.Select(a => a.Area).Sum():N0} m²";
            }
            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();
            StatusContracts.Text = $"Vertragsarten: {groups.Count} (" + string.Join(", ", groups.Select(g => $"{g.Key}: {g.Item2:N0} m²")) + ")";
            groups = areaComs.GroupBy(a => a.AreaComType.DisplayName).Select(a => (a.Key, a.Sum(b => b.Area))).OrderByDescending(a => a.Item2).ToList();
            StatusContracts.ToolTip = $"Vertragsarten: {groups.Count}\n" + string.Join($"\n", groups.Select(g => $"{g.Key}: {g.Item2:N0} m²"));
        }

        private async Task<(List<string>, IQueryable<AreaCom>, List<string>)> GetFilters(AppDbContext ctx) {
            List<string> filterNames = [];
            IQueryable<AreaCom> areaComQuery = ctx.AreaCommitments.Where(a => a.MgNr == Member.MgNr).OrderBy(a => a.FbNr);
            if (ActiveAreaCommitmentInput.IsChecked == true) {
                areaComQuery = Utils.ActiveAreaCommitments(areaComQuery);
                filterNames.Add("aktiv");
            }

            var filterVar = new List<string>();
            var filterNotVar = new List<string>();
            var filterAttr = new List<string>();
            var filterNotAttr = new List<string>();

            var filter = TextFilter.ToList();
            if (filter.Count > 0) {
                var var = await ctx.WineVarieties.ToDictionaryAsync(v => v.SortId, v => v);
                var attr = await ctx.WineAttributes.ToDictionaryAsync(a => a.Name.ToLower().Split(" ")[0], a => a);

                for (int i = 0; i < filter.Count; i++) {
                    var e = filter[i];
                    if (e.Length == 2 && var.ContainsKey(e.ToUpper())) {
                        filterVar.Add(e.ToUpper());
                        filter.RemoveAt(i--);
                        filterNames.Add(var[e.ToUpper()].Name);
                    } else if (e.Length == 3 && e[0] == '!' && var.ContainsKey(e[1..].ToUpper())) {
                        filterNotVar.Add(e[1..].ToUpper());
                        filter.RemoveAt(i--);
                    } else if (attr.ContainsKey(e.ToLower())) {
                        var a = attr[e.ToLower()];
                        filterAttr.Add(a.AttrId);
                        filter.RemoveAt(i--);
                        filterNames.Add($"Attribut {a.Name}");
                    } else if (e[0] == '!' && attr.ContainsKey(e[1..].ToLower())) {
                        var a = attr[e[1..].ToLower()];
                        filterNotAttr.Add(a.AttrId);
                        filter.RemoveAt(i--);
                        filterNames.Add($"ohne Attribut {a.Name}");
                    }
                }

                if (filterVar.Count > 0) areaComQuery = areaComQuery.Where(a => filterVar.Contains(a.AreaComType.WineVar.SortId));
                if (filterNotVar.Count > 0) areaComQuery = areaComQuery.Where(a => !filterNotVar.Contains(a.AreaComType.WineVar.SortId));
                if (filterAttr.Count > 0) areaComQuery = areaComQuery.Where(a => a.AreaComType.WineAttr != null && a.AreaComType.WineAttr.AttrId != null && filterAttr.Contains(a.AreaComType.WineAttr.AttrId));
                if (filterNotAttr.Count > 0) areaComQuery = areaComQuery.Where(a => a.AreaComType.WineAttr == null || a.AreaComType.WineAttr.AttrId == null || !filterAttr.Contains(a.AreaComType.WineAttr.AttrId));
            }

           return (filterNames, areaComQuery, filter);
        }

        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();

            FbNrInput.Text = a.FbNr.ToString();
            MgNrInput.Text = a.MgNr.ToString();
            YearFromInput.Text = a.YearFrom.ToString();
            YearToInput.Text = a.YearTo.ToString();

            ControlUtils.SelectItemWithPk(KgInput, a.KgNr);
            if (a.RdNr != null) {
                ControlUtils.SelectItemWithPk(RdInput, a.KgNr, a.RdNr);
            } else {
                RdInput.SelectedIndex = 0;
            }
            GstNrInput.Text = a.GstNr;
            AreaInput.Text = a.Area.ToString();

            ControlUtils.SelectItemWithPk(AreaComTypeInput, a.VtrgId);
            if (a.CultId != null) {
                ControlUtils.SelectItemWithPk(WineCultivationInput, a.CultId);
            } else {
                WineCultivationInput.SelectedIndex = 0;
            }

            CommentInput.Text = a.Comment;

            FinishInputFilling();
        }

        private async void InitInputs() {
            ClearOriginalValues();
            ClearDefaultValues();

            using (var ctx = new AppDbContext()) {
                FbNrInput.Text = (await ctx.NextFbNr()).ToString();
                MgNrInput.Text = Member.MgNr.ToString();
                YearFromInput.Text = DateTime.Now.Year.ToString();
                WineCultivationInput.SelectedIndex = 0;
            }

            SetDefaultValue(FbNrInput);
            ValidateRequiredInputs();
        }

        protected override async Task OnRenewContext(AppDbContext ctx) {
            await base.OnRenewContext(ctx);
            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 RefreshAreaCommitmentList();
        }

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

        private void NewAreaCommitmentButton_Click(object? sender, RoutedEventArgs? evt) {
            IsCreating = true;
            AreaCommitmentList.IsEnabled = false;
            AreaCommitmentList.SelectedItem = null;
            HideAreaCommitmentNewEditDeleteButtons();
            ShowAreaCommitmentSaveResetCancelButtons();
            UnlockInputs();
            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;

            HideAreaCommitmentNewEditDeleteButtons();
            ShowAreaCommitmentSaveResetCancelButtons();
            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) {
            AreaCom a = (AreaCom)AreaCommitmentList.SelectedItem;
            if (a == null) return;

            var r = MessageBox.Show(
                $"Soll die Flächenbindung {a.GstNr} ({a.Area} m²) wirklich unwiderruflich gelöscht werden?",
                "Flächenbindung löschen", MessageBoxButton.OKCancel, MessageBoxImage.Warning, MessageBoxResult.Cancel);
            if (r == MessageBoxResult.OK) {
                using var ctx = new AppDbContext();
                ctx.Remove(a);
                await ctx.SaveChangesAsync();
                await RefreshAreaCommitmentList();
            }
        }

        private async Task<int> UpdateAreaCom(int? oldFbNr) {
            using var ctx = new AppDbContext();
            int newFbNr = int.Parse(FbNrInput.Text);

            var a = new AreaCom {
                FbNr = oldFbNr ?? newFbNr,
                MgNr = int.Parse(MgNrInput.Text),
                YearFrom = int.Parse(YearFromInput.Text),
                YearTo = (YearToInput.Text == "") ? null : int.Parse(YearToInput.Text),
                KgNr = ((AT_Kg)KgInput.SelectedItem).KgNr,
                RdNr = (RdInput.SelectedItem as WbRd)?.RdNr,
                GstNr = GstNrInput.Text.Trim(),
                Area = int.Parse(AreaInput.Text),
                VtrgId = (AreaComTypeInput.SelectedItem as AreaComType)!.VtrgId,
                CultId = (WineCultivationInput.SelectedItem as WineCult)?.CultId,
                Comment = (CommentInput.Text == "") ? null : CommentInput.Text.Trim(),
            };

            if (RdInput.SelectedItem is WbRd rd) {
                if (rd.RdNr == 0) {
                    rd.RdNr = await ctx.NextRdNr(a.KgNr);
                    a.RdNr = rd.RdNr;
                    ctx.Add(rd);
                }
            }

            if (oldFbNr != null) {
                ctx.Update(a);
            } else {
                ctx.Add(a);
            }

            await ctx.SaveChangesAsync();

            if (newFbNr != a.FbNr) {
                await ctx.Database.ExecuteSqlAsync($"UPDATE area_commitment SET fbnr = {newFbNr} WHERE fbnr = {oldFbNr}");
            }

            await App.HintContextChange();

            return newFbNr;
        }

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

        private async void AreaCommitmentSaveButton_Click(object? sender, RoutedEventArgs? evt) {
            int? fbnr = null;
            try {
                fbnr = await UpdateAreaCom((AreaCommitmentList.SelectedItem as AreaCom)?.FbNr);
            } 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);
            }
            IsEditing = false;
            IsCreating = false;
            AreaCommitmentList.IsEnabled = true;
            HideAreaCommitmentSaveResetCancelButtons();
            ShowAreaCommitmentNewEditDeleteButtons();
            LockInputs();
            UnlockSearchInputs();
            FinishInputFilling();
            await RefreshAreaCommitmentList();
            RefreshInputs();
            SearchInput.Text = "";
            ControlUtils.SelectItem(AreaCommitmentList, AreaCommitmentList.ItemsSource.Cast<AreaCom>().Where(a => a.FbNr == fbnr).FirstOrDefault());
        }

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

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

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

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

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

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

        private void ShowAreaCommitmentSaveResetCancelButtons() {
            AreaCommitmentSaveButton.IsEnabled = false;
            AreaCommitmentResetButton.IsEnabled = false;
            AreaCommitmentCancelButton.IsEnabled = true;
            AreaCommitmentSaveButton.Visibility = Visibility.Visible;
            AreaCommitmentResetButton.Visibility = Visibility.Visible;
            AreaCommitmentCancelButton.Visibility = Visibility.Visible;
        }

        private void HideAreaCommitmentSaveResetCancelButtons() {
            AreaCommitmentSaveButton.IsEnabled = false;
            AreaCommitmentResetButton.IsEnabled = false;
            AreaCommitmentCancelButton.IsEnabled = false;
            AreaCommitmentSaveButton.Visibility = Visibility.Hidden;
            AreaCommitmentResetButton.Visibility = Visibility.Hidden;
            AreaCommitmentCancelButton.Visibility = Visibility.Hidden;
        }

        private void ShowAreaCommitmentNewEditDeleteButtons() {
            EnableAreaCommitmentNewEditDeleteButtons();
            NewAreaCommitmentButton.Visibility = Visibility.Visible;
            EditAreaCommitmentButton.Visibility = Visibility.Visible;
            DeleteAreaCommitmentButton.Visibility = Visibility.Visible;
        }

        private void HideAreaCommitmentNewEditDeleteButtons() {
            DisableAreaCommitmentNewEditDeleteButtons();
            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) {
            RefreshInputs();
        }

        private void AttributesInput_SelectionChanged(object sender, ItemSelectionChangedEventArgs evt) {

        }

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

        private async void SearchInput_TextChanged(object sender, RoutedEventArgs evt) {
            TextFilter = SearchInput.Text.ToLower().Split(" ").ToList().FindAll(e => e.Length > 0);
            await RefreshAreaCommitmentListQuery(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);
        }
    }
}