using System.Linq.Expressions;
using e_suite.API.Common;
using e_suite.API.Common.exceptions;
using e_suite.API.Common.models;
using e_suite.API.Common.repository;
using e_suite.Database.Audit;
using e_suite.Database.Core.Extensions;
using e_suite.Database.Core.Helpers;
using e_suite.Database.Core.Tables.CustomFields;
using e_suite.Database.Core.Tables.Forms;
using e_suite.Utilities.Pagination;
using eSuite.Core.Miscellaneous;
using HtmlAgilityPack;
using Microsoft.EntityFrameworkCore;

namespace e_suite.Modules.FormsManager;

public class FormsManager : IFormsManager
{
    private readonly IFormRepository _formRepository;
    private readonly ICustomFieldRepository _customFieldRepository;
    private readonly ICustomFieldHelper _customFieldHelper;
    private readonly ICustomFieldValidator _customFieldValidator;

    public FormsManager(IFormRepository formRepository, ICustomFieldRepository customFieldRepository, ICustomFieldHelper customFieldHelper, ICustomFieldValidator customFieldValidator)
    {
        _formRepository = formRepository;
        _customFieldRepository = customFieldRepository;
        _customFieldHelper = customFieldHelper;
        _customFieldValidator = customFieldValidator;
    }

    private readonly Dictionary<FormTemplate, FormTemplateVersion> _currentFormTemplateVersionCache = [];


    private static void Assert(FormTemplate? customField)
    {
        if (customField == null || customField.Deleted)
        {
            throw new NotFoundException($"There is no CustomField with this Id");
        }
    }

    public async Task CreateFormTemplateAsync(AuditUserDetails auditUserDetails, CreateFormTemplate createFormTemplate, CancellationToken cancellationToken)
    {
        await _formRepository.TransactionAsync(async () =>
        {

            if (createFormTemplate.Guid != null && await _formRepository.GetTemplateAsync(new GeneralIdRef { Guid = createFormTemplate.Guid }, cancellationToken) != null)
                throw new ExistsException("A Field with this id exists");

            var formTemplate = new FormTemplate
            {
                Name = createFormTemplate.Name,
                Guid = createFormTemplate.Guid ?? Guid.NewGuid()
            };

            await _formRepository.CreateTemplateAsync(auditUserDetails, formTemplate, cancellationToken);
            Assert(formTemplate);

            var formVersion = new FormTemplateVersion
            {
                Version = 1,
                FormDefinition = createFormTemplate.Definition,
                TemplateId = formTemplate.Id,
                Guid = Guid.NewGuid()
            };

            await _formRepository.CreateNewFormVersionAsync(auditUserDetails, formVersion, cancellationToken);
        });
    }

    public async Task DeleteFormTemplateAsync(AuditUserDetails auditUserDetails, IGeneralIdRef generalIdRef, CancellationToken cancellationToken)
    {
        await _formRepository.TransactionAsync(async () =>
        {
            var formTemplate = await _formRepository.GetTemplateWithOutLoadedRefereranceAsync(generalIdRef, cancellationToken) ?? throw new NotFoundException("Form with this id doesn't exists");
            formTemplate.Deleted = true;
            await _formRepository.EditFormTemplateAsync(auditUserDetails, formTemplate, cancellationToken);
            var formVersions = await _formRepository.GetAllFormVersions(formTemplate.Id).ToListAsync(cancellationToken);
            await _formRepository.DeleteAllVersionsAsync(auditUserDetails, formVersions, cancellationToken);
        });
    }

    public async Task EditFormTemplateAsync(AuditUserDetails auditUserDetails, EditFormTemplate formTemplate, CancellationToken cancellationToken) 
    {
        await _formRepository.TransactionAsync(async () =>
        {
            var formTemplateOriginal = await LoadAndEditFormTemplate(auditUserDetails, formTemplate, cancellationToken);

            var formVersions = await _formRepository.GetAllFormVersions(formTemplateOriginal.Id).ToListAsync(cancellationToken);
            var lastFormVersion = formVersions.Last();
            if (lastFormVersion.FormDefinition != formTemplate.Definition)
            {
                var formVersion = new FormTemplateVersion
                {
                    FormDefinition = formTemplate.Definition,
                    TemplateId = formTemplateOriginal.Id,
                    Version = lastFormVersion.Version + 1

                };
                await _formRepository.CreateNewFormVersionAsync(auditUserDetails, formVersion, cancellationToken);
            }
        });
    }

