using System.Text.Json;
using e_suite.API.Common.exceptions;
using e_suite.API.Common.models;
using e_suite.Database.Audit;
using e_suite.Database.Core.Extensions;
using e_suite.Service.SigmaImporter.Extensions;
using e_suite.Service.SigmaImporter.Repository;
using e_suite.Utilities.Pagination;
using eSuite.Core.Miscellaneous;
using Microsoft.Extensions.Logging;

namespace e_suite.Service.SigmaImporter.PrintSpecifications;

public partial class PrintSpecificationImporter
{
    private async Task ImportPrintSpecifications(AuditUserDetails auditUserDetails,
        GetFormTemplate specificationFormTemplate, List<GetDomain> eSuiteDomains)
    {
        var specificationFormTemplateGeneralIdRef = specificationFormTemplate.ToGeneralIdRef()!;

        var printSpecifications = (await _sigmaFtpuRepository.GetActivePrintSpecifications()).ToList() ??
                                  throw new NotFoundException("The Print Specifications not found from Sigma");
        var printSpecificationDomains = (await _sigmaFtpuRepository.GetActivePrintSpecificationDomains()).ToList() ??
                                        throw new NotFoundException("Unable to load the print specification domains from Sigma");

        await PrepareSiteDictionary(auditUserDetails, printSpecifications);
        await PrepareProcessDictionary(auditUserDetails, printSpecifications);

        var specifications =
            await _specificationManager.GetSpecifications(new Paging() { Page = 0 }, CancellationToken.None);

        var additionList = new List<SigmaPrintSpecification>();
        var editSpecificationList = new List<EditSpecification>();
        var editFormInstanceList = new List<EditFormInstance>();

        var batchSize = 100;

        while (printSpecifications.Count>0)
        {
            var batch = printSpecifications.Take(batchSize).ToArray();
            _logger.LogInformation("{DateTime} Processing specifications batch of {BatchSize}, {Total} left to process.", _clock.GetNow, batch.Length, printSpecifications.Count);
            printSpecifications.RemoveAll(x => batch.Contains(x));

            var sigmaIds = batch.Select(x => x.SigmaId).ToArray();
            var specs = specifications.Data.Where(x => x.SigmaId.HasValue & sigmaIds.Contains( x.SigmaId!.Value));

            var formInstances = await _formsManager.GetFormInstanceAsync(specs.Select(x => x.FormInstanceId), CancellationToken.None);


            foreach (var printSpecification in batch)
            {
                var specification = specifications.Data.SingleOrDefault(x => x.SigmaId == printSpecification.SigmaId);
                if (specification != null)
                {
                    var specificationHasChanges = false;

                    specificationHasChanges = specificationHasChanges | !specification.Name.Equals(printSpecification.Name, StringComparison.InvariantCulture);

                    //Todo figure out if there are any changes to the specification domains.

                    if (specificationHasChanges)
                    {
                        editSpecificationList.Add(new EditSpecification
                        {
                            Id = specification.ToGeneralIdRef()!,
                            Name = printSpecification.Name,
                            FormInstanceId = specification.FormInstanceId,
                            Site = specification.Site,
                            //Domains = GetIncludedDomainsFromSigma(printSpecification, printSpecificationDomains, eSuiteDomains)//Todo upgrade this to be present the updated list of domains instead of the current domains.
                        });
                    }

                    var formInstance = formInstances.SingleOrDefault(x => x.Id == specification.FormInstanceId.Id || x.Guid == specification.FormInstanceId.Guid);
                    //var formInstance = await _formsManager.GetFormInstanceAsync(specification.FormInstanceId, CancellationToken.None);

                    if (formInstance != null)
                    {
                        var editFormInstance = new EditFormInstance(formInstance);
                        var edited = false;

                        EditCustomFieldValue(editFormInstance, _barcodeCommentsGuid, printSpecification.Barcode, ref edited);
                        EditCustomFieldValue(editFormInstance, _bwrGuid, printSpecification.Bwr ?? 0, ref edited);
                        EditCustomFieldValue(editFormInstance, _colours, printSpecification.Colours, ref edited);
                        EditCustomFieldValue(editFormInstance, _digitalSupplyMethodGuid, TranslateSupplyMethod(printSpecification.SupplyMethod), ref edited);
                        EditCustomFieldValue(editFormInstance, _digitalSupplyCommentsGuid, printSpecification.DigitalSupply, ref edited);
                        EditCustomFieldValue(editFormInstance, _gmgProfileGuid, LookupGmgProfileGlossaryGuid(printSpecification.GmgFk), ref edited);
                        EditCustomFieldValue(editFormInstance, _maxDotGuid, printSpecification.Maxdot ?? 0, ref edited);
                        EditCustomFieldValue(editFormInstance, _minDotGuid, printSpecification.Mindot ?? 0, ref edited);
                        EditCustomFieldValue(editFormInstance, _maxInkGuid, printSpecification.MaxInk ?? 0, ref edited);
                        EditCustomFieldValue(editFormInstance, _minMagGuid, printSpecification.MinMag ?? 0, ref edited);
                        EditCustomFieldValue(editFormInstance, _minMultiInkFontGuid, printSpecification.Mmif ?? 0, ref edited);
                        EditCustomFieldValue(editFormInstance, _minMultiInkLineGuid, printSpecification.Mmil ?? 0, ref edited);
                        EditCustomFieldValue(editFormInstance, _minNegFontGuid, printSpecification.Mnf ?? 0, ref edited);
                        EditCustomFieldValue(editFormInstance, _minNegLineGuid, printSpecification.Mnl ?? 0, ref edited);
                        EditCustomFieldValue(editFormInstance, _minPosFontGuid, printSpecification.Mpf ?? 0, ref edited);
                        EditCustomFieldValue(editFormInstance, _minPosLineGuid, printSpecification.Mpl ?? 0, ref edited);
                        EditCustomFieldValue(editFormInstance, _notesGuid, printSpecification.Notes, ref edited);
                        EditCustomFieldValue(editFormInstance, _printProcessGuid, LookupProcessGlossaryItem(printSpecification.Process), ref edited);
                        EditCustomFieldValue(editFormInstance, _printProcessCommentsGuid, printSpecification.PrintProcess, ref edited);
                        EditCustomFieldValue(editFormInstance, _sequenceCGuid, printSpecification.SequenceC, ref edited);
                        EditCustomFieldValue(editFormInstance, _sequenceMGuid, printSpecification.SequenceM, ref edited);
                        EditCustomFieldValue(editFormInstance, _sequenceYGuid, printSpecification.SequenceY, ref edited);
                        EditCustomFieldValue(editFormInstance, _sequenceKGuid, printSpecification.SequenceK, ref edited);
                        EditCustomFieldValue(editFormInstance, _substrateCommentsGuid, printSpecification.Substrate, ref edited);
                        EditCustomFieldValue(editFormInstance, _techSpecCommentsGuid, printSpecification.TechSpec, ref edited);
                        EditCustomFieldValue(editFormInstance, _trapGuid, printSpecification.Trap ?? 0, ref edited);
                        EditCustomFieldValue(editFormInstance, _domainGuid, GetIncludedDomainsFromSigma(printSpecification, printSpecificationDomains, eSuiteDomains), ref edited);

                        if (edited)
                        {
                            editFormInstanceList.Add(editFormInstance);
                        }
                    }
                }
                else
                    additionList.Add(printSpecification);
            }

            if (editFormInstanceList.Count > 0)
            {
                foreach (var editFormInstance in editFormInstanceList)
                    await _formsManager.EditFormInstanceAsync(auditUserDetails, editFormInstance, CancellationToken.None);
            }

            if (editSpecificationList.Count > 0)
            {
                await _specificationManager.EditSpecification(auditUserDetails, editSpecificationList, false,
                    CancellationToken.None);
            }

            if (additionList.Count > 0)
            {
                await AddNewPrintSpecs(auditUserDetails, specificationFormTemplate, additionList,
                    specificationFormTemplateGeneralIdRef, printSpecificationDomains, eSuiteDomains);
            }

            editFormInstanceList.Clear();
            editSpecificationList.Clear();
            additionList.Clear();
        }
    }

