using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Controls;
using InABox.Clients;
using InABox.Core;
using InABox.Wpf;
using InABox.Wpf.Reports;
using InABox.WPF;

namespace InABox.DynamicGrid;

public class DynamicFormEditButton : INotifyPropertyChanged
{
    public delegate void DynamicFormEditButtonDelegate(DynamicFormEditWindow window, DynamicFormEditButton button);

    private object? _content;
    public object? Content
    {
        get => _content;
        set
        {
            _content = value;
            OnPropertyChanged();
        }
    }

    public DynamicFormEditButtonDelegate Action { get; set; }

    public DynamicFormEditButton(object? content, DynamicFormEditButtonDelegate action)
    {
        Content = content;
        Action = action;
    }

    public event PropertyChangedEventHandler? PropertyChanged;

    protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

/// <summary>
///     Interaction logic for FormDesigner.xaml
/// </summary>
public partial class DynamicFormEditWindow : Window, IDynamicFormWindow
{
    public delegate void CustomiseDynamicFormEditWindow(DynamicFormEditWindow window);

    public enum FormResult
    {
        None,
        Cancel,
        Save,
        Complete
    }

    public DynamicFormEditWindow()
    {
        InitializeComponent();
        Grid.OnChanged += Grid_OnChanged;

        //Complete.IsEnabled = Security.IsAllowed<CanChangeDigitalFormCompletion>();
    }

    public DynamicFormDesignGrid Grid => Preview;

    public FormMode Mode
    {
        get => Grid.Mode;
        private set => Grid.Mode = value;
    }

    public FormResult Result = FormResult.None;

    public IDigitalFormDataModel? DataModel
    {
        get => Grid.DataModel;
        set
        {
            Grid.DataModel = value;
            RefreshEnabled();
        }
    }

    public ObservableCollection<DynamicFormEditButton> CustomButtons { get; set; } = new ObservableCollection<DynamicFormEditButton>();

    private bool IsReopening = false;
    private bool HasChanged = false;
    private bool HasUnsavedChanges => Grid.IsChanged || HasChanged || DataModel!.Instance.ID == Guid.Empty;

    private DFLayoutType _type;
    public DFLayoutType Type
    {
        get => _type;
        set
        {
            _type = value;
            Width = _type == DFLayoutType.Mobile ? 600 : 1000;
            Height = 800;
        }
    }

    public DFSaveStorage SaveValues() => Grid.SaveValues();

    public void LoadValues(DFLoadStorage storage)
    {
        Grid.LoadValues(storage);
        RefreshEnabled();
    }

    private void RefreshEnabled()
    {
        var formInstance = DataModel!.Instance;
        var completed = formInstance.FormCompleted;

        CompletedDate.Content = !completed.IsEmpty() ? $"Completed {completed:d MMM yyyy} at {completed:hh:mm tt}" : "Not completed yet";

        if (completed.IsEmpty()
            && DFUtils.CanEditForm(formInstance.GetType(), formInstance, DataModel.Entity))
        {
            Mode = FormMode.Filling;
        }
        else
        {
            Mode = Security.IsAllowed<CanEditCompletedForms>()
                ? FormMode.Editing
                : FormMode.ReadOnly;
        }

        if (Mode == FormMode.Editing || Mode == FormMode.Filling || Mode == FormMode.Preview)
        {
            SaveForm.IsEnabled = HasUnsavedChanges;
            if(Mode == FormMode.Editing)
            {
                SaveForm.Content = "Save Form";
            }
            else
            {
                SaveForm.Content = "Save Progress";
            }
        }
        else
        {
            if (Security.IsAllowed<CanChangeDigitalFormCompletion>())
            {
                SaveForm.IsEnabled = HasUnsavedChanges;
                SaveForm.Content = "Save Form";
            }
            else
            {
                SaveForm.IsEnabled = false;
                SaveForm.Content = "Save Progress";
            }
        }

        if (!completed.IsEmpty() && Security.IsAllowed<CanChangeDigitalFormCompletion>())
        {
            CompleteForm.Content = "Re-open form";
            CompleteForm.IsEnabled = true;
            IsReopening = true;
        }
        else
        {
            CompleteForm.Content = "Complete form";
            CompleteForm.IsEnabled = (Mode == FormMode.Filling || Security.IsAllowed<CanChangeDigitalFormCompletion>()) && DataModel?.Instance.FormCompleted == DateTime.MinValue;
            IsReopening = false;
        }
    }

    private void Grid_OnChanged(DynamicFormDesignGrid sender, string fieldName)
    {
        if (Mode == FormMode.Editing || Mode == FormMode.Filling || Mode == FormMode.Preview)
        {
            SaveForm.IsEnabled = true;
        }
    }

    protected override void OnClosing(CancelEventArgs e)
    {
        if (DialogResult == null)
        {
            Result = FormResult.Cancel;
            DialogResult = false;
        }
        base.OnClosing(e);
    }

