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

namespace Elwig.Windows {
    public abstract class AdministrationWindow : ContextWindow {

        protected Control[] ExemptInputs { get; set; }
        protected Control[] RequiredInputs { get; set; }

        private bool _isEditing;
        private bool _isCreating;
        public bool IsEditing {
            get { return _isEditing; }
            set {
                _isEditing = value;
                LockContext = IsEditing || IsCreating;
            }
        }
        public bool IsCreating {
            get { return _isCreating; }
            set {
                _isCreating = value;
                LockContext = IsEditing || IsCreating;
            }
        }
        protected bool DoShowWarningWindows = true;
        protected bool IsClosing { get; private set; }

        private TextBox[] TextBoxInputs;
        private TextBox[] PlzInputs;
        private ComboBox[] ComboBoxInputs;
        private ComboBox[] PlzOrtInputs;
        private ListBox[] ListBoxInputs;
        private CheckBox[] CheckBoxInputs;
        private RadioButton[] RadioButtonInputs;
        private readonly Dictionary<Control, bool> Valid;
        private readonly Dictionary<Control, int?> OriginalValues;
        private readonly Dictionary<Control, int?> DefaultValues;

        private readonly RoutedCommand AltInsert = new("AltInsert", typeof(AdministrationWindow), [new KeyGesture(Key.Insert, ModifierKeys.Alt)]);
        private readonly RoutedCommand AltDelete = new("AltDelete", typeof(AdministrationWindow), [new KeyGesture(Key.Delete, ModifierKeys.Alt)]);
        private readonly RoutedCommand CtrlZ = new("CtrlZ", typeof(AdministrationWindow), [new KeyGesture(Key.Z, ModifierKeys.Control)]);
        private readonly RoutedCommand CtrlS = new("CtrlS", typeof(AdministrationWindow), [new KeyGesture(Key.S, ModifierKeys.Control)]);
        private readonly RoutedCommand CtrlB = new("CtrlB", typeof(AdministrationWindow), [new KeyGesture(Key.B, ModifierKeys.Control)]);

        public AdministrationWindow() : base() {
            CommandBindings.Add(new CommandBinding(AltInsert, ShortcutNew));
            CommandBindings.Add(new CommandBinding(AltDelete, ShortcutDelete));
            CommandBindings.Add(new CommandBinding(CtrlZ, ShortcutReset));
            CommandBindings.Add(new CommandBinding(CtrlS, ShortcutSave));
            CommandBindings.Add(new CommandBinding(CtrlB, ShortcutEdit));
            IsEditing = false;
            IsCreating = false;
            ExemptInputs = [];
            RequiredInputs = [];
            TextBoxInputs = [];
            PlzInputs = [];
            ComboBoxInputs = [];
            ListBoxInputs = [];
            PlzOrtInputs = [];
            CheckBoxInputs = [];
            RadioButtonInputs = [];
            Valid = [];
            OriginalValues = [];
            DefaultValues = [];
            Closing += OnClosing;
            Loaded -= base.OnLoaded;
            Loaded += OnLoaded;
            Loaded += base.OnLoaded;
        }

        abstract protected void ShortcutNew();
        abstract protected void ShortcutDelete();
        abstract protected void ShortcutReset();
        abstract protected void ShortcutSave();
        abstract protected void ShortcutEdit();

        private void ShortcutNew(object sender, EventArgs evt) { ShortcutNew(); }
        private void ShortcutDelete(object sender, EventArgs evt) { ShortcutDelete(); }
        private void ShortcutReset(object sender, EventArgs evt) { ShortcutReset(); }
        private void ShortcutSave(object sender, EventArgs evt) { ShortcutSave(); }
        private void ShortcutEdit(object sender, EventArgs evt) { ShortcutEdit(); }