    private async Task<FormTemplate> LoadAndEditFormTemplate(AuditUserDetails auditUserDetails, EditFormTemplate formTemplate, CancellationToken cancellationToken)
    {
        var formTemplateOriginal = await _formRepository.GetTemplateAsync(formTemplate.Id, cancellationToken);
        Assert(formTemplateOriginal);
        var editedFormTemplate = false;
        if (!formTemplateOriginal!.Name.Equals(formTemplate.Name, StringComparison.InvariantCulture))
        {
            editedFormTemplate = true;
            formTemplateOriginal!.Name = formTemplate.Name;
        }

        if (editedFormTemplate)
        {
            await _formRepository.EditFormTemplateAsync(auditUserDetails, formTemplateOriginal, cancellationToken);
        }

        return formTemplateOriginal;
    }

    public async Task<GetFormTemplate> GetFormTemplateAsync(IGeneralIdRef generalIdRef, CancellationToken cancellationToken)
    {
        var formTemplate = await _formRepository.GetTemplateAsync(generalIdRef, cancellationToken);
        Assert(formTemplate);
        return await MapFormTemplate(formTemplate!, cancellationToken);
    }

    public async Task<IPaginatedData<GetFormTemplate>> GetFormTemplatesAsync(Paging paging, CancellationToken cancellationToken)
    {
        var forms = _formRepository.GetTemplates();

        var paginatedData = await PaginatedData.Paginate(forms, paging,
            KeySelector, cancellationToken);
        
        var mappedData = new List<GetFormTemplate>();

        foreach (var item in paginatedData.Data)
            mappedData.Add(await MapFormTemplate(item, cancellationToken));

        var paginatedResult = new PaginatedData<GetFormTemplate>
        {
            Count = paginatedData.Count,
            Page = paginatedData.Page,
            PageSize = paginatedData.PageSize,
            Data = mappedData
        };
        return paginatedResult;
    }

    private Expression<Func<FormTemplate, object>> KeySelector(string? sortKey)
    {
        return sortKey?.ToLowerInvariant() switch
        {
            "id" => x => x.Id,
            "guid" => x => x.Guid,
            "version" => x => x.Versions
                .Where(v => !v.Deleted)
                .Max(v => v.Version),
            _ => x => x.Name
        };
    }

    private async Task<GetFormTemplate> MapFormTemplate(FormTemplate formTemplate, CancellationToken cancellationToken)
    {
        var currentVersion = await GetCurrentVersionFromTemplate(formTemplate, cancellationToken);

        var fieldsAsGeneralIdRefs = await GetCustomFieldsFromDefinition(currentVersion.FormDefinition);
        var customFieldDefinitions = await GetCustomFieldDefiniations(fieldsAsGeneralIdRefs, cancellationToken);

        return new GetFormTemplate
        {
            Definition = currentVersion.FormDefinition,
            Guid = formTemplate.Guid,
            Id = formTemplate.Id,
            Name = formTemplate.Name,
            Version = currentVersion.Version,
            CustomFieldDefinitions = customFieldDefinitions
        };
    }

    private async Task<FormTemplateVersion> GetCurrentVersionFromTemplate(FormTemplate formTemplate, CancellationToken cancellationToken)
    {
        if (_currentFormTemplateVersionCache.TryGetValue(formTemplate, out var template))
            return template;

        var currentFormTemplateVersion = await _formRepository.GetAllFormVersions(formTemplate.Id)
            .OrderByDescending(fx => fx.Version).FirstAsync(fx => !fx.Deleted, cancellationToken);

        _currentFormTemplateVersionCache.Add(formTemplate, currentFormTemplateVersion);

        return currentFormTemplateVersion;
    }

    private static readonly Dictionary<string, List<GeneralIdRef>> _customFieldsFromDefinitionCache = [];