    private static void EditCustomFieldValue(EditFormInstance editFormInstance, Guid fieldId, object? value, ref bool edited)
    {

        //todo check this will cope with an array of values.
        var fieldIdRef = fieldId.ToGeneralIdRef();

        foreach (var field in editFormInstance.CustomFieldValues)
        {
            if (field.Id.Equals(fieldIdRef))
            {
                if (value is IEnumerable<CustomFieldValue> enumerableCustomFieldValues)
                {
                    edited = true;
                    field.Values = [..enumerableCustomFieldValues];

                    return;
                }

                var firstValue = field.Values.First();
                if (value is CustomFieldValue customFieldValue)
                {
                    var valueAsGeneralIdRef = JsonSerializer.Deserialize<GeneralIdRef>(customFieldValue.Value.ToString()!)!;
                    if (!valueAsGeneralIdRef.Equals(firstValue.Value))
                    {
                        edited = true;
                        field.Values = [customFieldValue];
                        return; //As we've edited the field, we can shortcut out of the method here for performance.
                    }
                }
                else
                {
                    var newValue = ConvertValueToString( value );
                    if (!firstValue.Value.ToString()!.Equals(newValue))
                    {
                        edited = true;
                        field.Values =
                        [
                            new CustomFieldValue
                            {
                                DisplayValue = value?.ToString() ?? string.Empty,
                                Value = newValue
                            }
                        ];
                    }
                }

                //As the field has been located, there's no need to search for a second instance of the same field.
                return;
            }
        }

        if (value != null)
        {
            //If we get here, the field wasn't existing on the form, so will need to be added.
            var newFieldValue = AddCustomFieldValue(fieldId, value);
            editFormInstance.CustomFieldValues.Add(newFieldValue);
            edited = true;
        }
    }

