
using e_suite.Database.Audit;
using e_suite.Database.Core.Tables.Printer;
using e_suite.Utilities.Pagination;
using eSuite.Core.Miscellaneous;
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.Core.Extensions;
using e_suite.Database.Core.Models;
using e_suite.Messaging.Common;

namespace e_suite.Modules.SpecificationManager;

public class SpecificationManager : ISpecificationManager
{
    private readonly ISpecificationManagerRepository _specificationManagerRepository;
    private readonly IGlossariesManagerRepository _glossariesManagerRepository;
    private readonly IFormRepository _formRepository;
    private readonly ISiteManagerRepository _siteManagerRepository;
    private readonly IEFlowSyncMessageSender _eFlowSyncMessageSender;

#pragma warning disable IDE0290
    public SpecificationManager(ISpecificationManagerRepository specificationManagerRepository, IGlossariesManagerRepository glossariesManagerRepository, IFormRepository formRepository, ISiteManagerRepository siteManagerRepository, IEFlowSyncMessageSender eFlowSyncMessageSender)
    {
        _specificationManagerRepository = specificationManagerRepository;
        _glossariesManagerRepository = glossariesManagerRepository;
        _formRepository = formRepository;
        _siteManagerRepository = siteManagerRepository;
        _eFlowSyncMessageSender = eFlowSyncMessageSender;
    }
#pragma warning restore IDE0290

    private static void Assert<T>(T? item, string message)
    {
        if (typeof(T).IsAssignableTo(typeof(ISoftDeletable)))
        {
            if (item is not ISoftDeletable deletable || deletable.Deleted)
                throw new NotFoundException(message);
        }
        else
        {
            if (item == null)
                throw new NotFoundException(message);
        }
    }

    public async Task<GeneralIdRef?> GetTemplateForPrintSpec(GeneralIdRef generalIdRef, CancellationToken cancellationToken)
    {
        var printSpecificationTemplateGuid = new Guid("{8D910089-3079-4A29-ABAD-8DDF82DB6DBB}");

        var printSpecification = await _glossariesManagerRepository.FindGlossary(generalIdRef, cancellationToken);
        Assert(printSpecification, "Template glossary item does not exist");
        var customFieldValue = printSpecification!.CustomFieldValues.SingleOrDefault(x => x.CustomField.Guid == printSpecificationTemplateGuid);
        Assert(customFieldValue, "Glossary does not contain template reference");
        var specificationFormGuid = customFieldValue!.Value;
        var specificationFormGeneralIdRef = new GeneralIdRef { Guid = new Guid(specificationFormGuid) };
        var result = await _formRepository.GetTemplateAsync(specificationFormGeneralIdRef,cancellationToken);
        Assert(result, "Template does not exist");
        return result.ToGeneralIdRef();
    }

    public async Task<ReadSpecification?> GetSpecification(GeneralIdRef generalIdRef, CancellationToken cancellationToken)
    {
        var specification = await _specificationManagerRepository.GetSpecification(generalIdRef, cancellationToken);
        Assert(specification, "Specification not found");
        return await MapSpecification(specification!, cancellationToken);
    }

    private readonly Dictionary<GeneralIdRef, Site> _siteDictionary = [];

    public async Task CreateSpecification(
        AuditUserDetails auditUserDetails,
        CreateSpecification create,
        bool triggerEFlowSync,
        CancellationToken cancellationToken
    )
    {
        await CreateSpecification(auditUserDetails, [create], triggerEFlowSync, cancellationToken);
    }

    public async Task CreateSpecification(
        AuditUserDetails auditUserDetails,
        IEnumerable<CreateSpecification> items,
        bool triggerEFlowSync,
        CancellationToken cancellationToken
    )
    {
        var newSpecifications = new List<Specification>();
        foreach (var create in items)
        {
            if (!_siteDictionary.TryGetValue(create.Site, out var site))
            {
                site = await _siteManagerRepository.GetSite(create.Site, cancellationToken) ??
                       throw new NotFoundException("Site not found");
                _siteDictionary.Add(create.Site, site);
            }

            var formInstance = await _formRepository.GetFormInstance(create.FormInstanceId, false, cancellationToken) ??
                               throw new NotFoundException("Form instance not found");

            var newSpecification = new Specification
            {
                Guid = create.Guid ?? Guid.NewGuid(),
                Name = create.Name,
                FormInstanceId = formInstance.Id,
                Site = site,
                SiteId = site.Id,
                SigmaId = create.SigmaId
            };

            newSpecifications.Add(newSpecification);
        }

        await _specificationManagerRepository.TransactionAsync(async () =>
        {
            await _specificationManagerRepository.CreateSpecification(auditUserDetails, newSpecifications, cancellationToken);
        });

        if (triggerEFlowSync)
            _eFlowSyncMessageSender.SyncEFlowPrinterCategories();
    }