    public static Task<List<GeneralIdRef>> GetCustomFieldsFromDefinition( string formDefinition )
    {
        if (_customFieldsFromDefinitionCache.TryGetValue(formDefinition, out var definition))
            return Task.FromResult(definition);

        var doc = new HtmlDocument();
        doc.LoadHtml(formDefinition);

        //first I need to figure out the custom fields that are in the definition
        var fieldsAsGeneralIdRefs = new List<GeneralIdRef>();

        var nodes = doc.DocumentNode.SelectNodes("//span");

        if (nodes != null)
        {
            foreach (var node in nodes)
            {
                if (node.Attributes.Contains("fieldType"))
                {
                    if (node.Attributes["fieldType"].Value == "CustomField")
                    {
                        fieldsAsGeneralIdRefs.Add(new GeneralIdRef
                        {
                            Guid = Guid.Parse(node.Attributes["guid"].Value)
                        });
                    }
                }
            }
        }

        if (fieldsAsGeneralIdRefs.Count > 0)
            _customFieldsFromDefinitionCache.Add(formDefinition, fieldsAsGeneralIdRefs);

        return Task.FromResult(fieldsAsGeneralIdRefs);
    }
    
    private async Task<List<CustomFieldDefinition>> GetCustomFieldDefiniations(IEnumerable<GeneralIdRef> fieldsAsGeneralIdRefs, CancellationToken cancellationToken)
    {
        var results = new List<CustomFieldDefinition>();

        var customFields = await GetCustomFields(fieldsAsGeneralIdRefs, cancellationToken);

        foreach (var customField in customFields)
            results.Add(await _customFieldHelper.TranslateToCustomFieldDefinitionAsync(customField, cancellationToken));

        return results;
    }

    private async Task<List<CustomField>> GetCustomFields(IEnumerable<GeneralIdRef> fieldsAsGeneralIdRefs, CancellationToken cancellationToken)
    {
        var results = new List<CustomField>();

        foreach (var fieldRef in fieldsAsGeneralIdRefs)
        {
            var result = await _customFieldRepository.GetCustomFieldList().FindByGeneralIdRefAsync(fieldRef, cancellationToken) ?? throw new NotFoundException("Cannot find custom field");
            results.Add(result);
        }

        return results;
    }
    
    public async Task<ReadFormInstance?> GetFormInstanceAsync(GeneralIdRef generalIdRef, CancellationToken cancellationToken)
    {
        var formInstance = await _formRepository.GetFormInstance(generalIdRef,false, cancellationToken) ?? throw new NotFoundException("Unable to find form instance");
        var currentVersion = await GetCurrentVersionFromTemplate(formInstance.FormTemplateVersion.Template, cancellationToken);
        var customFieldDefinitions = await GetCustomFieldDefinitions(formInstance.FormTemplateVersion, cancellationToken);

        var result = new ReadFormInstance
        {
            Id = formInstance.Id,
            Guid = formInstance.Guid,
            TemplateId = formInstance.FormTemplateVersion.Template.ToGeneralIdRef()!,
            Name = formInstance.FormTemplateVersion.Template.Name,
            Definition = formInstance.FormTemplateVersion.FormDefinition,
            CustomFieldDefinitions = customFieldDefinitions,
            CustomFieldValues = await _customFieldHelper.CustomFieldValuesList(formInstance.FormFields, cancellationToken),
            Version = formInstance.FormTemplateVersion.Version,
            UpdatedVersion = currentVersion.Version
        };
        
        return result;
    }

    private readonly Dictionary<FormTemplateVersion, List<CustomFieldDefinition>> _getCustomFieldDefinitionsCache = [];

    private async Task<List<CustomFieldDefinition>> GetCustomFieldDefinitions(FormTemplateVersion formTemplateVersion, CancellationToken cancellationToken)
    {
        if (_getCustomFieldDefinitionsCache.TryGetValue(formTemplateVersion, out var definitions))
            return definitions;

        var fieldsAsGeneralIdRefs = await GetCustomFieldsFromDefinition(formTemplateVersion.FormDefinition);
        List<CustomFieldDefinition> customFieldDefinitions = await GetCustomFieldDefiniations(fieldsAsGeneralIdRefs, cancellationToken);
        _getCustomFieldDefinitionsCache.Add(formTemplateVersion, customFieldDefinitions);

        return customFieldDefinitions;
    }