    private static string ConvertValueToString(object? value)
    {
        return value?.ToString()?.ReplaceLineEndings("\n").Trim() ?? string.Empty;
    }

    private async Task AddNewPrintSpecs(AuditUserDetails auditUserDetails, GetFormTemplate specificationFormTemplate,
        List<SigmaPrintSpecification> additionList, GeneralIdRef specificationFormTemplateGeneralIdRef, List<PrintSpecDomain> printSpecDomains, List<GetDomain> eSuiteDomains)
    {
        try
        {
            var formInstanceDictionary =
                await PrepareFormInstanceDictionary(auditUserDetails, specificationFormTemplate, additionList, specificationFormTemplateGeneralIdRef, printSpecDomains, eSuiteDomains);

            var newSpecifications = formInstanceDictionary.Select(x =>
                new CreateSpecification
                {
                    Name = x.Key.Name,
                    FormInstanceId = x.Value.Guid!.Value.ToGeneralIdRef(),
                    Site = siteGeneralIdRefs[x.Key.SiteId],
                    SigmaId = x.Key.SigmaId,
                    //Domains = GetIncludedDomainsFromSigma(x.Key, printSpecDomains, eSuiteDomains)
                }
            ).ToArray();

            await _specificationManager.CreateSpecification(auditUserDetails, newSpecifications, false,
                CancellationToken.None);
        }
        catch (Exception ex)
        {
            _logger.LogError("AddNewPrintSpecs failed: {message}", ex.Message);
            throw;
        }
    }

    private List<CustomFieldValue> GetIncludedDomainsFromSigma(SigmaPrintSpecification sigmaPrintSpecification, List<PrintSpecDomain> printSpecDomains, List<GetDomain> eSuiteDomains)
    {
        var includedDomains = new List<CustomFieldValue>();

        foreach (var printSpecDomain in printSpecDomains.Where(x => x.PrintSpecId == sigmaPrintSpecification.SigmaId))
        {
            var domain = FindDomainBySigmaId(eSuiteDomains, printSpecDomain.ClientId);
            if (domain != null)
                includedDomains.Add( new CustomFieldValue
                {
                    DisplayValue = domain.Name,
                    Value = domain.ToGeneralIdRef().ToJson()
                }   );
        }

        return includedDomains;
    }

