using e_suite.API.Common;
using e_suite.API.Common.models;
using e_suite.API.Common.repository;
using e_suite.Database.Core.Extensions;
using e_suite.Database.Core.Models;
using e_suite.Service.EFlowSync.EflowAPI;
using e_suite.Service.EFlowSync.Extensions;
using e_suite.Utilities.Pagination;
using eSuite.Core.Miscellaneous;
using e_suite.Service.EFlowSync.Repository;

namespace e_suite.Service.EFlowSync;

public class EFlowSync : IEFlowSync
{
    private readonly IDomainManager _domainManager;
    private readonly IEFlowCategories _eFlowCategories;
    private readonly IFormRepository _formRepository;
    private readonly ISpecificationManagerRepository _specificationManagerRepository;
    private readonly IEFlowSyncRepository _eFlowSyncRepository;

    public EFlowSync(IDomainManager domainManager, IEFlowCategories eFlowCategories, IFormRepository formRepository, ISpecificationManagerRepository specificationManagerRepository, IEFlowSyncRepository eFlowSyncRepository)
    {
        _domainManager = domainManager;
        _eFlowCategories = eFlowCategories;
        _formRepository = formRepository;
        _specificationManagerRepository = specificationManagerRepository;
        _eFlowSyncRepository = eFlowSyncRepository;
    }

    private readonly Dictionary<object, string> _externalIdCache = new();

    public async Task SyncEFlowPrinterCategories(List<GeneralIdRef> fullMessageDomains)
    {
        //Load the list of domains that need to by sync'd
        var domains = (await _domainManager.GetDomainsAsync(new Paging { Page = 0 }, CancellationToken.None)).Data;

        foreach (var domain in domains)
        {
            if (fullMessageDomains.Count > 0)
            {
                if (!fullMessageDomains.Contains(domain.ToGeneralIdRef()!))
                    //This domain can be skipped as a sync wasn't requested.
                    continue;
            }

            if (string.IsNullOrWhiteSpace(domain.SunriseAppId) || string.IsNullOrWhiteSpace(domain.SunriseCategoryId))
                //Domain is not setup for sync with e-flow, so no point doing any more work.
                continue;
            
            //Get the category
            _eFlowCategories.ApiKey = domain.SunriseAppId;
            var eFlowPrinterCategory = await _eFlowCategories.GetCategories(domain.SunriseCategoryId);
            if (eFlowPrinterCategory == null)
                throw new NullReferenceException("Unable to find e-flow printer category");

            _externalIdCache.Clear();

            await AddMissingCategories(domain, eFlowPrinterCategory, CancellationToken.None);

            await PruneUnneededCategories(eFlowPrinterCategory);
        }
    }

    private async Task PruneUnneededCategories(CategoryInfo parentCategory)
    {
        var deleteList = new List<CategoryInfo>();

        //Now to prune the eFlowPrinterCategory structure
        foreach (var category in parentCategory.Children)
        {
            if (category.Deleted != null)
                continue;

            await PruneUnneededCategories(category);

            if (_externalIdCache.ContainsValue(category.Id))
                continue;

            if (category.Children.Count == 0)
            {
                deleteList.Add(category);
                await _eFlowCategories.DeleteCategory(category);
            }
        }

        foreach (var category in deleteList)
            parentCategory.Children.Remove(category);
    }

    private readonly Guid _domainGuid = new("45821c2d-9d6e-4c82-ac57-965755960422");

    private async Task AddMissingCategories(GetDomain domain, CategoryInfo eFlowPrinterCategory, CancellationToken cancellationToken)
    {
        //Get all the forms that have a formfield containing the domainGuid
        var domainCustomFieldGeneralIdRef = new GeneralIdRef { Guid = _domainGuid };

        var allFormsForDomain = await _formRepository.GetFormsContainingFieldValue(domainCustomFieldGeneralIdRef, domain.Guid.ToString(), cancellationToken);
        var formIds = allFormsForDomain.Select(x => x.Id);

        //Get all specifications that use the forms
        var specificationsForDomain = _specificationManagerRepository.GetSpecificationsFromForms(formIds).ToList();

        foreach (var specification in specificationsForDomain)
        {
            var organisation = specification.Site.Organisation;
            var currentOrganisationCategory = await EnsureCategoryExists(eFlowPrinterCategory, organisation, cancellationToken);

            var site = specification.Site;
            var currentSiteCategory = await EnsureCategoryExists(currentOrganisationCategory, site, cancellationToken);

            var currentPrintSpecCategory = await EnsureCategoryExists(currentSiteCategory, specification, cancellationToken);
        }
    }

    private async Task<CategoryInfo> EnsureCategoryExists(CategoryInfo parent, IName obj, CancellationToken cancellationToken)
    {
        var category = await FindOrAddCategory(parent, obj, cancellationToken);

        if (category.Deleted != null)
        {

            category.Deleted = null;
            await _eFlowCategories.RestoreCategory(category);
        }

        if (category.DisplayName != obj.Name)
        {
            category.DisplayName = obj.Name;
            await _eFlowCategories.UpdateCategory(category);
        }
        
        return category;
    }
    
    private async Task<CategoryInfo> FindOrAddCategory(CategoryInfo parent, IName obj, CancellationToken cancellationToken)
    {
        var type = obj.GetType();
        var entityName = type.FullName ?? type.Name;
        var primaryKeyJson = type.PrimaryKeyDictionary(obj).ToPrimaryKeyJson();

        if (!_externalIdCache.TryGetValue(obj, out var externalId))
        {
            externalId = await _eFlowSyncRepository.GetEFlowExternalId(entityName, primaryKeyJson);
            if (externalId != null)
            {
                _externalIdCache.Add(obj, externalId);
            }
        }

        foreach (var child in parent.Children)
        {
            if (externalId == null)
            {
                if (child.DisplayName == obj.Name)
                {
                    await _eFlowSyncRepository.AddEflowExternalId(entityName, primaryKeyJson, child.Id, cancellationToken);
                    _externalIdCache.Add(obj, child.Id);

                    return child;
                }
            }
            else
            {
                if (child.Id == externalId)
                {
                    return child;
                }
            }
        }

        //could not find the child, so we will need to add it here.
        var newCategory = new CategoryInfo
        {
            ParentId = parent.Id,
            Hostname = parent.Hostname,
            Id = Guid.NewGuid().ToString("N").Left(16),
            DisplayName = obj.Name
        };

        await _eFlowCategories.PostCategory(newCategory);
        parent.Children.Add(newCategory);
        _externalIdCache.Add(obj, newCategory.Id);

        return newCategory;
    }
}