    public async Task<List<ReadFormInstance>> GetFormInstanceAsync(IEnumerable<GeneralIdRef> generalIdRefs, CancellationToken cancellationToken)
    {
        var results = new List<ReadFormInstance>();
        var formInstances = await _formRepository.GetFormInstances(generalIdRefs, false, cancellationToken);
        foreach (var formInstance in formInstances)
        {
            var currentVersion = await GetCurrentVersionFromTemplate(formInstance.FormTemplateVersion.Template, cancellationToken);
            var fieldsAsGeneralIdRefs = await GetCustomFieldsFromDefinition(formInstance.FormTemplateVersion.FormDefinition);
            var customFieldDefinitions = await GetCustomFieldDefiniations(fieldsAsGeneralIdRefs, cancellationToken);

            var result = new ReadFormInstance
            {
                Id = formInstance.Id,
                Guid = formInstance.Guid,
                TemplateId = formInstance.FormTemplateVersion.Template.ToGeneralIdRef()!,
                Name = formInstance.FormTemplateVersion.Template.Name,
                Definition = formInstance.FormTemplateVersion.FormDefinition,
                CustomFieldDefinitions = customFieldDefinitions,
                CustomFieldValues = await _customFieldHelper.CustomFieldValuesList(formInstance.FormFields, cancellationToken),
                Version = formInstance.FormTemplateVersion.Version,
                UpdatedVersion = currentVersion.Version
            };

            results.Add(result);
        }

        return results;
    }

    public async Task<GeneralIdRef> CreateFormInstanceAsync(
        AuditUserDetails auditUserDetails,
        CreateFormInstance createFormInstance,
        CancellationToken cancellationToken
    )
    {
        var template = await _formRepository.GetTemplateAsync(createFormInstance.TemplateId, cancellationToken ) ?? throw new NotFoundException("Unable to find template");
        var version = GetSpecificVersionFromTemplate(template, createFormInstance.Version) ?? throw new NotFoundException("Unable to find template version");
        var newFormInstance = new FormInstance
        {
            FormTemplateVersionId = version.Id,
            Guid = createFormInstance.Guid ?? Guid.NewGuid()
        };

        await _formRepository.TransactionAsync(async () =>
        {
            await _formRepository.AddFormInstance(auditUserDetails, newFormInstance, cancellationToken);

            var validatedFields = await CalculateValidatedCustomFieldValues(auditUserDetails, createFormInstance.CustomFieldValues, version, cancellationToken);
            var customFieldValues = validatedFields.Select(validatedField => new FormFieldInstance
                {
                    FormInstanceId = newFormInstance.Id,
                    CustomFieldId = validatedField.CustomFieldId,
                    Index = validatedField.Index,
                    Value = validatedField.Value,
                    DisplayValue = validatedField.DisplayValue!
                })
                .ToList();


            await _formRepository.AddFormInstanceValues(auditUserDetails, customFieldValues, cancellationToken);
            
        });


        return new GeneralIdRef
        {
            Id = newFormInstance.Id,
            Guid = newFormInstance.Guid,
        };
    }


    public struct FormInstanceDict
    {
        public FormInstance FormInstance { get; set; }
        public CreateFormInstance CreateFormInstance { get; set; }
    }

    public async Task CreateFormInstancesAsync(AuditUserDetails auditUserDetails,
        IEnumerable<CreateFormInstance> createFormInstances,
        CancellationToken cancellationToken)
    {
        var formInstances = new List<FormInstanceDict>();
        var templateVersionLookup = new Dictionary<GeneralIdRef, FormTemplateVersion>();

        foreach (var createFormInstance in createFormInstances)
        {
            if (!templateVersionLookup.TryGetValue(createFormInstance.TemplateId, out var version))
            {
                var template =
                    await _formRepository.GetTemplateAsync(createFormInstance.TemplateId, cancellationToken) ??
                    throw new NotFoundException("Unable to find template");

                version = GetSpecificVersionFromTemplate(template, createFormInstance.Version) ??
                          throw new NotFoundException("Unable to find template version");
                templateVersionLookup.Add(createFormInstance.TemplateId, version);
            }

            var newFormInstance = new FormInstanceDict
            {
                FormInstance = new FormInstance
                {
                    FormTemplateVersion = version,
                    FormTemplateVersionId = version.Id,
                    Guid = createFormInstance.Guid ?? Guid.NewGuid()
                },
                CreateFormInstance = createFormInstance
            };

            formInstances.Add(newFormInstance);
        }

        await _formRepository.TransactionAsync(async () =>
        {
            await _formRepository.AddFormInstances(auditUserDetails, formInstances.Select(x => x.FormInstance),
                cancellationToken);

            var customFieldValues = new List<FormFieldInstance>();
            foreach (var formInstance in formInstances)
            {
                var validatedFields = await CalculateValidatedCustomFieldValues(auditUserDetails,
                    formInstance.CreateFormInstance.CustomFieldValues,
                    formInstance.FormInstance.FormTemplateVersion, cancellationToken);

                customFieldValues.AddRange(
                    validatedFields.Select(validatedField => new FormFieldInstance
                    {
                        FormInstanceId = formInstance.FormInstance.Id,
                        CustomFieldId = validatedField.CustomFieldId,
                        Index = validatedField.Index,
                        Value = validatedField.Value,
                        DisplayValue = validatedField.DisplayValue!
                    })
                );
            }

            await _formRepository.AddFormInstanceValues(auditUserDetails, customFieldValues, cancellationToken);
        });
    }