    private GetDomain? FindDomainBySigmaId(List<GetDomain> eSuiteDomains, long clientId)
    {
        foreach (var domain in eSuiteDomains)
        {
            if (domain.SigmaId == clientId)
                return domain;
        }

        return null;
    }

    private async Task<Dictionary<SigmaPrintSpecification, CreateFormInstance>> PrepareFormInstanceDictionary(AuditUserDetails auditUserDetails,
        GetFormTemplate specificationFormTemplate, List<SigmaPrintSpecification> printSpecifications,
        GeneralIdRef specificationFormTemplateGeneralIdRef, List<PrintSpecDomain> printSpecDomains, List<GetDomain> eSuiteDomains)
    {
        var formInstanceDictionary = new Dictionary<SigmaPrintSpecification, CreateFormInstance>();

        foreach (var printSpecification in printSpecifications)
        {
            formInstanceDictionary.Add(printSpecification, AddFormInstanceToDictionary(specificationFormTemplate, specificationFormTemplateGeneralIdRef, printSpecification, printSpecDomains, eSuiteDomains));
        }

        await _formsManager.CreateFormInstancesAsync(auditUserDetails, formInstanceDictionary.Values, CancellationToken.None);

        return formInstanceDictionary;
    }

    private CreateFormInstance AddFormInstanceToDictionary(GetFormTemplate specificationFormTemplate,
        GeneralIdRef specificationFormTemplateGeneralIdRef, SigmaPrintSpecification printSpecification, List<PrintSpecDomain> printSpecDomains, List<GetDomain> eSuiteDomains)
    {
        var createFormInstance = new CreateFormInstance
        {
            TemplateId = specificationFormTemplateGeneralIdRef,
            Version = specificationFormTemplate.Version,
            CustomFieldValues =
            [
                AddCustomFieldValue(_barcodeCommentsGuid, printSpecification.Barcode),
                AddCustomFieldValue(_bwrGuid, printSpecification.Bwr ?? 0),
                AddCustomFieldValue(_colours, printSpecification.Colours),
                AddCustomFieldValue(_digitalSupplyMethodGuid,TranslateSupplyMethod(printSpecification.SupplyMethod)),
                AddCustomFieldValue(_digitalSupplyCommentsGuid, printSpecification.DigitalSupply),
                AddCustomFieldValue(_gmgProfileGuid, LookupGmgProfileGlossaryGuid(printSpecification.GmgFk)),
                AddCustomFieldValue(_maxDotGuid, printSpecification.Maxdot ?? 0),
                AddCustomFieldValue(_minDotGuid, printSpecification.Mindot ?? 0),
                AddCustomFieldValue(_maxInkGuid, printSpecification.MaxInk ?? 0),
                AddCustomFieldValue(_minMagGuid, printSpecification.MinMag ?? 0),
                AddCustomFieldValue(_minMultiInkFontGuid, printSpecification.Mmif ?? 0),
                AddCustomFieldValue(_minMultiInkLineGuid, printSpecification.Mmil ?? 0),
                AddCustomFieldValue(_minNegFontGuid, printSpecification.Mnf ?? 0),
                AddCustomFieldValue(_minNegLineGuid, printSpecification.Mnl ?? 0),
                AddCustomFieldValue(_minPosFontGuid, printSpecification.Mpf ?? 0),
                AddCustomFieldValue(_minPosLineGuid, printSpecification.Mpl ?? 0),
                AddCustomFieldValue(_notesGuid, printSpecification.Notes),
                AddCustomFieldValue(_printProcessGuid,LookupProcessGlossaryItem(printSpecification.Process)),
                AddCustomFieldValue(_printProcessCommentsGuid, printSpecification.PrintProcess),
                AddCustomFieldValue(_sequenceCGuid, printSpecification.SequenceC),
                AddCustomFieldValue(_sequenceMGuid, printSpecification.SequenceM),
                AddCustomFieldValue(_sequenceYGuid, printSpecification.SequenceY),
                AddCustomFieldValue(_sequenceKGuid, printSpecification.SequenceK),
                AddCustomFieldValue(_substrateCommentsGuid, printSpecification.Substrate),
                AddCustomFieldValue(_techSpecCommentsGuid, printSpecification.TechSpec),
                AddCustomFieldValue(_trapGuid, printSpecification.Trap ?? 0),
                AddCustomFieldValue(_domainGuid, GetIncludedDomainsFromSigma(printSpecification, printSpecDomains, eSuiteDomains))
            ],
            Guid = Guid.NewGuid()
        };

        return createFormInstance;
    }

