using adas_core.Application.Services.Interfaces;
using adas_core.Domain.Enums;
using adas_core.Domain.Models;
using adas_core.Domain.Models.GroupedObservations;
using adas_core.Domain.Utils;
using Microsoft.Extensions.Logging;
using MongoDB.Bson;

namespace adas_core.Application.Subscriptions;

public class SubscriberGroupedService(
    ICacheService? cacheService,
    IGroupedObservationService groupedObservationService,
    ILogger<SubscriberGroupedService> logger,
    Lazy<IClientMessageService> clientMessageService)
    : ISubscriberGroupedService
{
    private List<WsSubscriberGrouped> _subscriberGrouped = [];


    //private static readonly ILogger _logger = Log.ForContext<SubscriberGrouped>();

    public List<WsSubscriberGrouped> SubscriberGroupedList
    {
        get
        {
            lock (_subscriberGrouped)
            {
                return _subscriberGrouped;
            }
        }
        set
        {
            lock (_subscriberGrouped)
            {
                _subscriberGrouped = value;
            }
        }
    }

    public List<WsSubscriberGrouped> GetGrouped()
    {
        lock (_subscriberGrouped)
        {
            return _subscriberGrouped.ToList();
        }
    }

    public void RemoveGroupedObsByPatientId(string patientId)
    {
        lock (_subscriberGrouped)
        {
            var itemToRemove = _subscriberGrouped.Where(c => c.PatientId.ToString() == patientId).ToList();
            itemToRemove.ForEach(item => _subscriberGrouped.Remove(item));
        }
    }

    /// <summary>
    ///     Check if what clients dont need to get updated with new grouped observations and remove them
    ///     diference between section and box if location is updated doesn't matter if is section or box
    ///     BoxSubscribers don't need to get updated otherwise SectionSubscribers may still need to get updated
    /// </summary>
    /// <param name="patientId">objectId on db for Patient</param>
    /// <param name="newLocation"></param>
    public void RemoveWsSubscriberByLocation(string patientId, PatientLocation? newLocation)
    {
        lock (_subscriberGrouped)
        {
            var itemToRemove = _subscriberGrouped.Where(c => c.PatientId.ToString() == patientId).ToList();
            foreach (var wsSubscriberGrouped in itemToRemove)
            {
                // Make a separate list for wsClients to be removed
                var wsClientsToRemove = new List<string>();
                foreach (var wsClient in wsSubscriberGrouped.WsSubscriber) wsClientsToRemove.Add(wsClient);

                // Remove the wsClients
                foreach (var wsClient in wsClientsToRemove) wsSubscriberGrouped.WsSubscriber.Remove(wsClient);

                CheckEmptySubscriberGroup(wsSubscriberGrouped);
            }
        }
    }

    public void RemoveWsSubscriberPatientIdAndWsId(string patientId, string wsIdToRemove)
    {
        lock (_subscriberGrouped)
        {
            var itemToRemove = _subscriberGrouped
                .Where(c => c.PatientId.ToString() == patientId && c.WsSubscriber.Contains(wsIdToRemove)).ToList();
            foreach (var wsSubscriberGrouped in itemToRemove)
            {
                // // Make a separate list for wsClients to be removed
                // var wsClientsToRemove = new List<string>();
                // foreach (var wsClient in wsSubscriberGrouped.WsSubscriber)
                // {
                //     if(wsClient == wsIdToRemove)
                //         wsClientsToRemove.Add(wsClient);
                // }
                //
                // // Remove the wsClients
                // foreach (var wsClient in wsClientsToRemove)
                // {
                //     wsSubscriberGrouped.WsSubscriber.Remove(wsClient);
                //     wsSubscriberGrouped.Group.Remove(wsClient);
                // }
                wsSubscriberGrouped.WsSubscriber.Remove(wsIdToRemove);
                wsSubscriberGrouped.Group.Remove(wsIdToRemove);

                CheckEmptySubscriberGroup(wsSubscriberGrouped);
            }
        }
    }

    /// <summary>
    ///     Check clients using generated grouped observations on disconections
    ///     removing the wole object in case of 0 or
    ///     only the group if any other client is using it
    /// </summary>
    /// <param name="wsSubscriberGrouped">
    ///     Object with properties to generate new grouped observations, clients using those
    ///     grouped obsercations and the generated list of grouped observations
    /// </param>
    public List<string> CheckEmptySubscriberGroup(WsSubscriberGrouped? wsSubscriberGrouped)
    {
        lock (_subscriberGrouped)
        {
            var removed = new List<string>();
            if (wsSubscriberGrouped == null) return [];

            if (wsSubscriberGrouped.WsSubscriber.Count == 0)
            {
                //_logger.Debug($"Removing wsSubscriberGrouped from list because client subscriber are empty");
                wsSubscriberGrouped.Timer.Stop();
                wsSubscriberGrouped.Timer.Dispose();
                _subscriberGrouped.Remove(wsSubscriberGrouped);
                removed.Add(
                    $"ws:{wsSubscriberGrouped.PatientId}-{string.Join(";", wsSubscriberGrouped.Names)}-{wsSubscriberGrouped.Regularity}-{wsSubscriberGrouped.Group.Count}");
                cacheService?.DeleteObjectAsync(wsSubscriberGrouped.HashCode);
            }
            else
            {
                foreach (var kvp in wsSubscriberGrouped.Group.ToList())
                    if (!wsSubscriberGrouped.WsSubscriber.Any(subscriber =>
                            subscriber.Contains(kvp.Key)))
                    {
                        //_logger.Debug($"Removing group {kvp.Key}:{kvp.Value} from list because client subscriber got disconnected but wsSubscriberGrouped still in use with {wsSubscriberGrouped.WsSubscriber.Count()} clients");
                        wsSubscriberGrouped.Group.Remove(kvp.Key);
                        wsSubscriberGrouped.WsSubscriber.Remove(kvp.Key);
                        removed.Add($"group:{kvp.Key}");
                    }
            }


            return removed;
        }
    }

    public void AddSubscriberGrouped(WsSubscriberGrouped wsSubscriberGrouped)
    {
        lock (_subscriberGrouped)
        {
            _subscriberGrouped.Add(wsSubscriberGrouped);
        }
    }

    public void CheckOnSubscriptionGroup(GroupedField groupedField, ObjectId patientId, string? timeZoneId,
        string connectionId, GroupedObservation lastObsInGroup)
    {
        var wsSubscriberGrouped = GetGrouped().FirstOrDefault(s => s.Compare(groupedField, patientId));
        if (wsSubscriberGrouped != null)
        {
            wsSubscriberGrouped.WsSubscriber.Add(connectionId);

            // Verifica si el diccionario contiene la clave y el valor deseado
            if (!wsSubscriberGrouped.Group.ContainsKey(connectionId) ||
                wsSubscriberGrouped.Group[connectionId] != connectionId)
                // Añade el par clave-valor al diccionario
                wsSubscriberGrouped.Group[connectionId] = groupedField.Group ?? string.Empty;
        }
        else
        {
            AddSubscriberGrouped(
                // ReSharper disable once AsyncVoidLambda
                new WsSubscriberGrouped(patientId, connectionId, timeZoneId, groupedField, lastObsInGroup,
                    async delegate(object? sender, string _)
                    {
                        try
                        {
                            if (sender is WsSubscriberGrouped ws) await AddEmptyObs(ws);
                        }
                        catch (Exception ex)
                        {
                            logger.LogError(ex, "Ocurrió un error al procesar el evento del suscriptor.");
                            throw;
                        }
                    }));
        }
    }

    public void UpdateLastGroupedObsInGroup(string wsgHashCode, GroupedObservation newGroupedObservation)
    {
        lock (_subscriberGrouped)
        {
            _subscriberGrouped.FirstOrDefault(c => c.HashCode == wsgHashCode)?.UpdateLastGo(newGroupedObservation);
        }
    }

    private async Task AddEmptyObs(WsSubscriberGrouped ws)
    {
        var gobs = await groupedObservationService.CreateNextEmptyObs(ws);

        UpdateLastGroupedObsInGroup(ws.HashCode, gobs);
        lock (_subscriberGrouped)
        {
            foreach (var sub in ws.WsSubscriber)
            {
                gobs.Group = ws.Group.GetValue(sub)!;
                logger.LogInformation("Sending empty obs on gruped for: {Sub}, with group: {Group}", sub, gobs.Group);

                _ = clientMessageService.Value.SendAsync(sub, OperationType.GroupedObservation, gobs);
            }
        }
    }
}