using adas_core.Application.Exceptions;
using adas_core.Application.Repositories.Interfaces;
using adas_core.Application.Services.Interfaces;
using adas_core.Application.Subscriptions;
using adas_core.Domain.Enums;
using adas_core.Domain.Models;
using adas_core.Domain.Models.AppSettings;
using adas_core.Domain.Models.Filter;
using adas_core.Domain.Models.GroupedObservations;
using adas_core.Domain.Models.MongoModels;
using adas_core.Domain.Models.Responses;
using adas_core.Domain.Utils;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using MongoDB.Bson;
using MongoDB.Driver;
using Serilog;
using Patient = adas_core.Domain.Models.MongoModels.Patient;

namespace adas_core.Application.Services;

public class ObservationService : IObservationService
{
    private readonly ICacheService _cacheService;
    private readonly CacheSettings? _cacheSettings;
    
    private readonly IAlarmService _alarmService;
    private readonly List<string> _allergies;
    private readonly ILocalAuditService _auditService;
    private readonly Lazy<ICalculatedObservationsService> _calculatedObservationsService;
    private readonly IClientMessageService _clientMessageService;
    private readonly IConfigObservationService _configObservationService;
    private readonly IConfigUnitsService _configUnitsService;
    private readonly List<string> _diagnosisCode;
    private readonly IDiagnosisService _diagnosisService;
    private readonly List<string> _drainageCode;
    private readonly IGroupedObservationService _groupedObservationService;
    private readonly IHttpContextAccessor _httpContextAccessor;

    private readonly List<string> _intravenousLines;
    private readonly List<string> _isolationCode;
    private readonly ILightBeaconService _lightBeaconService;
    private readonly ILogger<ObservationService> _logger;
    private readonly IObservationArchiveRepository _observationArchiveRepository;
    private readonly IObservationRepository _observationRepository;
    private readonly IPatientService _patientService;

    //Suprimo el warning porque detecta que no se usa en el código, pero sí se usa en los test
    //#pragma warning disable IDE0051 // Quitar miembros privados no utilizados
    //#pragma warning disable CS0169 // El campo '_observationService.createPatientWithORU' nunca se usa
    /*private readonly bool createPatientWithORU;
    private readonly bool createPatientWithoutLocation;

    private readonly bool createPatientWithLocation;

    //private readonly bool updatePatientLocationWithORU;
    private readonly bool updatePatientDataWithORU;
    private readonly bool ArchivePatientWithOru;*/
    //#pragma warning restore CS0169 // El campo '_observationService.createPatientWithORU' nunca se usa
    //#pragma warning restore IDE0051 // Quitar miembros privados no utilizados


    private readonly bool _persistObservationCodes;
    private readonly IPointOfCareService _pointOfCareService;
    private readonly List<string> _positionCode;
    private readonly IRecordingService _recordingService;
    private readonly IRelayService _relayService;
    private readonly ISubscriberGroupedService _subscriberGroupedService;
    private readonly ISubscribersService _subscribersService;


    public ObservationService(
        IPatientService patientService,
        IConfigObservationService configObservationService,
        IObservationRepository observationRepository,
        IObservationArchiveRepository observationArchiveRepository,
        IConfigUnitsService configUnitsService,
        IDiagnosisService diagnosisService,
        IOptions<ApiSettings> apiSettings,
        IOptions<CacheSettings> cacheSettings,
        ILightBeaconService lightBeaconService,
        IRelayService relayService,
        IRecordingService recordingService,
        ILogger<ObservationService> logger,
        IGroupedObservationService groupedObservationService,
        IAlarmService alarmService,
        IClientMessageService clientMessageService,
        ISubscribersService subscribersService,
        ISubscriberGroupedService subscriberGroupedService,
        Lazy<ICalculatedObservationsService> calculatedObservationsService,
        IHttpContextAccessor httpContextAccessor,
        ILocalAuditService auditService,
        IPointOfCareService pointOfCareService, 
        ICacheService cacheService
        )

    {
        _patientService = patientService;
        _configObservationService = configObservationService;
        _observationRepository = observationRepository;
        _observationArchiveRepository = observationArchiveRepository;
        _configUnitsService = configUnitsService;
        _diagnosisService = diagnosisService;
        _lightBeaconService = lightBeaconService;
        _relayService = relayService;
        _recordingService = recordingService;
        _httpContextAccessor = httpContextAccessor;
        _auditService = auditService;
        _groupedObservationService = groupedObservationService;
        _cacheService = cacheService;


        _logger = logger;

        if (apiSettings == null) throw new Exception("ApiSettings must be defined");


        _persistObservationCodes = apiSettings.Value.PersistObservationCodes;
        _intravenousLines = apiSettings.Value.IntravenousLinesCode ?? [];
        _allergies = apiSettings.Value.AllergiesCode;
        _drainageCode = apiSettings.Value.DrainageCode;
        _isolationCode = apiSettings.Value.IsolationCode;
        _positionCode = apiSettings.Value.PositionCode;
        _diagnosisCode = apiSettings.Value.DiagnosisCode;
        _alarmService = alarmService;
        _clientMessageService = clientMessageService;
        _subscribersService = subscribersService;
        _subscriberGroupedService = subscriberGroupedService;
        _calculatedObservationsService = calculatedObservationsService;
        _pointOfCareService = pointOfCareService;
        _cacheSettings =  cacheSettings.Value;
    }

    public async Task<List<PatientObservation>> FindLastObservations(ObjectId patientId, int num = 2,
        List<string>? filterObservations = null)
    {
        var result =
            await _observationRepository.AggregatedPatientLastObservations(patientId, num, filterObservations);
        return result;
    }

    private async Task<List<PatientObservation>> AggregatedLastObsCached(
        ObjectId patientId,
        List<Field>? filterObservations,
        CancellationToken ct = default)
    {
        // Normalizar campos
        var fieldNames = (filterObservations ?? new())
            .Select(f => f.Name ?? string.Empty)
            .Where(n => !string.IsNullOrWhiteSpace(n))
            .ToList();

        // Clave + TTL según PatientObservation / backend configurado
        var (key, ttl) = CacheKeys.LatestObservationsKeyWithTtl(
            _cacheSettings,
            patientId,
            fieldNames
        );

        // GET → MISS → LOCK → AGGREGATE → SET
        var result = await _cacheService.GetOrSetObjectAsync(
            key,
            async () =>
            {
                var raw = await _observationRepository
                    .AggregatedPatientLastObservationsByField(patientId, filterObservations);

                return raw;
            },
            ttl, ct);

        return result;
    }