    private void Complete()
    {
        if (!Grid.Validate(out var messages))
        {
            MessageWindow.ShowMessage(string.Join('\n', messages), "Validation Error", image: MessageWindow.WarningImage);
            return;
        }
        if (MessageWindow.ShowYesNo("Are you sure you want to complete this form?", "Confirm Completion"))
        {
            Result = FormResult.Complete;
            DialogResult = true;
        }
    }
    private void Reopen()
    {
        HasChanged = true;
        DataModel!.Instance.FormCompleted = DateTime.MinValue;
        DataModel!.Instance.FormCompletedBy.Clear();
        RefreshEnabled();
    }

    private void CompleteForm_Click(object sender, RoutedEventArgs e)
    {
        if (IsReopening)
            Reopen();
        else
            Complete();
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        if (sender is not Button btn || btn.Tag is not DynamicFormEditButton button) return;

        button.Action(this, button);
    }

    private DataModel? GetReportDataModel()
    {
        var dataModel = DataModel;
        if (dataModel is null) return null;

        var formType = dataModel.Instance.GetType();

        var filter = Filter.Create<Entity>(formType, x => x.ID).IsEqualTo(dataModel.Instance.ID);

        return (Activator.CreateInstance(typeof(DigitalFormReportDataModel<>)!
            .MakeGenericType(formType), new object?[] { filter, dataModel.Instance.Form.ID }) as DataModel)!;
    }

    private void PrintForm_Click(object sender, RoutedEventArgs e)
    {
        var model = GetReportDataModel();
        var dataModel = DataModel;
        if (model is null || dataModel is null) return;

        (model as IDigitalFormReportDataModel)!.AddFormData(dataModel.Instance.ID, SaveValues().ToLoadStorage());

        var menu = new ContextMenu();

        ReportUtils.PopulateMenu(menu, dataModel.Instance.Form.ID.ToString(), model, false);
        if(menu.Items.Count == 0)
        {
            menu.AddItem("No reports", null, null, enabled: false);
        }

        menu.IsOpen = true;
    }

    private void SaveForm_Click(object sender, RoutedEventArgs e)
    {
        if (DataModel?.Instance.FormCompleted.IsEmpty() == false && !Grid.Validate(out var messages))
        {
            MessageWindow.ShowMessage(string.Join('\n', messages), "Validation Error", image: MessageWindow.WarningImage);
            return;
        }
        if (MessageWindow.ShowYesNo("Are you sure you want to save this form?", "Confirm Save"))
        {
            Result = FormResult.Save;
            DialogResult = true;
        }
    }

    public static Columns<TForm> FormColumns<TForm>() where TForm : IDigitalFormInstance
    {
        return (FormColumns(typeof(TForm)) as Columns<TForm>)!;
    }
    public static IColumns FormColumns(Type TForm)
    {
        return Columns.Create<IDigitalFormInstance>(TForm, ColumnTypeFlags.None)
            .Add<IDigitalFormInstance>(x => x.ID)
            .Add<IDigitalFormInstance>(x=>x.Number)
            .Add<IDigitalFormInstance>(x=>x.Description)
            .Add<IDigitalFormInstance>(x => x.FormCompleted)
            .Add<IDigitalFormInstance>(x => x.FormData)
            .Add<IDigitalFormInstance>(x => x.BlobData)
            .Add<IDigitalFormInstance>(x => x.Form.ID)
            .Add<IDigitalFormInstance>(x => x.Form.Description)
            .Add<IDigitalFormInstance>(x => x.Form.DescriptionExpression)
            .Add("Parent.ID");
    }