        private new void OnLoaded(object sender, RoutedEventArgs evt) {
            TextBoxInputs = ControlUtils.FindAllChildren<TextBox>(this, ExemptInputs).ToArray();
            ComboBoxInputs = ControlUtils.FindAllChildren<ComboBox>(this, ExemptInputs).ToArray();
            CheckBoxInputs = ControlUtils.FindAllChildren<CheckBox>(this, ExemptInputs).ToArray();
            ListBoxInputs = ControlUtils.FindAllChildren<ListBox>(this, ExemptInputs).ToArray();
            RadioButtonInputs = ControlUtils.FindAllChildren<RadioButton>(this, ExemptInputs).ToArray();
            PlzInputs = ControlUtils.FindAllChildren<TextBox>(this).Where(tb => "PLZ".Equals(tb.Tag)).ToArray();
            PlzOrtInputs = PlzInputs.Select(tb => ControlUtils.FindNextSibling<ComboBox>(tb) ?? throw new MissingMemberException()).ToArray();
            foreach (var tb in TextBoxInputs)
                Valid[tb] = true;
            foreach (var cb in ComboBoxInputs)
                cb.SelectionChanged += ComboBox_SelectionChanged;
            foreach (var lb in ListBoxInputs)
                lb.SelectionChanged += ComboBox_SelectionChanged;
        }

        private void OnClosing(object? sender, CancelEventArgs evt) {
            if ((IsCreating || IsEditing) && HasChanged) {
                var r = MessageBox.Show("Soll das Fenster wirklich geschlossen werden?", "Schließen bestätigen",
                    MessageBoxButton.YesNo, MessageBoxImage.Warning, MessageBoxResult.No);
                if (r != MessageBoxResult.Yes) {
                    evt.Cancel = true;
                    return;
                }
            }
            IsClosing = true;
        }

        private ComboBox GetPlzOrtInput(TextBox input) {
            return PlzOrtInputs[Array.IndexOf(PlzInputs, input)];
        }

        abstract protected void UpdateButtons();

        protected override async Task OnRenewContext(AppDbContext ctx) {
            for (int i = 0; i < PlzInputs.Length; i++)
                await UpdatePlz(PlzInputs[i], PlzOrtInputs[i]);
        }

        protected void ValidateInput(Control input, bool valid) {
            Valid[input] = valid;
        }

        protected bool SenderIsRequired(object sender) {
            return (sender is Control c) && RequiredInputs.Contains(c);
        }

        protected void FinishInputFilling() {
            FillOriginalValues();
            ValidateDefaultValues();
            ValidateRequiredInputs();
        }

        protected void ClearInputStates() {
            foreach (var tb in TextBoxInputs)
                ControlUtils.ClearInputState(tb);
            foreach (var cb in ComboBoxInputs)
                ControlUtils.ClearInputState(cb);
            foreach (var lb in ListBoxInputs)
                ControlUtils.ClearInputState(lb);
            foreach (var cb in CheckBoxInputs)
                ControlUtils.ClearInputState(cb);
            foreach (var rb in RadioButtonInputs)
                ControlUtils.ClearInputState(rb);
        }

        protected void ValidateRequiredInputs() {
            foreach (var input in RequiredInputs) {
                if (input is Control c && c.Visibility != Visibility.Visible) {
                    ControlUtils.ClearInputState(input);
                    Valid[input] = true;
                } else if (input is TextBox tb && tb.Text.Length == 0) {
                    ControlUtils.SetInputInvalid(input);
                    Valid[input] = false;
                } else if (input is ComboBox cb && cb.SelectedItem == null && cb.ItemsSource != null && cb.ItemsSource.Cast<object>().Any()) {
                    ControlUtils.SetInputInvalid(input);
                    Valid[input] = false;
                } else if (input is ListBox lb && lb.SelectedItem == null && lb.ItemsSource != null && lb.ItemsSource.Cast<object>().Any()) {
                    ControlUtils.SetInputInvalid(input);
                    Valid[input] = false;
                } else if (input is CheckBox ckb && ((ckb.IsThreeState && ckb.IsChecked == null) || (!ckb.IsThreeState && ckb.IsChecked != true))) {
                    ControlUtils.SetInputInvalid(input);
                    Valid[input] = false;
                } else if (input is RadioButton rb && rb.IsChecked != true) {
                    ControlUtils.SetInputInvalid(input);
                    Valid[input] = false;
                }
            }
        }