    public async Task<List<PatientObservation>> FindLastObservationsByField(
        ObjectId patientId,
        List<Field>? filterObservations = null,
        bool mapped = true,
        CancellationToken ct = default)
    {
        //lista RAW desde la caché
        var raw = await AggregatedLastObsCached(patientId, filterObservations, ct);

        if (!mapped)
            return raw;

        // Mapeo
        var mappedList = new List<PatientObservation>();
        foreach (var o in raw)
        {
            var mo = await MapObservationsByName(o);
            if (mo != null)
                mappedList.Add(mo);
        }

        return mappedList;
    }

    public async Task<PatientObservation?> MapObservationsByName(PatientObservation obs)
    {
        return await _configObservationService.Map(obs, true);
    }

    public async Task<PatientObservation?> MapObservation(PatientObservation obs, bool onlyByName = false)
    {
        try
        {
            _logger.LogTrace("Mapping config Observation obs: {obs} onlyByName: {onlyByName}", obs, onlyByName);
            var obs2 = await _configObservationService.Map(obs, onlyByName);
            if (obs2 == null)
            {
                _logger.LogTrace("Mapping obs2 {obs}: Ignored", obs);
                return null;
            }

            var obs3 = await _configUnitsService.Map(obs2);

            var obs4 = await _calculatedObservationsService.Value.Map(obs3, onlyByName);

            _logger.LogTrace("Mapping calculatedObservations.Map obs4: {obs4}", obs4);

            if (obs4 == null)
            {
                _logger.LogTrace("Mapping obs4 {obs3}: Ignored", obs3);
                return null;
            }

            if (obs4.CheckObservations)
                _ = _alarmService.CheckObservationAlarm(obs4);


            return obs4;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error Mapping Observation, Ignoring Observation: {obs} Exception:{ex}", obs,
                ex.Message);
            return null;
        }
    }

    /// <summary>
    ///     Inserts a new observation if needed
    /// </summary>
    /// <param name="obs">Observation</param>
    /// <param name="persistObs"></param>
    /// <param name="mapObs">True if it needs to be inserted</param>
    public async Task InsertObservation(PatientObservation obs, bool persistObs = true, bool mapObs = true)
    {
        try
        {
            var obs2 = obs;

            //only will be false if the obs comes from the inner refactor job
            if (mapObs) obs2 = await MapObservation(obs2);

            if (obs2 == null)
            {
                _logger.LogDebug("Mapped observation returns null. Ignored {obs}", obs);
            }
            else
            {
                _logger.LogDebug("Mapped {obs2}", obs2);

                if (!_persistObservationCodes)
                {
                    obs2.Code = null;
                    obs2.CodingSystem = null;
                    obs2.ParentData = null;
                }

                _ = SendObsBroadcast(obs2);

                if (obs2.Persist.HasValue)
                    persistObs = obs2.Persist.Value;

                _logger.LogDebug("Insert {obs}, persist is {persistObs}", obs2, persistObs);

                if (persistObs)
                {
                    await _observationRepository.InsertOneAsync(obs2);
                    await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, null, obs2);
                    _logger.LogDebug("Inserted {obs2}", obs2);

                    _ = Task.Run(() =>
                        CheckForGroupedObs(obs2)
                    );
                }

                _ = DoRetentionActions(obs2);
            }
        }
        catch (Exception ex)
        {
            _logger.LogError("Error Inserting observation {obs}. Excepcion; {ex} ", obs, ex);
        }
    }

    public async Task InsertNurseObservation(PatientObservation obs)
    {
        try
        {
            var obs2 = obs;

            obs2 = await MapObservation(obs2, true);
            if (obs2 == null || obs2.Name == null)
            {
                _logger.LogError("Error mapping observation {obs}", obs.ToString());
                return;
            }

            await _observationRepository.InsertOneAsync(obs2);
            
            var (key, ttl) = CacheKeys.LatestObservationsKeyWithTtl(
                _cacheSettings,
                obs2.PatientId,
                [obs2.Name]
            );
            
            // GET → MISS → LOCK → AGGREGATE → SET
            var result = _cacheService.GetOrSetObjectAsync(
                key,
                async () =>
                {
                    return obs2;
                },
                ttl, default);
            
            _ = SendObsBroadcast(obs2);
            await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, null, obs2);
        }
        catch (Exception ex)
        {
            _logger.LogError("Error Inserting nurse observation {obs}. Excepcion; {ex} ", obs, ex);
        }
    }

    public async Task<bool> InsertIfChanged(string name, PatientObservation observation, bool persistObs = true,
        bool mapObs = true)
    {
        var changedList = await FindLastObservations(observation.PatientId, 1, [name]);
        var changed = changedList.All(o => observation.Value != o.Value);

        if (!changed) return false;

        await InsertObservation(observation, persistObs, mapObs);
        return true;
    }


    public Task SendObsBroadcast(List<PatientObservation> obsList, PatientLocation location)
    {
        // Display Subscription
        var displaySubscribers = _subscribersService.GetSubscribers().Where(s =>
            !s.Locations.IsNullOrEmpty() && s.Locations.Any(c =>
                c.UnitName == location.UnitName &&
                c.Bed == location.Bed &&
                c.Room == location.Room
            )).ToList();


        foreach (var subscriber in displaySubscribers)
        foreach (var obs in obsList)
            _ = _clientMessageService.SendAsync(subscriber.Id, OperationType.Observation, obs);

        return Task.CompletedTask;
    }

    public Task SendObsBroadcast(List<PatientObservation> obsList, ObjectId pocId)
    {
        // Display Subscription
        var displaySubscribers = _subscribersService.GetSubscribers().Where(s =>
            !s.LocationIds.IsNullOrEmpty() && s.LocationIds.Any(c =>
                c == pocId
            )).ToList();


        foreach (var subscriber in displaySubscribers)
        foreach (var obs in obsList)
            _ = _clientMessageService.SendAsync(subscriber.Id, OperationType.Observation, obs);

        return Task.CompletedTask;
    }

    public async Task SendObsBroadcast(BasePatientObservation obs)
    {
        if (obs.Name == null) return;

        var patient = obs.Patient ?? await _patientService.FindById(obs.PatientId);
        if (patient == null)
        {
            _logger.LogDebug("Not patient on bd to sendOnBroadcastObs: {obspatientid}", obs.PatientId);
            return;
        }

        _logger.LogTrace(
            "sending obs name: {obsname} to patient id: {patientid}, PointOfCare: {patientpointOfCare} {patientbed}",
            obs.Name, patient.Id, patient.UnitString, patient.Bed);


        var displaySubscribers = _subscribersService.GetSubscribers().Where(s =>
            !s.LocationIds.IsNullOrEmpty() && s.LocationIds.Any(c =>
                c == patient.PointOfCareId
            )).ToList();


        foreach (var subscriber in displaySubscribers)
            _ = _clientMessageService.SendAsync(subscriber.Id, OperationType.Observation, obs);
    }

    public async void ProcessObservations(List<PatientObservation> observations, Patient patient,
        DateTime messageTime, ObservationData? observationData = null)
    {
        try
        {
            _logger.LogDebug(
                "Patient: {patientid} PoC {patientpointOfCare} {patientbed} msg {messageTime} INSERTING {observationsCount} OBSERVATIONS",
                patient.Id, patient.PointOfCare, patient.Bed, messageTime, observations.Count);

            ParentDataClass? parentData = null;
            if (observationData != null)
                parentData = new ParentDataClass
                {
                    Code = observationData.Code,
                    CodingSystem = observationData.CodingSystem,
                    Name = observationData.Text
                };

            var obsToInsert = new List<PatientObservation>();
            // Alarm to insert
            // var alrmToInsert = new List<PatientObservationAlarm>();

            foreach (var obs in observations)
            {
                obs.ParentData = parentData;
                obs.MessageTime = messageTime;
                obs.PatientId = patient.Id;
                obs.Patient = patient;
                obs.Id = ObjectId.GenerateNewId();

                if (obs.Time == DateTime.MinValue)
                    obs.Time = DateTime.UtcNow;

                if (obs.MessageTime == DateTime.MinValue)
                    obs.MessageTime = DateTime.UtcNow;

                _logger.LogDebug(
                    "Patient: {patientId} PoC {patientpointOfCare} {patientbed} msg {messageTime} INSERTING observation {obs}",
                    patient.Id, patient.PointOfCare, patient.Bed, messageTime, obs);
                obsToInsert.Add(obs);
            }

            obsToInsert = await _calculatedObservationsService.Value.MapList(obsToInsert);

            _ = Task.WhenAll(obsToInsert.Select(obs =>
                Task.Run(() => InsertObservation(obs)))); //.GetAwaiter().GetResult();                
        }
        catch (Exception ex)
        {
            _logger.LogError(
                "Patient: {patientid} PoC {patientpointOfCare} {patientbed} msg {messageTime} ERROR PROCESSING observations. Exception: {ex}",
                patient.Id, patient.PointOfCare, patient.Bed, messageTime, ex);
        }
    }

    public Task SaveRequestNurseObsAsync(ApiRequest request)
    {
        return Task.Run(() => SaveRequestNurseObs(request));
    }

    public async Task SaveRequest(ApiRequest apiRequest)
    {
        if (
            string.IsNullOrEmpty(apiRequest.PatientNumber) &&
            apiRequest.Location != null && apiRequest.Location.IsEmpty()
        )
        {
            _logger.LogDebug("Patient and PointOfCare are nulls");
            throw new ApiRequestException("Patient and PointOfCare are nulls");
        }

        _logger.LogDebug("patientNumber: {PatientNumber} location: {Location}", apiRequest.PatientNumber,
            apiRequest.Location);
        _logger.LogDebug("RequestType: {Type}", apiRequest.Type);

        var patient = await _patientService.FindPatientByApiRequest(apiRequest);

        if (patient == null)
        {
            // NO PATIENTS OR LOCATIONS WERE FOUND 
            _logger.LogWarning(
                "Observation for patient: {apiRequestpatientNumber} with location: {apiRequestlocation} not found, ignoring",
                apiRequest.PatientNumber, apiRequest.Location);
            return;
        }


        try
        {
            switch (apiRequest.Type)
            {
                /*
                 * ORU_R01 - Unsolicited transmission of an observation message
                 * ORU_R40 - Unsolicited transmission of an alert observation message
                 */
                case "ORU_R40": // UNSOLICITED ALERT OBSERVATION
                    await _alarmService.SaveRequestAsync(apiRequest);
                    return;
                case "ORU_R01": //  UNSOLICITED OBSERVATON

                    if (!apiRequest.Alarms.IsNullOrEmpty())
                    {
                        await _alarmService.SaveRequestAsync(apiRequest);
                        return;
                    }


                    // OBSERVATIONS
                    if (apiRequest.Observation != null &&
                        (apiRequest.Observations == null || !apiRequest.Observations.Any()))
                        apiRequest.Observations = [apiRequest.Observation];

                    var obrcode = apiRequest.ObservationData?.Code ?? "";

                    if (apiRequest.Observations != null)
                    {
                        if (_intravenousLines.Contains(obrcode))
                            await ProcessIntravenousLinesObservation(apiRequest, patient);
                        else if (_allergies.Contains(obrcode))
                            await ProcessAllergiesObservation(apiRequest, patient);
                        else if (_drainageCode.Contains(obrcode))
                            await ProcessDrainageObservation(apiRequest, patient);
                        //TODO PARA SALIR EN EL RYC REFACTOR
                        else if (_isolationCode.Contains(obrcode))
                            await ProcessIsolationObservation(apiRequest, patient);
                        //TODO REFACTOR, CAMBIO RAPIDO PARA SALIR RYC, PENSAR SI NO SOLO HAY QUE FIARSE POR EL CÓDIGO SI NO POR TODA LA ESTRUCTURA
                        //DE CONFIG OBSERVATIONS, A LO MEJOR LA BD TIENE QUE INDICAR SI ES UN PROCESAMIENTO ESPECIAL? REVISAR.
                        else if (_positionCode.Contains(obrcode) &&
                                 !"Temperatura(ºC)".Equals(apiRequest.ObservationData?.Text))
                            await ProcessPositionObservation(apiRequest, patient);
                        else if (_diagnosisCode.Contains(obrcode))
                            await _diagnosisService.SaveRequest(apiRequest, patient);
                        else
                            ProcessObservations(apiRequest.Observations, patient, apiRequest.MessageTime,
                                apiRequest.ObservationData);
                    }

                    break;
                default:
                    _logger.LogWarning("ApiRequest type {apiRequesttype} sis not valid for Observations",
                        apiRequest.Type);
                    throw new ApiRequestException("ApiRequest type " + apiRequest.Type +
                                                  " is not valid for Observations");
            }
        }
        catch (Exception ex)
        {
            _logger.LogError(
                "ERROR SAVING REQUEST:  exception: {exMessage} trace: {exStackTrace}",
                ex.Message, ex.StackTrace); //add traceability
            throw new Exception(ex.Message);
        }
    }

    public Task SaveRequestAsync(ApiRequest apiRequest)
    {
        //return Task.FromResult(Task.Run(async () => { await SaveRequest(apiRequest); }));
        return Task.Run(() => SaveRequest(apiRequest));
    }


    public async Task<IAsyncCursor<PatientObservation>> FindByPatientIdAsync(ObjectId patientId)
    {
        return await _observationRepository.FindByPatientIdAsync(patientId);
    }

    public async Task<IAsyncCursor<PatientObservation>> FindByPatientIdAndCodingSystemAsync(ObjectId patientId,
        string codingSystem, string name)
    {
        return await _observationRepository.FindByPatientIdAndCodingSystemAsync(patientId, codingSystem, name);
    }

    public async Task DeleteByPatientId(ObjectId id)
    {
        try
        {
            var deletedObservationList = await _observationRepository.FindByPatientId(id);

            if (deletedObservationList == null || deletedObservationList.Count == 0)
                return;

            _logger.LogDebug("Delete Observations by Patient Id {id}", id);
            await _observationRepository.DeleteByPatientId(id);

            await _cacheService.DeleteByPatternAsync(CacheKeyPatterns.ByPatient("patients:latestObs", id.ToString()));
            
            await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, deletedObservationList,
                null);
        }
        catch (Exception ex)
        {
            _logger.LogError("Error Deleting by PatientId. Exception: {ex}", ex);
        }
    }

    public async Task Archive(PatientObservation observation)
    {
        await _observationArchiveRepository.InsertOneAsync(observation);
        await _observationRepository.DeleteAsync(observation.Id);
        await _cacheService.DeleteByPatternAsync(CacheKeyPatterns.ByPatient("patients:latestObs", observation.PatientId.ToString()));

    }

    public async Task Archive(Patient patient)
    {
        await ArchiveByPatientId(patient.Id);
    }

    public async Task ArchiveByPatientId(ObjectId id)
    {
        _logger.LogDebug("Archive Observations by Patient Id {id}", id);
        var cursor = await FindByPatientIdAsync(id);

        while (await cursor.MoveNextAsync())
            foreach (var current in cursor.Current)
            {
                current.Id = ObjectId.GenerateNewId();
                await _observationArchiveRepository.InsertOneAsync(current);
            }

        await DeleteByPatientId(id);
        await _cacheService.DeleteByPatternAsync(CacheKeyPatterns.ByPatient("patients:latestObs", id.ToString()));

    }

    public async Task<List<PatientObservation?>> FindLastIntravenousLinesObservationByLocation(ObjectId patientId)
    {
        return await _observationRepository.AggregatedPatientActiveIntravenousLinesObservations(patientId);
    }

    public async Task<Dictionary<ObjectId, DateTime>> FindAllLastPatientObservationTime()
    {
        return await _observationRepository.FindAllLastPatientObservationTime();
    }


    public async Task UpdateObservation(PatientObservation observation)
    {
        var obs = await _observationRepository.FindById(observation.Id);
        if (obs != null)
        {
            await _observationRepository.Update(observation);
            
            await _cacheService.DeleteByPatternAsync(CacheKeyPatterns.ByPatient("patients:latestObs",observation.PatientId.ToString()));

            await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, obs, observation);
        }
    }

    public async Task<PatientObservation?> FindLastBeforeDate(ObjectId patientId, DateTime date, string? obsName)
    {
        return await _observationRepository.FindLastObservationBeforeDate(patientId, obsName, date);
    }

    public async Task<List<PatientObservation>?> FindAnyWithSameDate(ObjectId patientId, DateTime date,
        string? obsName)
    {
        return await _observationRepository.FindAnyWithSameDate(patientId, obsName, date);
    }

    //TODO To implement
    public async Task<List<PatientObservation>> FindAllBeforeDate(ObjectId patientId, DateTime date,
        List<string>? filterObservations = null)
    {
        return await _observationRepository.FindAnyBeforeDate(patientId, date);
    }

    public async Task<List<PatientObservation>> FindLatestUniqueValuesByName(ObjectId patientId, string name,
        int? expires)
    {
        return await _observationRepository.FindLatestUniqueValuesByName(patientId, name, expires);
    }

    //TODO To implement
    public Task<List<PatientObservation>> FindAllAfterDate(ObjectId patientId, DateTime date,
        List<string>? filterObservations = null)
    {
        throw new NotImplementedException();
    }

    public async Task<IEnumerable<PatientObservation>> FindLastNotExpiredObservatonsByPatient(ObjectId patientId,
        string name, int? endAfter = null, int? num = null)
    {
        return await _observationRepository.FindLastNotExpiredObservatonsByPatient(patientId, name, endAfter, num);
    }

    /// <summary>
    ///     Retrieve all observations with expire time from configObservation service
    ///     Check all of this observations in patient_observations and expire them if (obs.time + config.expires) smaller than
    ///     current time
    ///     mark as expired in bd.
    /// </summary>
    public async Task CheckAndExpireObservations()
    {
        var count = 0;
        _logger.LogDebug("start checking expired observations");


        await using var enumerator = FindNotExpiredObservationsShouldBeExpired().GetAsyncEnumerator();
        while (await enumerator.MoveNextAsync())
        {
            count++;

            var current = enumerator.Current;
            current.Expired = true;

            await _observationRepository.Update(current);
        }

        _logger.LogDebug("found observations {count} ", count);
    }

    public async Task UpdateExpiredObservations(List<PatientObservation> patientObservations)
    {
        await _observationRepository.UpdateExpiredObservations(patientObservations);
        var id = patientObservations.First().Id;
        await _cacheService.DeleteByPatternAsync(CacheKeyPatterns.ByPatient("patients:latestObs", id.ToString()));

        foreach (var observation in patientObservations)
        {
            var auxObs = await _auditService.DeepCopyAsync(observation);
            observation.Expired = true;
            await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, auxObs, observation);
        }
    }

    public async IAsyncEnumerable<PatientObservation> FindNotExpiredObservationsShouldBeExpired()
    {
        var expiringObservations = await _configObservationService.GetAllConfigs();
        var expiringList = expiringObservations.ToList();
        expiringList.RemoveAll(z => z.Expires == null);
        var count = 0;

        var uniqueNames = expiringList
            .Select(item => item.Name)
            .Distinct()
            .ToList();


        using var enumerator = (await _observationRepository.FindNotExpired(uniqueNames)).GetEnumerator();
        while (enumerator.MoveNext())
        {
            count++;

            var current = enumerator.Current;
            var patientid = current.PatientId;
            var patient = await _patientService.FindById(patientid);
            if (patient == null)
            {
                await _observationRepository.FindByPatientId(patientid);

                await _observationRepository.DeleteByPatientId(patientid);
                await _cacheService.DeleteByPatternAsync(CacheKeyPatterns.ByPatient("patients:latestObs", patientid.ToString()));

                continue;
            }
            if(current.Name == null) continue;
            await _configObservationService.GetConfigObservationItemsByName(current.Name);
            var configObs = expiringObservations?
                .FirstOrDefault();
            if (configObs == null) continue;

            if (!double.TryParse(configObs.Expires.ToString(), out var expires))
                continue;

            var expectedExpireTime = current.Time.AddMinutes(expires);
            if (DateTime.Now > expectedExpireTime) yield return current;
        }

        _logger.LogDebug("expired observations retrieved {count} observations", count);
    }

    public async Task ExpireObservations()
    {
        //TODO expire each section
        var expiringObservations = await _configObservationService.GetAllConfigs();
        var expiringList = expiringObservations.ToList();
        expiringList.RemoveAll(z => z.Expires is null or <= 0);

        await _observationRepository.ExpireExpiredObservations(expiringList.Distinct()
            .ToList());
    }

    public async Task ExpireObservationsAndRecalculateAsync()
    {
        try
        {
            GlobalData.AddData("isCheckingExpiration", true);

            var configObservations =
                await _configObservationService.GetAllConfigs();

            var names = configObservations.Select(o => o.Name).ToList();

            var patients = await _patientService.FindAll();

            foreach (var patient in patients)
            {
                var listField = names?.Select(n => new Field
                {
                    Name = n,
                    Last = 1,
                    OnlyExpired = true
                }).ToList();

                var lastPatientObservationsByName =
                    await _observationRepository.AggregatedPatientLastObservationsByField(patient.Id, listField);

                foreach (var obs in
                         lastPatientObservationsByName)
                    await InsertObservation(obs, false, false); //Not really insert, only makes calcs
            }
            
            var obsToExpireList = new List<PatientObservation>();
            var i = 0;
            var count = 0;
            await using var enumerator = FindNotExpiredObservationsShouldBeExpired().GetAsyncEnumerator();
            while (await enumerator.MoveNextAsync())
            {
                i++;
                //every 1000 cut list to avoid memory leaks when there are tons of data.

                if (i > 1000)
                {
                    await UpdateExpiredObservations(obsToExpireList);
                    i = 0;
                    obsToExpireList.Clear();
                }

                var current = enumerator.Current;
                current.Expired = true;

                obsToExpireList.Add(current);
                count++;
            }
            
            await _cacheService.DeleteByPatternAsync(CacheKeyPatterns.ForEntity(CacheEnum.EntityType.PatientObservations));

            _logger.LogDebug("number of expired observations should be expired:{count}", count);
        }
        catch (Exception ex)
        {
            _logger.LogError("ERROR expire observations And recalculate {exMessage} trace: {exStackTrace}",
                ex.Message, ex.StackTrace);
        }

        GlobalData.AddData("isCheckingExpiration", false);
    }

    public async Task UpdateManyObjectId(string nameId, ObjectId id, ObjectId oldId)
    {
        await _observationRepository.UpdateManyObjectId(nameId, id, oldId);
    }

    public async Task InsertSimpleObservation(PatientObservation observation)
    {
        await _observationRepository.InsertOneAsync(observation);
        await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, null, observation);
    }

    public async Task<List<PatientObservation>> FindAllBetweenDates(
        ObjectId patientId,
        DateTime? startDate = null,
        DateTime? endDate = null,
        List<string>? filterObservations = null,
        bool fromArchived = false,
        PaginationFilter? filter = null
    )
    {
        try
        {
            var collection = fromArchived
                ? _observationArchiveRepository.Collection
                : _observationRepository.Collection;

            var filterBuilder = Builders<PatientObservation>.Filter;

            var conditions = new List<FilterDefinition<PatientObservation>>
            {
                filterBuilder.Eq(o => o.PatientId, patientId),
                filterBuilder.Ne(o => o.Name, null),
                //filterBuilder.In(o => o.Name, filterObservations ?? new List<string?>()),
                filterBuilder.Gt(o => o.Time, startDate ?? DateTime.MinValue),
                filterBuilder.Lt(o => o.Time, endDate ?? DateTime.MaxValue)
            };

            if (filterObservations != null && filterObservations.Any())
                conditions.Add(filterBuilder.In(o => o.Name, filterObservations));

            var combinedFilter = filterBuilder.And(conditions);

            var findOptions = new FindOptions<PatientObservation>
            {
                Skip = (filter?.PageNumber - 1) * filter?.PageSize,
                Limit = filter?.PageSize
            };

            var cursor = await collection.FindAsync(combinedFilter, findOptions);

            var observationsList = await cursor.ToListAsync();

            return observationsList;
        }
        catch (Exception ex)
        {
            Log.Error("Error while getting PatientObservation between dates. Exception: {ex}", ex);
            throw;
        }
    }


    public async Task ExpireAlertsAndPowerOffAsync()
    {
        try
        {
            GlobalData.AddData("isCheckingAlertsExpiration", true);


            var pointOfCares = await _pointOfCareService.GetAllConfigs();

            pointOfCares = pointOfCares
                .Where(box =>
                    box.Configuration is { BeaconList: not null } &&
                    box.Configuration!.BeaconList.Any() &&
                    !box.Configuration!.BeaconList.First().Options.Emulate)
                .ToList();


            var configObs = await _configObservationService.GetAllConfigs();
            var configList = configObs.ToList();

            configList.RemoveAll(z => z.Alarm is not { Enabled: true });


            if (configList.Count == 0)
                return;
            var patients = await _patientService.FindAll();
            if (!patients.Any())
                return;

            //apagamos balizas de los boxes que no tienen pacientes por si se ha quedado alguna encendida
            var emptyBoxes = pointOfCares
                .Where(box => box.Status != StatusEnum.PointOfCare.InUse)
                .ToList();

            emptyBoxes.ForEach(async void (b) =>
            {
                try
                {
                    await _lightBeaconService.PowerOffLed(b.Id);
                }
                catch (Exception ex)
                {
                    _logger.LogError("Exception powerOffLed {exMessage} trace: {exStackTrace}",
                        ex.Message, ex.StackTrace);
                }
            });

            var configObservationItems = configObs.ToList();
            var endAfterDefaultValue = configObservationItems
                .Where(c => c.Alarm != null)
                .Select(c =>
                    c.Alarm?.EndAfter ?? 0) //  comprobación de null aquí por seguridad adicional.
                .DefaultIfEmpty(0)
                .Max();

            foreach (var patient in patients)
            {
                //Nos quedamos con los pacientes que tienen cama

                if (patient.Location.UnitName == nameof(VirtualPointOfCare.Moved) ||
                    patient.Location.UnitName == nameof(VirtualPointOfCare.Deleted) ||
                    patient.Location.UnitName == nameof(VirtualPointOfCare.Pushed) ||
                    patient.Location.UnitName == nameof(VirtualPointOfCare.Unknown))
                    continue;

                _ = CheckBeacon(patient, configObs, endAfterDefaultValue);

                _ = CheckRelay(patient, configObs, endAfterDefaultValue);

                _ = CheckRecordings(patient, pointOfCares, configObs, endAfterDefaultValue);
            }
        }
        catch (Exception ex)
        {
            _logger.LogError("ERROR expire observations And recalculate {exMessage} trace: {exStackTrace}",
                ex.Message, ex.StackTrace);
        }

        GlobalData.AddData("isCheckingAlertsExpiration", false);
    }

    public async Task<PaginationResponse<PatientObservation>> GetPaginatedObservations(PaginationFilter filter)
    {
        var result = _observationRepository.GetPaginatedObservations(filter);

        var count = await result.CountDocumentsAsync();

        var data = await result.Skip((filter.PageNumber - 1) * filter.PageSize)
            .Limit(filter.PageSize)
            .ToCursorAsync();


        var dataList = await data.ToListAsync();

        return new PaginationResponse<PatientObservation>(dataList, filter.PageNumber, filter.PageSize, count);
    }


    private async Task DoRetentionActions(PatientObservation obs)
    {
        try
        {
            var result = await _configObservationService.RetentionActions(obs);
            if (result == null || !result.RetentionPolicyValue.HasValue || string.IsNullOrEmpty(obs.Name)) return;
            var deletedObs = new List<PatientObservation>();
            switch (result.RetentionPolicy)
            {
                case RetentionPolicy.DeleteOlderDays:
                    deletedObs =
                        await _observationRepository.DeleteOlderDaysAsync(obs.Name, result.RetentionPolicyValue.Value);
                    break;
                case RetentionPolicy.DeleteOlderSeconds:
                    deletedObs = await _observationRepository.DeleteOlderSecondsAsync(obs.Name,
                        result.RetentionPolicyValue.Value);
                    break;
                case RetentionPolicy.DeleteOlderNumber:
                    deletedObs = await _observationRepository.DeleteOlderNumberAsync(obs.Name,
                        result.RetentionPolicyValue.Value);
                    break;
                case RetentionPolicy.NoDelete:
                default:
                    break;
            }

            foreach (var observation in deletedObs)
                await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, observation, null);
            
            await _cacheService.DeleteByPatternAsync(CacheKeyPatterns.ByPatient("patients:latestObs", obs.PatientId.ToString()));

        }
        catch (Exception ex)
        {
            _logger.LogError("Error doing retention actions. Exception: {ex}", ex);
        }
    }

    private async void CheckForGroupedObs(PatientObservation obs)
    {
        try
        {
            //Removed ignore obs to future, we accept them and recalculate and front decide what to do
            foreach (var wsg in _subscriberGroupedService.GetGrouped().Where(g =>
                         g.PatientId == obs.PatientId && obs.Name != null && g.Names.Contains(obs.Name) &&
                         g.IsNewObservationRelevantForGroup(obs)))
            {
                wsg.Timer.Stop();

                var groupedField = new GroupedField
                {
                    Names = wsg.Names,
                    Name = obs.Name ?? string.Empty,
                    Max = wsg.Max,
                    Group = wsg.Group.FirstOrDefault().Value,
                    Regularity = wsg.Regularity,
                    Result = wsg.Result,
                    Since = wsg.Since,
                    StartTimeShift = wsg.StartTimeShift
                };


                var newGroupedObservation = await _groupedObservationService
                    .GenerateGroupedObservation(obs.PatientId, groupedField, wsg.LastGroupedObservationObs, obs,
                        wsg.TimeZoneId);

                _subscriberGroupedService.UpdateLastGroupedObsInGroup(wsg.HashCode, newGroupedObservation);

                foreach (var gr in wsg.Group)
                {
                    var grName = wsg.Group.GetValue(gr.Key);
                    if (grName == null) continue;

                    newGroupedObservation.Group = grName;
                    _ = Task.Run(() => _clientMessageService.SendAsync(gr.Key, OperationType.GroupedObservation,
                        newGroupedObservation));
                }

                //wsg.TimerReestart();
                
                await _cacheService.DeleteByPatternAsync(CacheKeys.GroupedObs(obs.PatientId, obs.Name ?? string.Empty));
                
            }
        }
        catch (Exception e)
        {
            _logger.LogError("error checking grouped observations: exception: {eMessage} trace:{eStackTrace}",
                e.Message, e.StackTrace);
        }
    }

    private async Task ProcessIntravenousLinesObservation(ApiRequest apiRequest, Patient patient)
    {
        var isInsertable = false;
        _logger.LogDebug("INSERT IntraVenousLineObservation");

        if (apiRequest.ObsertationData == null || !apiRequest.ObsertationData.Time.HasValue)
        {
            _logger.LogError("Error INSERTING IntraVenousLineObservation. ");
            return;
        }

        var obs = new PatientObservation
        {
            Time = apiRequest.ObsertationData.Time.Value,
            Code = apiRequest.ObsertationData.Code,
            CodingSystem = apiRequest.ObsertationData.CodingSystem,
            PatientId = patient.Id,
            ParentData = new ParentDataClass
            {
                Code = apiRequest.ObservationData?.Code, CodingSystem = apiRequest.ObservationData?.CodingSystem
            },
            MessageTime = apiRequest.MessageTime
        };


        //Todos los formatos de texto de vías son Catéter X: + localizacion 
        var typeLocation = apiRequest.ObservationData?.Text?.Split(':');
        obs.Value = new PatientIntravenousLinesValue
        {
            Type = typeLocation != null ? typeLocation[0] : string.Empty
        };
        if (typeLocation is { Length: > 1 })
            if (obs.Value is PatientIntravenousLinesValue obsValue)
                obsValue.Location = typeLocation[1];

        if (apiRequest.Observations != null)
        {
            if (obs.Value is not PatientIntravenousLinesValue obsValue)
                return;

            foreach (var observation in apiRequest.Observations)
            {
                var obsValueStr = observation.Value.ToString();
                if (obsValueStr == null) continue;

                switch (observation.Code)
                {
                    case "273248003":
                        //In ICCA an intravenouse Line OBS should come with Insertado and Retirado but can come with more, ignore them.
                        if (obsValueStr.Equals("Insertado") || obsValueStr.Equals("Retirado"))
                        {
                            obsValue.Action = observation.Value.ToString() ?? "NULL";
                            isInsertable = true;
                        }
                        else
                        {
                            return;
                        }

                        break;
                    case "397898000":
                        obsValue.RemoveTime = (DateTime?)observation.Value;
                        break;
                    case "439272007":
                        obsValue.InsertTime = (DateTime?)observation.Value;
                        break;
                    case "228864003":
                        obsValue.Duration = observation.Value.ToString() ?? "NULL";
                        break;
                }
            }
        }

        if (!isInsertable) return;

        await InsertObservation(obs);
    }

    private async Task ProcessAllergiesObservation(ApiRequest apiRequest, Patient patient)
    {
        _logger.LogDebug("INSERT AllergiesObservation");

        if (apiRequest.ObservationData == null)
        {
            _logger.LogError("Error Processing Allergies Observation. ObservationData is null");
            return;
        }

        if (!apiRequest.ObservationData.Time.HasValue)
        {
            _logger.LogError("Error Processing Allergies Observation. ObservationData time null");
            return;
        }

        var obs = new PatientObservation
        {
            Time = apiRequest.ObservationData.Time.Value,
            Name = apiRequest.ObsertationData?.Text ?? string.Empty,
            Code = apiRequest.ObservationData.Code,
            CodingSystem = apiRequest.ObservationData.CodingSystem,
            PatientId = patient.Id,
            ParentData = new ParentDataClass
            {
                Code = apiRequest.ObservationData.Code,
                CodingSystem = apiRequest.ObservationData.CodingSystem,
                Name = apiRequest.ObservationData.Text
            },
            MessageTime = apiRequest.MessageTime
        };

        List<PatientAllergiesValue> patientAllergiesValues = [];

        PatientAllergiesValue allergiesValues = new();

        if (apiRequest.Observations == null)
        {
            _logger.LogError("Error Processing Allergies Observation. Observations null");
            return;
        }

        for (var i = 0; i <= apiRequest.Observations.Count - 1; i++)
        {
            switch (apiRequest.Observations[i].Code)
            {
                case "263490005":
                    if (apiRequest.Observations[i].Value.ToString() == "Sin alergias conocidas") return;

                    continue;

                case "300916003": //Latex
                    if (apiRequest.Observations[i].Value.ToString() == "Si")
                    {
                        allergiesValues.Type = "Latex";
                        allergiesValues.Value = "Si";
                        //patientAllergiesValues.Add(allergiesValues);
                        //allergiesValues = new PatientAllergiesValue {  };
                    }

                    break;

                case "419199007":
                    allergiesValues.Type = apiRequest.Observations[i].Value.ToString();
                    break;

                case "277054007":
                case "416098002":
                    allergiesValues.Value = apiRequest.Observations[i].Value.ToString();
                    break;

                case "281296001":
                    allergiesValues.Notes = apiRequest.Observations[i].Value.ToString();
                    break;
            }

            if (i == apiRequest.Observations.Count - 1)
            {
                if (allergiesValues is { Type: not null })
                    patientAllergiesValues.Add(allergiesValues);
                //allergiesValues = new PatientAllergiesValue { value = new List<string>(), notes = new List<string>() };
                allergiesValues = new PatientAllergiesValue();
            }
            else
            {
                i++;
                if (apiRequest.Observations[i].Code == "300916003" ||
                    apiRequest.Observations[i].Code == "419199007")
                {
                    if (allergiesValues is { Type: not null })
                        patientAllergiesValues.Add(allergiesValues);
                    //allergiesValues = new PatientAllergiesValue { value = new List<string>(), notes = new List<string>() };
                    allergiesValues = new PatientAllergiesValue();
                }

                i--;
            }
        }

        obs.Value = patientAllergiesValues;
        await InsertObservation(obs);
    }


    private async Task ProcessIsolationObservation(ApiRequest apiRequest, Patient patient)
    {
        if (apiRequest.ObservationData is { Text: "Aislamiento", Time: not null })
        {
            _logger.LogDebug("INSERT Isolation OBS");
            var value = apiRequest.ObservationData.Value?.ToString()?.Replace(";", ",");
            if (value == null)
            {
                _logger.LogError("Error Processing Isolation Observation. Value is null");
                return;
            }

            var obs = new PatientObservation
            {
                Time = apiRequest.ObservationData.Time.Value,
                Name = "Isolation",
                CodingSystem = "ADAS",
                PatientId = patient.Id,
                Value = value,
                MessageTime = apiRequest.MessageTime
            };

            await InsertObservation(obs);
        }
    }

    private async Task ProcessPositionObservation(ApiRequest apiRequest, Patient patient)
    {
        if (apiRequest.ObservationData != null &&
            apiRequest.ObservationData?.Text?.ToUpper() == "CAMBIOS POSTURALES")
        {
            _logger.LogDebug("INSERT Position OBS");
            var strValue = apiRequest.ObservationData.Value?.ToString();

            if (string.IsNullOrEmpty(strValue))
            {
                var ob = apiRequest.Observation ?? apiRequest.Observations?.FirstOrDefault();
                if (ob != null)
                    strValue = ob.Value.ToString();
            }

            if (string.IsNullOrEmpty(strValue))
            {
                _logger.LogWarning("Position Observation value is null or empty.");
                return;
            }

            if (!apiRequest.ObservationData.Time.HasValue)
            {
                _logger.LogWarning("Position Observation time is null. ");
                return;
            }

            var obs = new PatientObservation
            {
                Time = apiRequest.ObservationData.Time.Value,
                Name = "Patient_Position",
                CodingSystem = "ADAS",
                PatientId = patient.Id,
                Value = strValue.Replace(";", ","),
                MessageTime = apiRequest.MessageTime
            };

            await InsertObservation(obs);
        }
    }


    private async Task ProcessDrainageObservation(ApiRequest apiRequest, Patient patient)
    {
        _logger.LogDebug("INSERT DrainageObservation");

        if (apiRequest.ObservationData == null || !apiRequest.ObservationData.Time.HasValue ||
            apiRequest.Observations == null)
        {
            _logger.LogWarning("Error Processing Drainage Observation.");
            return;
        }

        var obs = new PatientObservation
        {
            Time = apiRequest.ObservationData.Time.Value,

            Code = apiRequest.ObservationData.Code,
            CodingSystem = apiRequest.ObservationData.CodingSystem,
            PatientId = patient.Id,
            ParentData = new ParentDataClass
            {
                Code = apiRequest.ObservationData.Code,
                CodingSystem = apiRequest.ObservationData.CodingSystem,
                Name = apiRequest.ObservationData.Text
            },
            MessageTime = apiRequest.MessageTime
        };

        if (apiRequest.Observation != null) obs.Time = apiRequest.Observation.Time;

        obs.Value = new PatientDrainagesValue();

        if (obs.Value is not PatientDrainagesValue obsVal)
        {
            _logger.LogDebug("Error Processing Drainage Observation. Value is null. ");
            return;
        }

        foreach (var observation in apiRequest.Observations)
            switch (observation.Code)
            {
                case "138875005":

                    switch (observation.Name ?? "")
                    {
                        case "Tipo de drenaje":
                            obsVal.Type = observation.Value.ToString();
                            break;

                        case "Altura columna(cmH2O)":
                            if (int.TryParse(observation.Value.ToString(), out var valueParsed))
                                obsVal.Height = valueParsed;
                            break;
                    }

                    break;

                case "10546003":
                    obsVal.Location = observation.Value.ToString();
                    break;

                case "56868008":
                    if (int.TryParse(observation.Value.ToString(), out var obsValue)) obsVal.Volume = obsValue;

                    break;
            }

        await InsertObservation(obs);
    }

    /**
     * Las obs que se insertan de forma manual desde nurse deben seguir la logica contraria a las obs
     * recibidas desde el censo
     */
    public async Task SaveRequestNurseObs(ApiRequest apiRequest)
    {
        var patient = await _patientService.FindPatientByApiRequest(apiRequest);

        if (patient == null)
        {
            // NO PATIENTS OR LOCATIONS WERE FOUND 
            _logger.LogWarning(
                "Observation for patient: {apiRequestpatientNumber} with location: {apiRequestlocation} not found, ignoring",
                apiRequest.PatientNumber, apiRequest.Location);
            return;
        }

        if (apiRequest.Observations != null)
            ProcessObservations(apiRequest.Observations, patient, apiRequest.MessageTime,
                apiRequest.ObservationData);
    }


    private async Task CheckRecordings(Patient patient, List<PointOfCare> configs,
        IEnumerable<ConfigObservation> obsConfigList, int defaultValue)
    {
        //paramos grabación si no hay alarmas activas y hay una grabación 
        var recordingEndAfter = obsConfigList
            .Where(c => c.Alarm is { Recording: not null })
            .Select(c => c.Alarm is { Recording: not null } ? c.Alarm.Recording.EndAfter : defaultValue)
            .DefaultIfEmpty(defaultValue)
            .Max();

        var obsListWithRecordingAlarms =
            await _observationRepository.FindLastNotExpiredObservatonsByPatient(patient.Id, "ADAS_ALARM",
                recordingEndAfter);

        if (obsListWithRecordingAlarms.Any())
            return;


        var boxCfg = configs.FirstOrDefault(box => box.Id == patient.PointOfCareId);

        var roomId = boxCfg?.Configuration?.Id;
        if (roomId == null || !int.TryParse(roomId.ToString(), out var roomIdParsed))
            return;

        var currentRecordings = await _recordingService.GetRecordings(roomIdParsed);


        if (currentRecordings == null || !currentRecordings.Any())
            return;

        var manualRecordings =
            currentRecordings.Where(r => r.AlarmType == AlarmEnum.Type.Manual);

        foreach (var rec in manualRecordings)
        {
            _logger.LogDebug("PatientId: {obsid}. Send Stop Manual Recording  Power Off", patient.Id);

            var manualRecording = new ManualRecording
            {
                Recording = new MRecording
                {
                    StartRecordingTime = rec.StartRecordingTime,
                    StopRecordingTime = rec.StopRecordingTime ?? DateTime.Now
                }
            };

            if (boxCfg != null)
                await _recordingService.SendRecordingData(patient, boxCfg, manualRecording, false);
        }
    }

    private async Task CheckRelay(Patient patient,
        IEnumerable<ConfigObservation> obsConfigList, int defaultValue)
    {
        var relayEndAfter = obsConfigList
            .Where(c => c.Alarm is { OpenDoor: not null })
            .Select(c => c.Alarm is { OpenDoor: not null } ? c.Alarm.OpenDoor.EndAfter : defaultValue)
            .DefaultIfEmpty(defaultValue)
            .Max();

        var obsListWithRelayAlarms =
            await _observationRepository.FindLastNotExpiredObservatonsByPatient(patient.Id, "ADAS_ALARM",
                relayEndAfter);

        if (!patient.PointOfCareId.HasValue)
        {
            _logger.LogError("PatientId: {PatId} has no points of Care on check relay", patient.Id);
            return;
        }

        //Si no hay activas apagamos Relay
        var relayConfig = await _pointOfCareService.FindById(patient.PointOfCareId.Value);
        if (relayConfig is { Configuration.RelayIdList: not null } && !obsListWithRelayAlarms.Any())
        {
            _logger.LogDebug("PatientId: {PatId}. Send relay Power Off", patient.Id);
            var relays = _relayService.GetRelayInList(relayConfig.Configuration.RelayIdList);
            relays.ForEach(async void (r) =>
            {
                try
                {
                    await _relayService.PowerOff(r);
                }
                catch (Exception e)
                {
                    _logger.LogError("PatientId: {PatId}. Exception Power Off Relay {exMessage} trace: {exStackTrace}",
                        patient.Id, e.Message, e.StackTrace);
                }
            });
        }
    }

    private async Task CheckBeacon(Patient patient,
        IEnumerable<ConfigObservation> obsConfigList, int defaultValue)
    {
        var beaconEndAfter = obsConfigList
            .Where(c => c.Alarm is { Enabled: true, Beacon.Enabled: true })
            .Select(c => c.Alarm is { Beacon: not null } ? c.Alarm.Beacon.EndAfter : defaultValue)
            .DefaultIfEmpty(defaultValue)
            .Max();

        var obsListWithBeaconAlarms =
            await _observationRepository.FindLastNotExpiredObservatonsByPatient(patient.Id, "ADAS_ALARM",
                beaconEndAfter);


        //TODO: Heredar de la observación el endAfter si el de la baliza está 0

        //Si no tiene alarmas activas y no está ya apagada lo hacemos
        if (!obsListWithBeaconAlarms.Any() && patient.PointOfCareId.HasValue)
        {
            _logger.LogDebug("PatientId: {patientId}. Send Beacon code Power Off", patient.Id);
            await _lightBeaconService.PowerOffLed(patient.PointOfCareId.Value);
        }
    }
}