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.Helpers;
using e_suite.Database.Core.Tables.Glossaries;
using eSuite.Core.Miscellaneous;
using e_suite.Database.Core.Tables.CustomFields;

namespace e_suite.Modules.GlossariesManager;

public class GlossariesManager : IGlossariesManager
{
    private readonly IGlossariesManagerRepository _glossariesManagerRepository;
    private readonly ICustomFieldValidator _customFieldValidator;
    private readonly ICustomFieldHelper _customFieldHelper;

    public GlossariesManager(IGlossariesManagerRepository glossariesManagerRepository, ICustomFieldValidator customFieldValidator, ICustomFieldHelper customFieldHelper)
    {
        _glossariesManagerRepository = glossariesManagerRepository;
        _customFieldValidator = customFieldValidator;
        _customFieldHelper = customFieldHelper;
    }

    public async Task<GlossaryItem?> GetGlossaryItem(AuditUserDetails auditUserDetails, GeneralIdRef generalIdRef, CancellationToken cancellationToken)
    { 
        var glossary = await _glossariesManagerRepository.FindGlossary(generalIdRef, cancellationToken) ??
            throw new NotFoundException("Glossary not found");

        await LoadParentHierarchy(glossary, cancellationToken);

        var glossaryItem = await GlossaryItemHelper.TranslateToGlossaryItem(glossary, _customFieldHelper, cancellationToken);
        if (glossaryItem == null)
            return glossaryItem;

        var children = await _glossariesManagerRepository.FindGlossaryChildren(glossaryItem.Id, cancellationToken);

        var childGlossaryItems = new List<GlossaryItem>();
        foreach (var child in children)
        {
            if (!child.Deleted)
            {
                childGlossaryItems.Add((await GlossaryItemHelper.TranslateToGlossaryItem(child, _customFieldHelper, cancellationToken))!);
            }
        }

        glossaryItem.Children = childGlossaryItems;

        return glossaryItem;
    }

    private async Task LoadParentHierarchy(Glossary glossary, CancellationToken cancellationToken)
    {
        var item = glossary;
        // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
        while (item != null)
        {
            item.Parent ??= (await _glossariesManagerRepository.FindGlossary(new GeneralIdRef { Id = item.ParentId }, cancellationToken))!;

            item = item.Parent;
        }
    }

    public async Task AddGlossaryItem(AuditUserDetails auditUserDetails, NewGlossaryItem glossaryItem, CancellationToken cancellationToken)
    {
        await _glossariesManagerRepository.TransactionAsync(async () =>
        {
            await DoAddGlossaryItem(auditUserDetails, [glossaryItem], cancellationToken);
        });
    }

    public async Task AddGlossaryItems(AuditUserDetails auditUserDetails, IEnumerable<NewGlossaryItem> glossaryItems, CancellationToken cancellationToken)
    {
        await _glossariesManagerRepository.TransactionAsync(async () =>
        {
            await DoAddGlossaryItem(auditUserDetails, glossaryItems, cancellationToken);
        });
    }

    private async Task DoAddGlossaryItem(AuditUserDetails auditUserDetails, IEnumerable<NewGlossaryItem> glossaryItems,
        CancellationToken cancellationToken)
    {
        var itemsToWorkOn = new List<(NewGlossaryItem, Glossary)>();

        foreach (var glossaryItem in glossaryItems)
        {
            var parent =
                await _glossariesManagerRepository.GetParentGlossary(glossaryItem.Parent, cancellationToken);

            if (glossaryItem.Parent != null && parent == null)
                throw new NullReferenceException("parent could not be found");

            var duplicate = glossaryItem.Guid == null
                ? null
                : await _glossariesManagerRepository.FindGlossary(new GeneralIdRef { Guid = glossaryItem.Guid },
                    cancellationToken);

            if (duplicate != null)
                throw new ArgumentException($"Guid {glossaryItem.Guid} is already used");

            var glossary = new Glossary
            {
                Deleted = false,
                Guid = glossaryItem.Guid ?? Guid.NewGuid(),
                Name = glossaryItem.Name,
                ParentId = parent?.Id,
                Parent = parent!
            };

            itemsToWorkOn.Add(new ValueTuple<NewGlossaryItem, Glossary>
            {
                Item1 = glossaryItem,
                Item2 = glossary
            });
        }

        await _glossariesManagerRepository.AddGlossaryItems(auditUserDetails, itemsToWorkOn.Select(x => x.Item2),
            cancellationToken);
        await UpdateChildCustomFieldDefinitions(auditUserDetails,
            itemsToWorkOn.Select(x =>
                new ValueTuple<IEnumerable<IGeneralIdRef>, Glossary>
                {
                    Item1 = x.Item1.ChildCustomFieldDefinition,
                    Item2 = x.Item2
                }
            ),
            cancellationToken);
        await UpdateCustomFieldValues(auditUserDetails, itemsToWorkOn.Select(x =>
            new ValueTuple<EditableGlossaryItem, Glossary>
            {
                Item1 = x.Item1,
                Item2 = x.Item2
            }
        ), cancellationToken);
    }


