using System;
using System.Collections.Generic;
using System.Linq;
using InABox.Clients;
using System.Diagnostics.CodeAnalysis;

namespace InABox.Core
{
    public interface IDigitalFormReportDataModel
    {
        public DigitalFormVariable[]? Variables { get; set; }

        void AddFormData(Guid formInstanceID, DFLoadStorage storage);
    }

    /// <summary>
    /// </summary>
    /// <typeparam name="T">
    ///     Should always be an EntityForm<,></typeparam>
    public class DigitalFormReportDataModel<T> : AutoDataModel<T>, IDigitalFormReportDataModel
        where T : Entity, IRemotable, IPersistent, IDigitalFormInstance, new()
    {

        private Guid? FormId { get; set; } = null;

        private CoreTable? FormDataTable { get; set; } = null;

        private Dictionary<Guid, DFLoadStorage> FormData { get; set; } = new Dictionary<Guid, DFLoadStorage>();

        //private DigitalFormVariable[]? _variables = null;

        public DigitalFormVariable[]? Variables { get; set; }

        public DigitalFormReportDataModel(Filter<T> filter, Guid? formId = null) : base(filter)
        {
            FormId = formId;
        }

        public override string Name => "Digital Form Reports";

        public void AddFormData(Guid formInstanceID, DFLoadStorage storage)
        {
            FormData.Add(formInstanceID, storage);
        }

        protected override void CheckRequiredTables(List<string> requiredtables)
        {
            base.CheckRequiredTables(requiredtables);

            requiredtables.Add(TableName<T>());
        }

        private void LoadFormDataTable(Guid formID)
        {
            var formDataTable = new CoreTable();

            formDataTable.Columns.Add(new CoreColumn
            {
                ColumnName = "Parent.ID",
                DataType = typeof(Guid)
            });

            Variables ??= new Client<DigitalFormVariable>().Load(new Filter<DigitalFormVariable>(x => x.Form.ID).IsEqualTo(formID));

            foreach (var variable in Variables)
            {
                var columns = variable.GetDisplayColumns().ToArray();
                foreach (var column in columns)
                    formDataTable.Columns.Add(column);
            }

            FormDataTable = formDataTable;

            AddTable("Form_Data", formDataTable, isdefault: true);
            LinkTable(typeof(T), "ID", "Form_Data", "Parent.ID");
        }

        private void LoadFormDataIntoRow(CoreRow row, DFLoadStorage data)
        {
            foreach(var variable in Variables!)
            {
                var value = variable.Deserialize(data.GetEntry(variable.Code));
                foreach(var (k, v) in variable.GetDisplayValues(value))
                {
                    row[k] = v;
                }
            }
        }

        protected override void AfterLoad(IEnumerable<string> requiredtables)
        {
            base.AfterLoad(requiredtables);

            if(FormDataTable == null)
            {
                LoadFormDataTable(FormId ?? ExtractValues<T, Guid>(x => x.Form.ID).FirstOrDefault());
            }

            var idList = ExtractValues<T, Guid>(x => x.ID);
            var jsonLists = ExtractValues<T, string>(x => x.FormData, false).ToList();
            var blobLists = ExtractValues<T, string?>(x => x.BlobData, false).ToList();

            for(var i = 0; i < jsonLists.Count; ++i)
            {
                if (!FormData.TryGetValue(idList[i], out var formData))
                {
                    var json = jsonLists[i];
                    var blobJSON = blobLists[i];

                    formData = DigitalForm.DeserializeFormData(json, blobJSON);
                }

                var formRow = FormDataTable!.NewRow();
                formRow["Parent.ID"] = idList[i];
                if (formData != null) LoadFormDataIntoRow(formRow, formData);

                FormDataTable.Rows.Add(formRow);
            }

            Load(typeof(CoreTable), FormDataTable!, requiredtables, "Form_Data");
        }
        
        public string? EvaluateExpression(string expression)
        {
            if (string.IsNullOrWhiteSpace(expression))
                return null;
            
            Dictionary<string, object?> variables = new Dictionary<string, object?>();

            var formrow = FormDataTable.Rows.FirstOrDefault();
            foreach (var column in FormDataTable.Columns)
                variables[$"Data.{column.ColumnName}"] = formrow?[column.ColumnName];

            var instancetable = GetTable<T>();
            var instancerow = instancetable.Rows.FirstOrDefault();
            foreach (var column in instancetable.Columns)
                variables[$"Form.{column.ColumnName}"] = instancerow?[column.ColumnName];
                
            var inter = typeof(T)
                .GetInterfaces()
                .FirstOrDefault(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IDigitalFormInstance<>));
            if (inter != null)
            {
                var link = inter.GenericTypeArguments[0];
                var entityLinkDef = link.GetSuperclassDefinition(typeof(EntityLink<>));
                if (entityLinkDef != null)
                {
                    var entityType = entityLinkDef.GenericTypeArguments[0];
                    var parenttablename = $"{DataModel.TableName(typeof(T))}_{DataModel.TableName(entityType)}";
                    var entitytable = GetDataModelTable(parenttablename);
                    if (entitytable != null)
                    {
                        foreach (var column in entitytable.Table.Columns)
                            variables[$"{entityType.EntityName().Split('.').Last()}.{column.ColumnName}"] =
                                instancerow?[column.ColumnName];
                    }
                }
            }
            
            var exp = new CoreExpression(expression);
            return exp.Evaluate(variables)?.ToString();
        }
    }
}