    public static bool EditDigitalForm(
        IDigitalFormInstance formInstance,
        [NotNullWhen(true)] out IDigitalFormDataModel? dataModel,
        Entity? parent = null,
        CustomiseDynamicFormEditWindow? customise = null)
    {

        dataModel = null;

        DigitalFormLayout layout = null;
        DigitalFormVariable[] variables = null;
        DFLoadStorage? values = null;
        string? error = null;
        string? errorDetails = null;

        Progress.ShowModal("Loading Form", (progress) =>
        {
            var formid = formInstance.Form.ID;

            values = DigitalForm.DeserializeFormData(formInstance);

            var results = Client.QueryMultiple(
                new KeyedQueryDef<DigitalFormVariable>(new Filter<DigitalFormVariable>(x => x.Form.ID).IsEqualTo(formid)),
                new KeyedQueryDef<DigitalFormLayout>(
                    new Filter<DigitalFormLayout>(x => x.Form.ID).IsEqualTo(formid)
                        .And(x => x.Active).IsEqualTo(true)
                        .And(x => x.Layout).IsNotEqualTo("")));

            variables = results[nameof(DigitalFormVariable)].Rows.Select(x => x.ToObject<DigitalFormVariable>()).ToArray();

            var desktopLayout = results[nameof(DigitalFormLayout)]
                .Rows.FirstOrDefault(x => x.Get<DigitalFormLayout, DFLayoutType>(x => x.Type) == DFLayoutType.Desktop)
                ?.ToObject<DigitalFormLayout>();

            layout = desktopLayout ?? results[nameof(DigitalFormLayout)].ToObjects<DigitalFormLayout>().FirstOrDefault();
            if (layout != null)
            {
                if (parent is null)
                {
                    var parentlink = CoreUtils.HasProperty(formInstance.GetType(), "Parent")
                        ? CoreUtils.GetPropertyValue(formInstance, "Parent") as IEntityLink
                        : null;
                    var parenttype = parentlink?.GetType().BaseType?.GetGenericArguments().FirstOrDefault();

                    if (parenttype != null && parentlink != null)
                    {
                        var parentid = parentlink.ID;
                        var filter = Filter.Create(parenttype);
                        filter.Property = "ID";
                        filter.Operator = Operator.IsEqualTo;
                        filter.Value = parentid;

                        parent = Client.Create(parenttype)
                            .Query(filter, DFUtils.RequiredEntityColumns(parenttype, variables), null).ToObjects(parenttype).FirstOrDefault() as Entity;
                    }

                    if (parent == null)
                    {
                        error = "An error occurred while loading the form: Form Entity is null";
                        errorDetails = $"Form parent is null; Form Type: {formInstance.GetType()}; Parent Type: {parenttype}; Form ID: {formInstance.ID}";
                    }
                }
            }
            else
            {
                error = "No layout found for form!";
            }
        });

        if (!error.IsNullOrWhiteSpace())
        {
            if(errorDetails is null)
            {
                MessageWindow.ShowError(error, error, shouldLog: false);
            }
            else
            {
                MessageWindow.ShowError(error, errorDetails);
            }
            return false;
        }

        var form = new DynamicFormEditWindow
        {
            Type = layout.Type,
            Title = string.Format("Viewing {0}", formInstance.Form.Description)
        };
        form.LoadLayout(layout, variables);

        try
        {
            dataModel = formInstance.CreateDataModel(variables, parent!);
            dataModel.Variables = variables;
            dataModel.OnModelSaved += (model) =>
            {
                DFUtils.OnSave(formInstance.GetType(), formInstance, parent!);
            };
            form.DataModel = dataModel;
        }
        catch (Exception e)
        {
            Logger.Send(LogType.Error, ClientFactory.UserID, $"Error during Edit Form / CreateDataModel: {CoreUtils.FormatException(e)}");
        }

        form.Initialize();
        
        form.LoadValues(values ?? new DFLoadStorage());

        customise?.Invoke(form);

        if (form.ShowDialog() == true)
        {
            if (form.Result == FormResult.Complete)
            {
                formInstance.FormCompleted = DateTime.Now;
                formInstance.FormCompletedBy.ID = ClientFactory.UserGuid;
                formInstance.FormCompletedBy.UserID = ClientFactory.UserID;
                /*formInstance.FormCompleted = form.Completed
                    ? formInstance.FormCompleted.IsEmpty()
                        ? DateTime.Now
                        : formInstance.FormCompleted
                    : DateTime.MinValue;

                formInstance.FormCompletedBy.ID = form.Completed
                    ? !formInstance.FormCompletedBy.IsValid()
                        ? ClientFactory.UserGuid
                        : formInstance.FormCompletedBy.ID
                    : Guid.Empty;

                formInstance.FormCompletedBy.UserID = form.Completed
                    ? string.IsNullOrWhiteSpace(formInstance.FormCompletedBy.UserID)
                        ? ClientFactory.UserID
                        : formInstance.FormCompletedBy.UserID
                    : "";*/
            }

            DigitalForm.SerializeFormData(formInstance, variables, form.SaveValues());
        }

        return form.Result == FormResult.Save || form.Result == FormResult.Complete;
    }

    public static bool EditDigitalForm<TForm>(Guid formID, [NotNullWhen(true)] out IDigitalFormDataModel? dataModel)
        where TForm : Entity, IDigitalFormInstance, IRemotable, IPersistent, new()
    {
        var form = (new Client<TForm>()
            .Query(
                new Filter<TForm>(x => x.ID).IsEqualTo(formID),
                FormColumns<TForm>())
            .Rows.FirstOrDefault()?.ToObject<TForm>())
            ?? throw new Exception($"{typeof(TForm)} {formID} does not exist");
        return EditDigitalForm(form, out dataModel);
    }

    private void DynamicFormWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e)
    {
        if (DialogResult != true && HasUnsavedChanges)
        {
            if (!MessageWindow.ShowYesNo("This form has unsaved changes. Do you wish to discard them?", "Discard Changes?"))
            {
                e.Cancel = true;
            }
        }
    }
}