        protected void ValidateDefaultValues() {
            foreach (var input in DefaultValues.Keys) {
                if (InputIsNotDefault(input))
                    ControlUtils.SetInputNotDefault(input);
            }
        }

        protected void LockInputs() {
            foreach (var tb in TextBoxInputs)
                tb.IsReadOnly = true;
            foreach (var cb in ComboBoxInputs)
                cb.IsEnabled = false;
            foreach (var lb in ListBoxInputs)
                lb.IsEnabled = false;
            foreach (var cb in CheckBoxInputs)
                cb.IsEnabled = false;
            foreach (var rb in RadioButtonInputs)
                rb.IsEnabled = false;
        }

        protected void UnlockInputs() {
            foreach (var tb in TextBoxInputs)
                tb.IsReadOnly = false;
            foreach (var cb in ComboBoxInputs)
                cb.IsEnabled = true;
            foreach (var lb in ListBoxInputs)
                lb.IsEnabled = true;
            foreach (var cb in CheckBoxInputs)
                cb.IsEnabled = true;
            foreach (var rb in RadioButtonInputs)
                rb.IsEnabled = true;
        }

        protected void ClearOriginalValues() {
            OriginalValues.Clear();
        }

        protected void ClearDefaultValues() {
            DefaultValues.Clear();
        }

        protected void FillOriginalValues() {
            foreach (var tb in TextBoxInputs)
                OriginalValues[tb] = ControlUtils.GetInputHashCode(tb);
            foreach (var cb in ComboBoxInputs)
                OriginalValues[cb] = ControlUtils.GetInputHashCode(cb);
            foreach (var lb in ListBoxInputs)
                OriginalValues[lb] = ControlUtils.GetInputHashCode(lb);
            foreach (var cb in CheckBoxInputs)
                OriginalValues[cb] = ControlUtils.GetInputHashCode(cb);
            foreach (var rb in RadioButtonInputs)
                OriginalValues[rb] = ControlUtils.GetInputHashCode(rb);
        }

        protected void SetOriginalValue(Control input, object? value) {
            OriginalValues[input] = Utils.GetEntityIdentifier(value);
            if (InputHasChanged(input)) {
                ControlUtils.SetInputChanged(input);
            } else {
                ControlUtils.ClearInputState(input);
            }
        }

        protected void SetOriginalValue(Control input) {
            SetOriginalValue(input, ControlUtils.GetInputHashCode(input));
        }

        protected void UnsetOriginalValue(Control input) {
            OriginalValues.Remove(input);
            ControlUtils.ClearInputState(input);
        }

        protected void SetDefaultValue(Control input, object? value) {
            DefaultValues[input] = Utils.GetEntityIdentifier(value);
            if (!InputHasChanged(input)) {
                if (InputIsNotDefault(input)) {
                    ControlUtils.SetInputNotDefault(input);
                } else {
                    ControlUtils.ClearInputState(input);
                }
            }
        }

        protected void SetDefaultValue(Control input) {
            SetDefaultValue(input, ControlUtils.GetInputHashCode(input));
        }

        protected void UnsetDefaultValue(Control input) {
            DefaultValues.Remove(input);
            if (!InputHasChanged(input)) {
                ControlUtils.ClearInputState(input);
            }
        }

        protected void ClearInputs(bool validate = true) {
            foreach (var tb in TextBoxInputs) {
                tb.Text = "";
                var binding = tb.GetBindingExpression(TextBox.TextProperty);
                binding?.UpdateSource();
            }
            foreach (var cb in ComboBoxInputs) {
                cb.SelectedItem = null;
                var binding = cb.GetBindingExpression(ComboBox.SelectedItemProperty);
                binding?.UpdateSource();
            }
            foreach (var lb in ListBoxInputs)
                lb.SelectedItems.Clear();
            foreach (var cb in CheckBoxInputs) {
                cb.IsChecked = cb.IsThreeState ? null : false;
                var binding = cb.GetBindingExpression(CheckBox.IsCheckedProperty);
                binding?.UpdateSource();
            }
            foreach (var rb in RadioButtonInputs) {
                rb.IsChecked = rb.IsThreeState ? null : false;
                var binding = rb.GetBindingExpression(RadioButton.IsCheckedProperty);
                binding?.UpdateSource();
            }
            if (validate) ValidateRequiredInputs();
        }