    private FormTemplateVersion? GetSpecificVersionFromTemplate(FormTemplate template, long version)
    {
        return _formRepository.GetAllFormVersions(template.Id).SingleOrDefault(x => x.Version == version);
    }

    private readonly Dictionary<FormTemplateVersion, List<CustomField>> _formVersionCustomFields = [];

    private async Task<IEnumerable<ValidatedCustomFieldValue>> CalculateValidatedCustomFieldValues(
        AuditUserDetails auditUserDetails,
        List<CustomFieldValues> customFieldValues,
        FormTemplateVersion version,
        CancellationToken cancellationToken
    )
    {
        if (!_formVersionCustomFields.TryGetValue(version, out var customFields))
        {
            var fieldsAsGeneralIdRefs = await GetCustomFieldsFromDefinition(version.FormDefinition);
            customFields = await GetCustomFields(fieldsAsGeneralIdRefs, cancellationToken);

            _formVersionCustomFields.Add(version,customFields);
        }

        var inputFieldValues = customFieldValues.ToCustomFieldsValues();
        var validatedFields =
            await _customFieldValidator.ValidateFields(auditUserDetails, inputFieldValues, customFields, cancellationToken);
        return validatedFields;
    }

    public async Task EditFormInstanceAsync(
        AuditUserDetails auditUserDetails,
        EditFormInstance editFormInstance,
        CancellationToken cancellationToken
    )
    {
        await _formRepository.TransactionAsync(async () =>
        {
            var formInstance =
                await _formRepository.GetFormInstance(editFormInstance.FormInstanceId, true, cancellationToken) ?? throw new NotFoundException("FormInstance not found");
            if (formInstance.FormTemplateVersion.Version != editFormInstance.Version)
            {
                var formTemplate =
                    await _formRepository.GetTemplateAsync(editFormInstance.TemplateId, cancellationToken) ?? throw new NotFoundException("Template not found");
                var newVersion = formTemplate.Versions.SingleOrDefault(x => x.Version == editFormInstance.Version) ?? throw new NotFoundException("Version not found");
                formInstance.FormTemplateVersion = newVersion;
                formInstance.FormTemplateVersionId = newVersion.Id;
            }

            var validatedFields = await CalculateValidatedCustomFieldValues(auditUserDetails, editFormInstance.CustomFieldValues, formInstance.FormTemplateVersion, cancellationToken);
            var customFieldValues = validatedFields.Select(validatedField => new FormFieldInstance
                {
                    FormInstance = formInstance,
                    FormInstanceId = formInstance.Id,
                    CustomField = validatedField.CustomField,
                    CustomFieldId = validatedField.CustomFieldId,
                    Index = validatedField.Index,
                    Value = validatedField.Value,
                    DisplayValue = validatedField.DisplayValue!
                })
                .ToList();

            var formFieldValueKeyComparer = new FormFieldValueKeyComparer();
            var delta = Delta<FormFieldInstance>.CalculateDelta(customFieldValues, formInstance.FormFields, formFieldValueKeyComparer, cancellationToken);

            Parallel.ForEach(delta.Matches, match =>
            {
                match.Original.DisplayValue = match.Updated.DisplayValue;
                match.Original.Value = match.Updated.Value;
            }); 
            
            await _formRepository.SaveCustomFieldValues(auditUserDetails, delta, cancellationToken);
        });
    }

    public Task EditFormInstanceAsync(AuditUserDetails auditUserDetails, IEnumerable<EditFormInstance> editFormInstances,
        CancellationToken cancellationToken)
    {
        throw new NotImplementedException();
    }
}