    public async Task UpdateGlossaryItem(AuditUserDetails auditUserDetails, EditGlossaryItem editGlossaryItem, CancellationToken cancellationToken)
    {
        await _glossariesManagerRepository.TransactionAsync(async () =>
        {
            var glossaryItem = await _glossariesManagerRepository.FindGlossary(editGlossaryItem.Id, cancellationToken) ??
                               throw new ArgumentNullException(nameof(editGlossaryItem),"Glossary item not found");

            glossaryItem.Name = editGlossaryItem.Name;

            await UpdateChildCustomFieldDefinitions(auditUserDetails, glossaryItem, editGlossaryItem.ChildCustomFieldDefinition, cancellationToken);
            await UpdateCustomFieldValues(auditUserDetails, glossaryItem, editGlossaryItem, cancellationToken);

            await _glossariesManagerRepository.EditGlossaryItem(auditUserDetails, glossaryItem, cancellationToken);
        });
    }

    private async Task UpdateCustomFieldValues(AuditUserDetails auditUserDetails, IEnumerable<(EditableGlossaryItem, Glossary)> itemsToWorkOn,
        CancellationToken cancellationToken)
    {
        List<Delta<GlossaryCustomFieldValue>> deltas = [];

        foreach (var item in itemsToWorkOn)
        {
            if (item.Item2.Parent == null && item.Item1.CustomFieldValues.Count > 0)
                throw new NullReferenceException("Root items cannot have custom field values.");

            var updatedcustomFieldValues =
                await CompileGlossaryCustomFieldValues(auditUserDetails, item.Item1, item.Item2.Parent, item.Item2,
                    cancellationToken);

            var currentCustomFieldValues = await _glossariesManagerRepository.GetGlossaryCustomValues(item.Item2, cancellationToken);

            var glossaryCustomFieldValueKeyComparer = new GlossaryCustomFieldValueKeyComparer();
            Delta<GlossaryCustomFieldValue> delta = Delta<GlossaryCustomFieldValue>.CalculateDelta(updatedcustomFieldValues, currentCustomFieldValues, glossaryCustomFieldValueKeyComparer, cancellationToken);

            Parallel.ForEach(delta.Matches, match =>
            {
                match.Original.DisplayValue = match.Updated.DisplayValue;
                match.Original.Value = match.Updated.Value;
            });

            deltas.Add(delta);
        }
        
        await _glossariesManagerRepository.SaveCustomFieldValues(auditUserDetails, deltas, cancellationToken);
    }

    private async Task UpdateCustomFieldValues(AuditUserDetails auditUserDetails, Glossary glossaryItem,
        EditableGlossaryItem glossary, CancellationToken cancellationToken)
    {
        var items = new List<(EditableGlossaryItem, Glossary)>
        {
            new()
            {
                Item1 = glossary,
                Item2 = glossaryItem
            }
        };

        await UpdateCustomFieldValues(auditUserDetails, items, cancellationToken);

    }