        protected bool IsValid => Valid.All(kv => kv.Value);

        protected bool GetInputValid(Control input) {
            return Valid[input];
        }

        protected bool InputHasChanged(Control input) {
            if (!OriginalValues.TryGetValue(input, out int? original)) {
                return false;
            } else {
                var current = ControlUtils.GetInputHashCode(input);
                return original != current;
            }
        }

        protected bool InputIsNotDefault(Control input) {
            if (!DefaultValues.TryGetValue(input, out int? defaultValue)) {
                return false;
            } else {
                var current = ControlUtils.GetInputHashCode(input);
                return defaultValue != current;
            }
        }

        protected bool HasChanged =>
            IsEditing && (
                !IsValid ||
                TextBoxInputs.Any(InputHasChanged) ||
                ComboBoxInputs.Any(InputHasChanged) ||
                ListBoxInputs.Any(InputHasChanged) ||
                CheckBoxInputs.Any(InputHasChanged) ||
                RadioButtonInputs.Any(InputHasChanged)
            ) || IsCreating && (
                TextBoxInputs.Any(i => InputIsNotDefault(i) || (!i.IsReadOnly && i.Text != "")) ||
                ComboBoxInputs.Any(i => InputIsNotDefault(i) || (i.IsEnabled && i.SelectedItem != null)) ||
                ListBoxInputs.Any(i => InputIsNotDefault(i) || i.SelectedItem != null) ||
                CheckBoxInputs.Any(InputIsNotDefault) ||
                RadioButtonInputs.Any(InputIsNotDefault)
            );

        protected async Task UpdatePlz(TextBox plzInput, ComboBox ortInput) {
            var plzInputValid = Validator.CheckPlz(plzInput, RequiredInputs.Contains(plzInput)).IsValid;

            List<AT_PlzDest> list = [];
            if (plzInputValid && plzInput.Text.Length == 4) {
                var plz = int.Parse(plzInput.Text);
                using var ctx = new AppDbContext();
                list = await ctx.PlzDestinations
                   .Where(p => p.Plz == plz)
                   .Include(p => p.Ort)
                   .ToListAsync();
            }

            ControlUtils.RenewItemsSource(ortInput, list);
            if (ortInput.SelectedItem == null && list.Count == 1)
                ortInput.SelectedItem = list[0];
            UpdateComboBox(ortInput);
        }

        protected bool InputTextChanged(TextBox input) {
            return InputTextChanged(input, new ValidationResult(true, null));
        }

        protected bool InputTextChanged(TextBox input, Func<TextBox, bool, ValidationResult> checker) {
            return InputTextChanged(input, checker(input, SenderIsRequired(input)));
        }

        protected bool InputTextChanged(TextBox input, ValidationResult res) {
            ValidateInput(input, res.IsValid);
            if (res.IsValid) {
                if (InputHasChanged(input)) {
                    ControlUtils.SetInputChanged(input);
                } else if (InputIsNotDefault(input)) {
                    ControlUtils.SetInputNotDefault(input);
                } else {
                    ControlUtils.ClearInputState(input);
                }
            } else {
                ControlUtils.SetInputInvalid(input);
            }
            UpdateButtons();
            return res.IsValid;
        }

        protected bool InputLostFocus(TextBox input, Func<TextBox, bool, ValidationResult> checker, string? msg = null) {
            return InputLostFocus(input, checker(input, SenderIsRequired(input)), msg);
        }

        protected bool InputLostFocus(TextBox input, ValidationResult res, string? msg = null) {
            if (DoShowWarningWindows && !res.IsValid && !IsClosing && (IsEditing || IsCreating))
                MessageBox.Show(res.ErrorContent?.ToString(), msg ?? res.ErrorContent?.ToString(), MessageBoxButton.OK, MessageBoxImage.Warning);
            return res.IsValid;
        }