    private readonly Dictionary<long, GeneralIdRef> siteGeneralIdRefs = [];

    private async Task PrepareSiteDictionary(AuditUserDetails auditUserDetails, List<SigmaPrintSpecification> printSpecifications)
    {
        siteGeneralIdRefs.Clear();
        await LoadExistingSites();
        await CreateAndInsertMissingSites(auditUserDetails, printSpecifications);
    }

    private async Task CreateAndInsertMissingSites(AuditUserDetails auditUserDetails, List<SigmaPrintSpecification> printSpecifications)
    {
        var newSites = new List<CreateSite>();
        foreach (var printSpecification in printSpecifications)
        {
            if (siteGeneralIdRefs.ContainsKey(printSpecification.SiteId))
            {
                continue;
            }
            
            var newSite = new CreateSite
            {
                OrganisationId = _sigmaOrganisationGeneralRefId,
                Name = printSpecification.PrinterName,
                Address = printSpecification.PrinterAddress,
                SigmaId = printSpecification.SiteId,
                Guid = Guid.NewGuid()
            };

            var siteGeneralIdRef = newSite.Guid.Value.ToGeneralIdRef();
            siteGeneralIdRefs.Add(newSite.SigmaId.Value, siteGeneralIdRef);
            newSites.Add(newSite);
        }
        await _siteManager.CreateSites(auditUserDetails, newSites, false, CancellationToken.None);
    }

    private async Task LoadExistingSites()
    {
        var sigmaOrganisation= await _organisationsManager.GetOrganisation(SigmaOrganisationGuid.ToGeneralIdRef(), CancellationToken.None);
        var existingSites = await _siteManager.GetSites(new Paging
        {
            Page=0
        }, CancellationToken.None);

        foreach (var existingSite in existingSites.Data.Where( x => x.SigmaId != null))
        {
            siteGeneralIdRefs.Add(existingSite.SigmaId!.Value, existingSite.ToGeneralIdRef()!);
        }
    }

    private CustomFieldValue? LookupGmgProfileGlossaryGuid(int printSpecificationGmgFk)
    {
        try
        {
            return _gmgProfileDictionary[printSpecificationGmgFk];
        }
        catch (Exception)
        {
            return null;
        }
    }
    
    private CustomFieldValue? LookupProcessGlossaryItem(string printSpecificationProcess)
    {
        try
        {
            return _printProcessDictionary[printSpecificationProcess.ToLowerInvariant().Trim()];
        }
        catch (Exception)
        {
            return null;
        }
    }

    private  CustomFieldValue? TranslateSupplyMethod(string printSpecificationSupplyMethod)
    {
        try
        {
            return _supplyMethods[printSpecificationSupplyMethod.ToLower()];
        }
        catch (Exception)
        {
            return null;
        }
    }

    private static CustomFieldValues AddCustomFieldValue(Guid guid, object? value)
    {
        if (value is IEnumerable<CustomFieldValue> enumerableCustomFieldValues)
        {
            var values = new CustomFieldValues
            {
                Id = guid.ToGeneralIdRef(),
            };
            values.Values.AddRange(enumerableCustomFieldValues);

            return values;
        }

        if (value is CustomFieldValue customFieldValue)
        {
            return new CustomFieldValues
            {
                Id = guid.ToGeneralIdRef(),
                Values =
                [
                    customFieldValue
                ]
            };
        }

        return new CustomFieldValues
        {
            Id = guid.ToGeneralIdRef(),
            Values =
            [
                new CustomFieldValue
                {
                    DisplayValue = value?.ToString() ?? string.Empty,
                    Value = ConvertValueToString( value )
                }
            ]
        };
    }
}