    public async Task<IPaginatedData<ReadSpecification>> GetSpecifications(Paging paging, CancellationToken cancellationToken)
    {
        var specifications = _specificationManagerRepository.GetSpecifications();

        var paginatedData = await PaginatedData.Paginate(specifications, paging,
            KeySelector, cancellationToken);

        var dataPage = new List<ReadSpecification>();
        foreach (var data in paginatedData.Data)
            dataPage.Add(await MapSpecification(data, cancellationToken));

        var paginatedResult = new PaginatedData<ReadSpecification>
        {
            Count = paginatedData.Count,
            Page = paginatedData.Page,
            PageSize = paginatedData.PageSize,
            Data = dataPage
        };
        

        return paginatedResult;
    }

    private async Task<ReadSpecification> MapSpecification(Specification specification, CancellationToken cancellationToken)
    {
        var formInstanceId = specification.FormInstanceId;

        var formInstance = await _formRepository.GetFormInstance(new GeneralIdRef { Id = formInstanceId }, false, cancellationToken) ??
            throw new NotFoundException("Form instance not found");

        return new ReadSpecification
        {
            Id = specification.Id,
            Guid = specification.Guid,
            Name = specification.Name,
            Site = specification.Site.ToGeneralIdRef()!,
            SigmaId = specification.SigmaId,
            FormInstanceId = formInstance.ToGeneralIdRef()!,
        };
    }
    
    private Expression<Func<Specification, object>> KeySelector(string? sortKey)
    {
        return sortKey?.ToLowerInvariant() switch
        {
            "id" => x => x.Id,
            "guid" => x => x.Guid,
            "name" => x => x.Name,
            "site.id" => x => x.Site.Id,
            _ => x => x.Name
        };
    }

    public async Task DeleteSpecification(
        AuditUserDetails auditUserDetails,
        GeneralIdRef generalIdRef,
        bool triggerEFlowSync,
        CancellationToken cancellationToken
    )
    {
        await _specificationManagerRepository.TransactionAsync(async () =>
        {
            var specification = await _specificationManagerRepository.GetSpecification(generalIdRef, cancellationToken);
            Assert(specification, "Specification not found");

            specification!.Deleted = true;
            await _specificationManagerRepository.EditSpecification(auditUserDetails, specification, cancellationToken);
            if (triggerEFlowSync)
                _eFlowSyncMessageSender.SyncEFlowPrinterCategories();
        });
    }

    public async Task EditSpecification(AuditUserDetails auditUserDetails, EditSpecification edit, bool triggerEFlowSync, CancellationToken cancellationToken)
    {
        await EditSpecification(auditUserDetails, [edit], triggerEFlowSync, cancellationToken);
    }

    public async Task EditSpecification(AuditUserDetails auditUserDetails, IEnumerable<EditSpecification> items, bool triggerEFlowSync, CancellationToken cancellationToken)
    {
        await _specificationManagerRepository.TransactionAsync(async () =>
        {
            var updates = new List<Specification>();
            foreach (var edit in items)
            {
                var specification =
                    await _specificationManagerRepository.GetSpecification(edit.Id, cancellationToken) ??
                    throw new NotFoundException("Specification not found");
                
                var formInstance = await _formRepository.GetFormInstance(edit.FormInstanceId, false, cancellationToken) ??
                                   throw new NotFoundException("Form instance not found");

                specification.Name = edit.Name;
                specification.FormInstanceId = formInstance.Id;

                if (!specification.Site.GeneralIdEquals(edit.Site))
                {
                    var site = await _siteManagerRepository.GetSite(edit.Site, cancellationToken) ??
                               throw new NotFoundException("Site not found");
                    specification.Site = site;
                    specification.SiteId = site.Id;
                }

                //todo sort out the domain delta changes here.

                updates.Add(specification);
            }

            await _specificationManagerRepository.EditSpecification(auditUserDetails, updates, cancellationToken);
            if (triggerEFlowSync)
                _eFlowSyncMessageSender.SyncEFlowPrinterCategories();
        });
    }
}