        protected void CheckBox_Changed(object sender, RoutedEventArgs evt) {
            var input = (CheckBox)sender;
            if (SenderIsRequired(input) && ((input.IsThreeState && input.IsChecked == null) || (!input.IsThreeState && input.IsChecked != true))) {
                ValidateInput(input, false);
                ControlUtils.SetInputInvalid(input);
            } else {
                ValidateInput(input, true);
                if (InputHasChanged(input)) {
                    ControlUtils.SetInputChanged(input);
                } else if (InputIsNotDefault(input)) {
                    ControlUtils.SetInputNotDefault(input);
                } else {
                    ControlUtils.ClearInputState(input);
                }
            }
            UpdateButtons();
        }

        protected void RadioButton_Changed(object sender, RoutedEventArgs evt) {
            var input = (RadioButton)sender;
            if (SenderIsRequired(input) && input.IsChecked != true) {
                ValidateInput(input, false);
                ControlUtils.SetInputInvalid(input);
            } else {
                ValidateInput(input, true);
                if (InputHasChanged(input)) {
                    ControlUtils.SetInputChanged(input);
                } else if (InputIsNotDefault(input)) {
                    ControlUtils.SetInputNotDefault(input);
                } else {
                    ControlUtils.ClearInputState(input);
                }
            }
            UpdateButtons();
        }

        protected void TextBox_TextChanged(object sender, RoutedEventArgs? evt) {
            var input = (Control)sender;
            var tb = input as TextBox;
            if (SenderIsRequired(input) && tb?.Text.Length == 0) {
                ValidateInput(input, false);
                ControlUtils.SetInputInvalid(input);
            } else {
                ValidateInput(input, true);
                if (InputHasChanged(input)) {
                    ControlUtils.SetInputChanged(input);
                } else if (InputIsNotDefault(input)) {
                    ControlUtils.SetInputNotDefault(input);
                } else {
                    ControlUtils.ClearInputState(input);
                }
            }
            UpdateButtons();
        }

        private void UpdateComboBox(Control input) {
            bool valid = false;
            if (input is ComboBox cb) {
                valid = cb.ItemsSource == null || cb.SelectedItem != null || !RequiredInputs.Contains(input) || !cb.ItemsSource.Cast<object>().Any();
            } else if (input is ListBox lb) {
                valid = lb.ItemsSource == null || lb.SelectedItem != null || !RequiredInputs.Contains(input) || !lb.ItemsSource.Cast<object>().Any();
            }
            if (valid) {
                ValidateInput(input, true);
                if (InputHasChanged(input)) {
                    ControlUtils.SetInputChanged(input);
                } else if (InputIsNotDefault(input)) {
                    ControlUtils.SetInputNotDefault(input);
                } else {
                    ControlUtils.ClearInputState(input);
                }
            } else {
                ValidateInput(input, false);
                ControlUtils.SetInputInvalid(input);
            }
            UpdateButtons();
        }

        protected void ComboBox_SelectionChanged(object sender, RoutedEventArgs? evt) {
            UpdateComboBox((Control)sender);
        }

        protected void IntegerInput_TextChanged(object sender, TextChangedEventArgs? evt) {
            // FIXME
            InputTextChanged((TextBox)sender, Validator.CheckInteger);
        }

        protected void DecimalInput_TextChanged(object sender, TextChangedEventArgs? evt) {
            // FIXME
            InputTextChanged((TextBox)sender, Validator.CheckDecimal);
        }

        protected void PartialDateInput_TextChanged(object sender, TextChangedEventArgs? evt) {
            InputTextChanged((TextBox)sender, Validator.CheckPartialDate);
        }

        protected void PartialDateInput_LostFocus(object sender, RoutedEventArgs? evt) {
            InputLostFocus((TextBox)sender, Validator.CheckPartialDate);
        }

        protected void DateInput_TextChanged(object sender, TextChangedEventArgs? evt) {
            InputTextChanged((TextBox)sender, Validator.CheckDate);
        }

        protected void DateInput_LostFocus(object sender, RoutedEventArgs? evt) {
            InputLostFocus((TextBox)sender, Validator.CheckDate);
        }