    private async Task UpdateChildCustomFieldDefinitions(AuditUserDetails auditUserDetails, Glossary glossary,
        IEnumerable<IGeneralIdRef> customFieldIds, CancellationToken cancellationToken)
    {
        var itemsToWorkOn = new List<(IEnumerable<IGeneralIdRef>, Glossary)>
        {
            new()
            {
                Item1 = customFieldIds,
                Item2 = glossary
            }
        };

        await UpdateChildCustomFieldDefinitions(auditUserDetails,
            itemsToWorkOn, cancellationToken);
    }

    private async Task UpdateChildCustomFieldDefinitions(AuditUserDetails auditUserDetails,
        IEnumerable<(IEnumerable<IGeneralIdRef>, Glossary)> itemsToWorkOn, CancellationToken cancellationToken)
    {
        var removalList = new List<GlossaryCustomField>();
        var additionList = new List<GlossaryCustomField>();

        foreach (var item in itemsToWorkOn)
        {
            List<CustomField> customFieldDefinitions;
            try
            {
                customFieldDefinitions =
                    await _glossariesManagerRepository.GetCustomFieldDefinitions(item.Item1, cancellationToken);
            }
            catch (NullReferenceException ex)
            {
                throw new NotFoundException("Unable to find custom field", ex);
            }

            foreach (var customField in _glossariesManagerRepository.GetGlossaryCustomFields(item.Item2.Id))
            {
                var found = false;
                foreach (var a in customFieldDefinitions)
                {
                    if (a.Id == customField.Id)
                    {
                        found = true;
                        break;
                    }
                }

                if (!found)
                    removalList.Add(customField);
            }

            foreach (var customFieldDefinition in customFieldDefinitions)
            {
                cancellationToken.ThrowIfCancellationRequested();
                var glossaryCustomField = new GlossaryCustomField
                {
                    GlossaryId = item.Item2.Id,
                    CustomFieldId = customFieldDefinition.Id
                };

                additionList.Add(glossaryCustomField);
            }
        }

        await _glossariesManagerRepository.SaveChildCustomFieldDefinitions(auditUserDetails, removalList, additionList, cancellationToken);
    }
    
    private async Task<List<GlossaryCustomFieldValue>> CompileGlossaryCustomFieldValues(AuditUserDetails auditUserDetails, EditableGlossaryItem glossaryItem, Glossary? parent, Glossary glossary, CancellationToken cancellationToken)
    {
        if (parent == null)
            return [];
        
        var parentCustomFieldDefinitions = parent.CustomFieldDefinitions.Select(x => x.CustomField).ToList();

        var inputFieldValues = glossaryItem.CustomFieldValues.ToCustomFieldsValues();

        var validatedFields = await _customFieldValidator.ValidateFields(auditUserDetails, inputFieldValues, parentCustomFieldDefinitions, cancellationToken);

        return validatedFields.Select(validatedField => new GlossaryCustomFieldValue
        {
            GlossaryId = glossary.Id,
            CustomFieldId = validatedField.CustomFieldId,
            Index = validatedField.Index,
            Value = validatedField.Value,
            DisplayValue = validatedField.DisplayValue!
        })
            .ToList();
    }


    public async Task DeleteGlossaryItem(AuditUserDetails auditUserDetails, GeneralIdRef generalIdRef, CancellationToken cancellationToken)
    {
        await _glossariesManagerRepository.TransactionAsync(async () =>
        {
            var glossaryItem = await _glossariesManagerRepository.FindGlossary(generalIdRef, cancellationToken) ??
                               throw new NotFoundException("Unable to find glossary item");

            if (glossaryItem!.Deleted)
                throw new InvalidOperationException("Already deleted");

            await SetItemAndChildrenAsDeleted(glossaryItem, cancellationToken);

            await _glossariesManagerRepository.EditGlossaryItem(auditUserDetails, glossaryItem, cancellationToken);
        });
    }

    private async Task SetItemAndChildrenAsDeleted(Glossary glossaryItem, CancellationToken cancellationToken)
    {
        var children = await _glossariesManagerRepository.FindGlossaryChildren(glossaryItem.Id, cancellationToken);
        foreach (var child in children)
        {
            await SetItemAndChildrenAsDeleted(child, cancellationToken);
        }

        glossaryItem.Deleted = true;
    }
}