        protected void TimeInput_TextChanged(object sender, TextChangedEventArgs? evt) {
            InputTextChanged((TextBox)sender, Validator.CheckTime);
        }

        protected void TimeInput_LostFocus(object sender, RoutedEventArgs? evt) {
            InputLostFocus((TextBox)sender, Validator.CheckTime);
        }

        protected async void PlzInput_TextChanged(object sender, TextChangedEventArgs? evt) {
            var plz = (TextBox)sender;
            InputTextChanged(plz, Validator.CheckPlz);
            if ("PLZ".Equals(plz.Tag))
                await UpdatePlz(plz, GetPlzOrtInput(plz));
        }

        protected async void PlzInput_LostFocus(object sender, RoutedEventArgs? evt) {
            var plz = (TextBox)sender;
            InputLostFocus(plz, Validator.CheckPlz);
            if ("PLZ".Equals(plz.Tag))
                await UpdatePlz(plz, GetPlzOrtInput(plz));
        }

        protected void EmailAddressInput_TextChanged(object sender, TextChangedEventArgs? evt) {
            InputTextChanged((TextBox)sender, Validator.CheckEmailAddress);
        }

        protected void EmailAddressInput_LostFocus(object sender, RoutedEventArgs? evt) {
            InputLostFocus((TextBox)sender, Validator.CheckEmailAddress);
        }

        protected void PhoneNrInput_TextChanged(object sender, TextChangedEventArgs? evt) {
            InputTextChanged((TextBox)sender, Validator.CheckPhoneNumber);
        }

        protected void PhoneNrInput_LostFocus(object sender, RoutedEventArgs? evt) {
            InputLostFocus((TextBox)sender, Validator.CheckPhoneNumber);
        }

        protected void IbanInput_TextChanged(object sender, TextChangedEventArgs? evt) {
            InputTextChanged((TextBox)sender, Validator.CheckIban);
        }

        protected void IbanInput_LostFocus(object sender, RoutedEventArgs? evt) {
            InputLostFocus((TextBox)sender, Validator.CheckIban);
        }

        protected void BicInput_TextChanged(object sender, TextChangedEventArgs? evt) {
            InputTextChanged((TextBox)sender, Validator.CheckBic);
        }

        protected void BicInput_LostFocus(object sender, RoutedEventArgs? evt) {
            InputLostFocus((TextBox)sender, Validator.CheckBic);
        }

        protected void UstIdNrInput_TextChanged(object sender, TextChangedEventArgs? evt) {
            InputTextChanged((TextBox)sender, Validator.CheckUstIdNr);
        }

        protected void UstIdNrInput_LostFocus(object sender, RoutedEventArgs? evt) {
            InputLostFocus((TextBox)sender, Validator.CheckUstIdNr);
        }

        protected void LfbisNrInput_TextChanged(object sender, TextChangedEventArgs? evt) {
            InputTextChanged((TextBox)sender, Validator.CheckLfbisNr);
        }

        protected void LfbisNrInput_LostFocus(object sender, RoutedEventArgs? evt) {
            InputLostFocus((TextBox)sender, Validator.CheckLfbisNr);
        }

        protected void OrganicAuthorityCodeInput_TextChanged(object sender, TextChangedEventArgs? evt) {
            InputTextChanged((TextBox)sender, Validator.CheckOrganicAuthorityCode);
        }

        protected void OrganicAuthorityCodeInput_LostFocus(object sender, RoutedEventArgs? evt) {
            InputLostFocus((TextBox)sender, Validator.CheckOrganicAuthorityCode);
        }

        protected void UpperCaseInput_TextChanged(object sender, TextChangedEventArgs? evt) {
            InputTextChanged((TextBox)sender, Validator.CheckUpperCase);
        }

        protected void UpperCaseInput_LostFocus(object sender, RoutedEventArgs? evt) {
            InputLostFocus((TextBox)sender, Validator.CheckUpperCase);
        }

        protected void AreaComTypeDetailsButton_Click(object sender, RoutedEventArgs? evt) {
            App.FocusBaseDataAreaComType();
        }
    }
}
