using adas_core.Application.Services.Interfaces;
using adas_core.Domain.Enums;
using adas_core.Domain.Models;
using adas_core.Domain.Models.AppSettings;
using adas_core.Domain.Models.Pumps;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using MongoDB.Bson;

namespace adas_core.Application.Customizations.H12O.UCIN;

/// <summary>
/// Implements the UCIN-specific clinical calculations that derive secondary
/// <see cref="PatientObservation"/> values (Complexity, Oxygenation_Index, Respiratory, IntravenousLines,
/// Medication, ERMedication, Monitor, Surgery, Temp_Gradient, TAm alert, etc.) from incoming
/// raw observations and active <see cref="PatientTreatment"/> entries, using the catalogue of
/// codes, groups, and types configured in <see cref="ApiSettings"/>.
/// </summary>
public class CalculatedObservations : ICalculatedObservations
{
    private readonly List<string> _complexityObservations =
        ["Respiratory", "IntravenousLines", "Medication", "Surgery", "Weight_Newborn", "Weight_Current", "Monitor"];

    private readonly List<string> _ecmo = [];
    private readonly List<string> _electroencephalogram = [];

    private readonly List<string> _highFrequencyVentilation = [];
    //private readonly List<string> _shift = [];


    private readonly Tuple<IntraVenousLineTypes, int, List<string>>[] _intraVenousLinesTypeValue =
    [
        Tuple.Create(IntraVenousLineTypes.Artery, 5,
            new List<string> { "Catéter arterial", "Catéter UMBILICAL arteria" }),
        Tuple.Create(IntraVenousLineTypes.CentralVein, 4,
            new List<string> { "Catéter venoso CENTRAL", "Catéter UMBILICAL vena" }),
        Tuple.Create(IntraVenousLineTypes.Picc, 3, new List<string> { "Catéter PICC" }),
        Tuple.Create(IntraVenousLineTypes.MiddleLine, 2,
            new List<string> { "Catéter línea media", "Catéter EPICUTÁNEO PERIFÉRICO" }),
        Tuple.Create(IntraVenousLineTypes.Peripheral, 1, new List<string> { "Catéter Venoso PERIFÉRICO" })
    ];

    private readonly List<string> _invasiveVentilation = [];
    private readonly ILogger<CalculatedObservations> _logger;


    private readonly List<string> _medicationBolus = [];

    //private readonly Lazy<IMedicineService> medicineService;
    private readonly IMedicineService _medicineService;

    private readonly List<string> _nonInvasiveVentilation = [];

    //private readonly List<string> Oni = new List<string>();
    private readonly List<string> _notesIndicatingMedication = [];


    private readonly Lazy<IObservationService> _observationService;
    private readonly List<string> _oniCodes = [];
    private readonly List<string> _regionalBrainSaturation = [];
    private readonly List<string> _respiratory = [];


    private readonly Tuple<RespiratoryTypes, int, List<string>>[] _respiratoryTypeValue =
    [
//            Tuple.Create(RespiratoryTypes.INO, 10, new List<string>{ ""}),
        Tuple.Create(RespiratoryTypes.Vafo, 5, new List<string> { "V.A.F.O." }),
        Tuple.Create(RespiratoryTypes.Vmc, 3, new List<string> { "V.M.C." }),
        Tuple.Create(RespiratoryTypes.Vmni, 2, new List<string> { "V.N.I. Ciclada", "CPAP" }),
        Tuple.Create(RespiratoryTypes.NasalCannulas, 1, new List<string> { "Alto Flujo", "Bajo Flujo" }),
        Tuple.Create(RespiratoryTypes.None, 0, new List<string> { "Sin assistance respiratoria" })
    ];

    private readonly List<string> _rxaStatus = ["Concluido"];
    private readonly List<string> _surgery = [];
    private readonly List<string> _surgeryText = [];
    private readonly List<string> _transcutanous = [];
    private readonly Lazy<ITreatmentService> _treatmentService;


    /// <summary>
    /// Initializes a new instance of the <see cref="CalculatedObservations"/> class,
    /// resolving its dependencies (observation, medicine, and treatment services, plus the logger)
    /// from the supplied <see cref="IServiceProvider"/> and loading the configured code catalogues
    /// (EEG, bolus medications, transcutaneous, regional brain saturation, surgery, respiratory,
    /// ONi, ventilation modes, ECMO, and notes indicating medication) from <see cref="ApiSettings"/>.
    /// </summary>
    /// <param name="serviceProvider">The application's service provider used to resolve the required dependencies and configuration.</param>
    public CalculatedObservations(IServiceProvider serviceProvider)
    {
        _observationService = serviceProvider.GetRequiredService<Lazy<IObservationService>>(); //observationService;
        _medicineService = serviceProvider.GetRequiredService<IMedicineService>(); //medicineService;
        _treatmentService = serviceProvider.GetRequiredService<Lazy<ITreatmentService>>(); //treatmentService;

        var apiSettings = serviceProvider.GetRequiredService<IOptions<ApiSettings>>(); //apiSettings;

        _logger = serviceProvider.GetRequiredService<ILogger<CalculatedObservations>>();

        var electroencephalogram = apiSettings.Value.Electroencephalogram ?? null;
        electroencephalogram?.ForEach(x => _electroencephalogram.Add(x.Trim()));

        var medicationBolus = apiSettings.Value.MedicationBolus ?? null;
        medicationBolus?.ForEach(x => _medicationBolus.Add(x.Trim()));

        var transcutaneous = apiSettings.Value.Transcutaneous ?? null;
        transcutaneous?.ForEach(x => _transcutanous.Add(x.Trim()));

        var regionalBrainSaturation = apiSettings.Value.RegionalBrainSaturation ?? null;
        regionalBrainSaturation?.ForEach(x => _regionalBrainSaturation.Add(x.Trim()));

        var surgery = apiSettings.Value.Surgery ?? null;
        surgery?.ForEach(x => _surgery.Add(x.Trim()));

        var surgeryText = apiSettings.Value.SurgeryText ?? null;
        surgeryText?.ForEach(x => _surgeryText.Add(x.Trim()));

        var respiratory = apiSettings.Value.Respiratory ?? null;
        respiratory?.ForEach(x => _respiratory.Add(x.Trim()));
        //inoCode = Respiratory.LastOrDefault();

        var oni = apiSettings.Value.Oni ?? null;
        oni?.ForEach(x => _oniCodes.Add(x.Trim()));

        var highFrequencyVentilation = apiSettings.Value.HighFrequencyVentilation ?? null;
        highFrequencyVentilation?.ForEach(x => _highFrequencyVentilation.Add(x.Trim()));

        var invasiveVentilation = apiSettings.Value.InvasiveVentilation ?? null;
        invasiveVentilation?.ForEach(x => _invasiveVentilation.Add(x.Trim()));

        var nonInvasiveVentilation = apiSettings.Value.NonInvasiveVentilation ?? null;
        nonInvasiveVentilation?.ForEach(x => _nonInvasiveVentilation.Add(x.Trim()));

        //var shift = apiSettings.Value.Shift ?? null;
        //shift?.ForEach(x => _shift.Add(x.Trim()));

        var notesIndicatingMedication =
            apiSettings.Value.NotesIndicatingMedication ?? null;
        notesIndicatingMedication?.ForEach(x => _notesIndicatingMedication.Add(x.Trim()));

        var ecmo = apiSettings.Value.Ecmo ?? null;
        ecmo?.ForEach(x => _ecmo.Add(x.Trim()));
    }

    /// <summary>
    /// Applies the appropriate clinical calculations to a raw patient observation based on its
    /// <see cref="BasePatientObservation.Name"/>, code, coding system, and expiration state, producing
    /// derived observations (respiratory assistance, oxygenation index, ventilation mode, TAm alert,
    /// parsed weight, temperature gradient, intravenous lines, monitor, surgery expiration, complexity,
    /// and hourly/temperature time-increment handling).
    /// </summary>
    /// <typeparam name="T">The concrete observation type, deriving from <see cref="BasePatientObservation"/>.</typeparam>
    /// <param name="obs">The observation to map or transform in-place.</param>
    /// <param name="onlyByName">
    /// Reserved for future use. When <see langword="true"/>, restricts the mapping strategy to
    /// name-based lookups only.
    /// </param>
    /// <returns>
    /// A <see cref="Task{T}"/> that resolves to the (possibly transformed) observation, or
    /// <see langword="null"/> when the input observation has no name.
    /// </returns>
    public async Task<T?> Map<T>(T obs, bool onlyByName) where T : BasePatientObservation
    {
        // Early exit: if the observation has no name, there's nothing to map
        if (string.IsNullOrEmpty(obs.Name)) return obs;

        // Respiratory observations: only process SNOMED-coded respiratory metrics
        if (!string.IsNullOrEmpty(obs.Code) && _respiratory.Contains(obs.Code) && obs.CodingSystem == "SNM")
        {
            _logger.LogDebug("Mapping observation Respiratorio {obs}", obs);
            await CalculateAsistResp(obs);
        }

        // Oxygenation index calculation: triggered by air pressure, FiO2, or PaO2 readings
        if (obs.Name is "AirPressure_Mean" or "FiO2" or "PaO2") await CalculateOxygenationIndex(obs);

        // Ventilation mode evaluation: processes respiratory mode changes
        if (obs.Name == "Resp_Mode")
        {
            _logger.LogDebug("Mapping Resp mode observation {obs}", obs);
            await CalculateVentilationMode(obs);
        }

        // Mean arterial pressure (TAm) alerting: includes gestational age considerations
        if (obs.Name is "TAm" or "Age_Gestational_Fixed" or "Age_Gestational") await CalculateTAmAlert(obs);

        // Weight parsing: only for active (non-expired) patient observations
        if (obs.Name is "Weight_Newborn" or "Weight_Current")
            if (obs is PatientObservation { Expired: false })
                obs = (T)await ParseWeight(obs);

        // Temperature gradient calculation between incubator and patient
        if (obs is { Name: "Temp_Incubator" } or { Name: "Temp_Patient" }) await CalculateTempGradient(obs);

        // Intravenous lines observation processing with potential transformation
        if (obs is { Name: "IntravenousLinesObs" })
        {
            var intraObs = await CalculateIntravenousLineObservation(obs);
            // Apply the transformed observation if calculation produced a result
            if (intraObs != null)
                obs = (T)intraObs;
        }

        // Regional brain saturation monitoring: pCO2tc or transcutaneous O2 with specific codes
        if (obs.Name is "pCO2tc" or "Transcutaneous_O2")
            if (obs.Code != null && _regionalBrainSaturation.Contains(obs.Code))
                await CalculateMonitor(obs);

        // Surgery-related expiration check
        if (obs.Name == "Surgery") await CheckSurgeryExpired(obs);

        // Complexity calculations for predefined complex observation types
        if (obs.Name != null && _complexityObservations.Contains(obs.Name))
        {
            _logger.LogDebug("Mapping observation Complexity {obs}", obs);
            await CalculateComplexity(obs);
        }

        // Hourly accumulation observations: diuresis, drainages, hydric balance, and fluid entries
        // These check for existing observations at the same time and increment values
        if (obs.Name != null && (obs.Name.Equals("Diuresis_Hour") ||
                                 obs.Name.Equals("Drainages_Hour") ||
                                 obs.Name.Equals("Hydric_Balance") ||
                                 obs.Name.Equals("IV_Entries_Hour") ||
                                 obs.Name.Equals("Enteral_Entries_Hour")))
            obs = (T)await CheckObsExistsAndIncrementTime(obs);

        // Temperature observations: handle duplicate time entries by incrementing time
        if (obs.Name != null && (obs.Name.Equals("Temp_Patient") ||
                                 obs.Name.Equals("Temp_Incubator")
            ))
            obs = (T)await CheckObsWithSameTimeExistsAndIncrementTime(obs);

        return obs;
    }


    /// <summary>
    /// Maps a <see cref="PatientTreatment"/> to its processed form, computing the end time of single-dose
    /// treatments, evaluating the medicines associated with the treatment's codes/notes, and dispatching
    /// the appropriate downstream calculations (bolus, monitor, surgery, ECMO complexity, and ONi observations)
    /// based on the configured code catalogues.
    /// </summary>
    /// <param name="treatment">The <see cref="PatientTreatment"/> to be mapped.</param>
    /// <returns>A task that represents the asynchronous mapping operation. The task result contains the processed <see cref="PatientTreatment"/>.</returns>
    /// <exception cref="ArgumentNullException">Thrown when the <paramref name="treatment"/> parameter is <c>null</c>.</exception>
    public async Task<PatientTreatment> Map(PatientTreatment treatment)
    {
        if (treatment.SingleDose)
            treatment = CalculateSingleDoseEndDate(treatment);

        var tempTreatment = treatment;

        await CheckTreatmentMedicines(tempTreatment);


        if (tempTreatment.RequestedGiveCodes.Any())
        {
            if (tempTreatment.RequestedGiveCodes.Any(t =>
                    _medicationBolus.Contains(t.Identifier))) //remove  && t.codingSystem == "FTPCS" not match always
            {
                _logger.LogDebug("Medication Bolus detected. Creating calculated observation");
                await CalculateBolus(tempTreatment);
            }

            if (tempTreatment.RequestedGiveCodes.Any(t => _electroencephalogram.Contains(t.Identifier)))
                await CalculateMonitor(tempTreatment);

            if (tempTreatment.RequestedGiveCodes.Any(t =>
                    _surgery.Contains(t.Identifier) && _surgeryText.Contains(t.Text)))
                await CalculateSurgery(tempTreatment);


            if (treatment.RequestedGiveCodes.Any(t => _ecmo.Contains(t.Identifier)))
                //If ecmo treatment is NW recalculate complexity to show ECMO if is DC recalculate complexity
                await CalculateComplexityOnEcmo(treatment);

            if (tempTreatment.RequestedGiveCodes.Any(t => _oniCodes.Contains(t.Identifier)))
            {
                if (tempTreatment.OrderControl == OrderControlType.Nw)
                {
                    var oniObservation = new PatientObservation
                    {
                        PatientId = treatment.PatientId,
                        Code = "84481000140102",
                        Name = "ONi",
                        CodingSystem = "MDC",
                        Value = tempTreatment.RequestedGiveAmountMinimum ?? '-',
                        Time = tempTreatment.OrderTime ?? DateTime.Now,
                        Units = "ppm"
                    };
                    await _observationService.Value.InsertObservation(oniObservation, mapObs: false);
                }

                if (treatment.OrderControl == OrderControlType.Dc)
                {
                    var lastOnis = await _observationService.Value.FindLastObservations(treatment.PatientId, 1,
                        ["ONi"]);
                    var lastOni = lastOnis.FirstOrDefault();
                    if (lastOni == null) return treatment;
                    lastOni.Expired = true;
                    await _observationService.Value.UpdateObservation(lastOni);
                    await _observationService.Value.InsertObservation(lastOni, false);
                }
            }
        }


        return treatment;
    }

    /// <summary>
    /// Identity mapping for a <see cref="PumpObservation"/>: returns the supplied instance unchanged,
    /// wrapped in a completed task. Provided to satisfy the customization contract; pump observations
    /// do not currently require UCIN-specific calculation.
    /// </summary>
    /// <param name="pumpObservation">The pump observation to map.</param>
    /// <returns>A task containing the same <see cref="PumpObservation"/> instance that was passed in.</returns>
    public async Task<PumpObservation> Map(PumpObservation pumpObservation)
    {
        return await Task.FromResult(pumpObservation);

    }

    /// <summary>
    /// Convenience overload that recalculates the "Medication" and "ERMedication" observations for a
    /// patient using the supplied list of active medicines, building a transient <see cref="PatientTreatment"/>
    /// for the underlying call.
    /// </summary>
    /// <param name="activeMedicines">The list of active <see cref="Medicine"/> instances currently associated with the patient.</param>
    /// <param name="patientId">The unique identifier of the patient.</param>
    /// <returns>A task that represents the asynchronous calculation operation.</returns>
    public async Task CalculateMedicineObservation(List<Medicine> activeMedicines, ObjectId patientId)
    {
        await CalculateMedicineObservation(activeMedicines, [], new PatientTreatment { PatientId = patientId });
    }

    /// <summary>
    /// Recalculates the "OpiateBoluses" observation for a patient based on the bolus treatments
    /// returned by <c>GetActiveBolus</c>. Skips persistence when the value is unchanged from the
    /// last stored observation.
    /// </summary>
    /// <param name="patientId">The unique identifier of the patient.</param>
    /// <returns>A task that represents the asynchronous calculation operation.</returns>
    public async Task CalculateActiveBolus(ObjectId patientId)
    {
        // SIN RXA
        /*
         *   var activeBolus = treatmentService.Value.GetActiveTreatmentsByPatient(patientId)
          .FindAll(p => p.requestedGiveCodes.Any(r => MedicationBolus.Contains(r.identifier)) && p.orderTime > DateTime.Now.AddHours(-12) && p.boloPom);

          var bolusObs = new PatientObservation()
          {
          patientid = patientId,
          time = DateTime.Now,
          //code = "MedicationBolus", //treatment.requestedGiveCode.identifier,
          //codingSystem = "ADAS", //treatment.requestedGiveCode.codingSystem,
          name = "OpiateBoluses",
          codingSystem = "ADAS",
          value = activeBolus.Count
          };

          var lastBolus = observationService.Value.FindLastObservations(patientId, 1, new List<string> { "OpiateBoluses" }).FirstOrDefault();
          if (lastBolus != null && int.Parse(lastBolus.value.ToString()) == int.Parse(bolusObs.value.ToString()))
          {
          return;
          }
          observationService.Value.InsertObservation(bolusObs);
          */
        //Por RXA

        var activeBolus = await GetActiveBolus(patientId, null);
        var bolusObs = new PatientObservation
        {
            PatientId = patientId,
            Time = DateTime.Now,
            //code = "MedicationBolus", //treatment.requestedGiveCode.identifier,
            //codingSystem = "ADAS", //treatment.requestedGiveCode.codingSystem,
            Name = "OpiateBoluses",
            CodingSystem = "ADAS",
            Value = activeBolus.Count
        };


        var lastsBolus = await _observationService.Value.FindLastObservations(patientId, 1, ["OpiateBoluses"]);
        var lastBolus = lastsBolus.FirstOrDefault();
        if (lastBolus != null && int.TryParse(lastBolus.Value.ToString(), out var lastbolus) &&
            int.TryParse(bolusObs.Value.ToString(), out var bolusobs) && lastbolus == bolusobs) return;
        await _observationService.Value.InsertObservation(bolusObs, mapObs: false);
    }

    /// <summary>
    /// Retrieves all treatments currently considered active for the specified patient, delegating
    /// the actual retrieval to the configured <see cref="ITreatmentService"/>.
    /// </summary>
    /// <param name="id">The unique identifier of the patient.</param>
    /// <returns>A collection of active <see cref="PatientTreatment"/> objects for the patient.</returns>
    public async Task<IEnumerable<PatientTreatment?>> GetActiveTreatmentsByPatient(ObjectId id)
    {
        var activeTreatments = await _treatmentService.Value.GetActiveTreatmentsByPatient(id);

        return activeTreatments;
    }

    /// <summary>
    /// Identity mapping for a <see cref="PatientDiagnosis"/>: returns the supplied instance unchanged,
    /// wrapped in a completed task. Provided to satisfy the customization contract; diagnoses do not
    /// currently require UCIN-specific calculation.
    /// </summary>
    /// <param name="diagnosis">The <see cref="PatientDiagnosis"/> to map.</param>
    /// <returns>A task containing the same <see cref="PatientDiagnosis"/> instance that was passed in.</returns>
    public Task<PatientDiagnosis> Map(PatientDiagnosis diagnosis)
    {
        return Task.FromResult(diagnosis);
    }

    /// <summary>
    /// Resolves time-inconsistencies for a new patient observation when it arrives with the same
    /// (down-to-the-second) timestamp as the latest stored observation of the same name. The new
    /// observation's <c>Time</c> is shifted forward by one second, and a fresh <see cref="ObjectId"/>
    /// is generated to avoid duplicate-key collisions.
    /// </summary>
    /// <param name="newObservation">The new patient observation to evaluate for time inconsistencies.</param>
    /// <returns>
    /// A task that represents the asynchronous operation. The task result contains the fixed
    /// <see cref="PatientObservation"/> if the input had a valid name; otherwise, <see langword="null"/>.
    /// </returns>
    public async Task<PatientObservation?> FixTimeInconsistencyWithLast(PatientObservation newObservation)
    {
        if (string.IsNullOrEmpty(newObservation.Name))
        {
            _logger.LogError("Observation name is null or empty: {newObservation}", newObservation);
            return null;
        }

        var lastObservations =
            await _observationService.Value.FindLastObservations(newObservation.PatientId, 1, [newObservation.Name]);

        var lastObservation = lastObservations.FirstOrDefault();

        if (lastObservation != null && DateTime.Compare(
                new DateTime(lastObservation.Time.Year, lastObservation.Time.Month,
                    lastObservation.Time.Day, lastObservation.Time.Hour,
                    lastObservation.Time.Minute, lastObservation.Time.Second),
                new DateTime(newObservation.Time.Year, newObservation.Time.Month,
                    newObservation.Time.Day, newObservation.Time.Hour,
                    newObservation.Time.Minute, newObservation.Time.Second)
            ) >= 0)
            newObservation.Time = lastObservation.Time.AddSeconds(1);

        newObservation.Id = ObjectId.GenerateNewId();

        return newObservation;
    }

    /// <summary>
    /// Identity mapping for a list of <see cref="PatientObservation"/> instances: returns the supplied
    /// list unchanged, wrapped in a completed task. Provided to satisfy the customization contract;
    /// the pre-mapping phase does not currently require UCIN-specific transformation.
    /// </summary>
    /// <param name="listToInsert">The list of patient observations to be pre-mapped.</param>
    /// <returns>A task containing the original list of patient observations.</returns>
    public Task<List<PatientObservation>> PreMapList(List<PatientObservation> listToInsert)
    {
        return Task.FromResult(listToInsert);
    }

    /// <summary>
    /// Identity mapping for a source alarm observation paired with a <see cref="PatientObservationAlarm"/>:
    /// returns the supplied observation unchanged, wrapped in a completed task. Provided to satisfy the
    /// customization contract; alarms are not currently transformed in the UCIN customization.
    /// </summary>
    /// <param name="obs">The patient observation that triggered the alarm.</param>
    /// <param name="alarmToInsert">The alarm metadata to be inserted alongside the observation.</param>
    /// <returns>A task containing the same <see cref="PatientObservation"/> instance that was passed in.</returns>
    public Task<PatientObservation> MapSourceAlarm(PatientObservation obs, PatientObservationAlarm alarmToInsert)
    {
        return Task.FromResult(obs);
    }

    /// <summary>
    /// Placeholder alarm dispatch hook used by the customization contract. The UCIN customization
    /// does not currently implement custom alarm dispatching; calling this method always throws.
    /// </summary>
    /// <param name="obs">The patient observation that triggered the alarm.</param>
    /// <param name="name">The alarm display name.</param>
    /// <param name="code">The optional alarm code from <see cref="AlarmEnum.Name"/>.</param>
    /// <returns>Never returns a result.</returns>
    /// <exception cref="NotImplementedException">Always thrown because alarm dispatch is not implemented in the UCIN customization.</exception>
    public Task SendAlarm(PatientObservation obs, string name, AlarmEnum.Name? code)
    {
        throw new NotImplementedException();
    }

    /// <summary>
    /// When a "Surgery" observation is marked as expired, inserts a follow-up "Surgery" observation
    /// with value <c>0</c> at the same time plus one second, signaling that the surgery complexity
    /// contribution has been removed. Errors are logged and rethrown.
    /// </summary>
    /// <param name="obs">The base patient observation expected to be a <see cref="PatientObservation"/> with <c>Name == "Surgery"</c>.</param>
    /// <returns>A task that represents the asynchronous insert operation.</returns>
    /// <exception cref="InvalidCastException">Thrown when <paramref name="obs"/> cannot be cast to <see cref="PatientObservation"/>.</exception>
    /// <exception cref="Exception">Rethrown after the underlying exception is written to the console for diagnostics.</exception>
    public async Task CheckSurgeryExpired(BasePatientObservation obs)
    {
        try
        {
            var pobs = (PatientObservation)obs;
            if (!pobs.Expired) return;
            var surgeryObs = new PatientObservation
            {
                PatientId = obs.PatientId,
                Name = "Surgery",
                Min = 0,
                Max = 5,
                CodingSystem = "ADAS",
                Time = obs.Time.AddSeconds(1),
                Value = 0,
                Expires = pobs.Expires
            };
            _logger.LogDebug("CheckSurgeryExpired. Inserting: {obs}", surgeryObs.ToString());

            await _observationService.Value.InsertObservation(surgeryObs, mapObs: false);
        }
        catch (Exception e)
        {
            Console.WriteLine(e);
            throw;
        }
    }

    /// <summary>
    /// For hourly accumulation observations, checks whether another observation with the same name
    /// already exists within the same hour and, if so, shifts this observation's <c>Time</c> forward
    /// by one second so the system can identify the most recent entry.
    /// </summary>
    /// <param name="obs">The base patient observation expected to be a <see cref="PatientObservation"/>.</param>
    /// <returns>A task that represents the asynchronous operation. The task result contains the (possibly time-shifted) base patient observation.</returns>
    public async Task<BasePatientObservation> CheckObsExistsAndIncrementTime(BasePatientObservation obs)
    {
        var pobs = (PatientObservation)obs;

        var lastObsFromHour =
            await _observationService.Value.FindLastBeforeDate(pobs.PatientId, pobs.Time.AddMinutes(59), obs.Name);

        if (lastObsFromHour != null &&
            DateTime.Compare(new DateTime(lastObsFromHour.Time.Year, lastObsFromHour.Time.Month,
                    lastObsFromHour.Time.Day, lastObsFromHour.Time.Hour, 0, 0)
                , new DateTime(pobs.Time.Year, pobs.Time.Month, pobs.Time.Day, pobs.Time.Hour, 0, 0)) == 0)
            pobs.Time = lastObsFromHour.Time.AddSeconds(1);

        return pobs;
    }

    /// <summary>
    /// For temperature observations, checks whether another observation with the same name already
    /// exists at the exact same timestamp and, if so, shifts this observation's <c>Time</c> forward
    /// by one second so the system can identify the most recent entry.
    /// </summary>
    /// <param name="obs">The base patient observation expected to be a <see cref="PatientObservation"/>.</param>
    /// <returns>A task that represents the asynchronous operation. The task result contains the (possibly time-shifted) base patient observation.</returns>
    public async Task<BasePatientObservation> CheckObsWithSameTimeExistsAndIncrementTime(BasePatientObservation obs)
    {
        var pobs = (PatientObservation)obs;
        var existObsWithSameTime =
            await _observationService.Value.FindAnyWithSameDate(pobs.PatientId, pobs.Time, pobs.Name);

        if (existObsWithSameTime != null && existObsWithSameTime.Any()) pobs.Time = pobs.Time.AddSeconds(1);

        return pobs;
    }

    /// <summary>
    /// Computes the Oxygenation Index (PMAP × FiO2 × 100 / PaO2) using the supplied observation plus
    /// the latest stored <c>AirPressure_Mean</c>, <c>FiO2</c>, and <c>PaO2</c> values. Persists the
    /// resulting <c>Oxygenation_Index</c> observation with a 10-second expiration matching FiO2's
    /// expiration window. Skipped gracefully when PaO2 is zero, when any of the inputs cannot be
    /// parsed, or when an exception is caught and logged.
    /// </summary>
    /// <param name="obs">The observation that triggered the calculation (one of <c>AirPressure_Mean</c>, <c>FiO2</c>, or <c>PaO2</c>).</param>
    /// <returns>A task that represents the asynchronous calculation operation.</returns>
    public async Task CalculateOxygenationIndex(BasePatientObservation obs)
    {
        var toSearchList = new List<string>();
        PatientObservation? airPressMeanObs = null;
        PatientObservation? fiO2Obs = null;
        PatientObservation? paO2Obs = null;

        try
        {
            toSearchList.AddRange(new List<string> { "AirPressure_Mean", "FiO2", "PaO2" });

            var values = await _observationService.Value.FindLastObservations(obs.PatientId, 1, toSearchList);

            switch (obs.Name)
            {
                case "AirPressure_Mean":
                    airPressMeanObs = (PatientObservation)obs;
                    fiO2Obs = values.FirstOrDefault(o => o.Name == "FiO2");
                    paO2Obs = values.FirstOrDefault(o => o.Name == "PaO2");
                    break;

                case "FiO2":
                    fiO2Obs = (PatientObservation)obs;
                    airPressMeanObs = values.FirstOrDefault(o => o.Name == "AirPressure_Mean");
                    paO2Obs = values.FirstOrDefault(o => o.Name == "PaO2");
                    break;

                case "PaO2":
                    paO2Obs = (PatientObservation)obs;
                    fiO2Obs = values.FirstOrDefault(p => p.Name == "FiO2");
                    airPressMeanObs = values.FirstOrDefault(o => o.Name == "AirPressure_Mean");
                    break;
            }


            //Controlar que fio2 no haya expirada al recogerla
            if (fiO2Obs != null && paO2Obs != null && airPressMeanObs != null)
                if (int.TryParse(airPressMeanObs.Value.ToString(), out var airPressureMean) &&
                    int.TryParse(fiO2Obs.Value.ToString(), out var fio2) &&
                    int.TryParse(paO2Obs.Value.ToString(), out var pao2))
                {
                    if (pao2 == 0)
                    {
                        _logger.LogWarning(
                            "Calculate Oxygenation Index observation. PaO2: {pao2}, observation: {obsName}", pao2,
                            obs.Name);
                        return;
                    }

                    var oxygenationIndexValue = airPressureMean * fio2 * 100 / pao2;

                    var oxygenationIndexObs = new PatientObservation
                    {
                        PatientId = obs.PatientId,
                        Name = "Oxygenation_Index",
                        CodingSystem = "ADAS",
                        Value = oxygenationIndexValue,
                        Time = DateTime.Now,
                        Expires = 10 //Expires in 10 seg because its FiO2 expire time.
                    };

                    await _observationService.Value.InsertObservation(oxygenationIndexObs, mapObs: false);
                }
        }
        catch (Exception ex)
        {
            _logger.LogError("Error calculate Oxygenation Index observation. Exception: {exMessage}", ex.Message);
        }
    }

    /// <summary>
    /// Sets the <c>EndTime</c> of a single-dose treatment to 8 hours after its <c>OrderTime</c>
    /// (or 8 hours from now when <c>OrderTime</c> is <see langword="null"/>).
    /// </summary>
    /// <param name="treatment">The single-dose treatment whose end time is to be calculated.</param>
    /// <returns>The same <see cref="PatientTreatment"/> instance with its <c>EndTime</c> updated.</returns>
    private static PatientTreatment CalculateSingleDoseEndDate(PatientTreatment treatment)
    {
        /* Deprecated calc finish treatment date on shift end. Now every treatment single dose is last 8 Hours
           DateTime now = DateTime.UtcNow;
         //Turno actual, tenemos que poner como fecha fin la fecha de inicio del turno siguiente
         var shiftNow = Shift.Where(
           i => DateTime.ParseExact(i, "HH:mm", CultureInfo.InvariantCulture).Ticks < DateTime.Now.Ticks
           ).LastOrDefault();

         var index = Shift.IndexOf(shiftNow);

         string endDateShift;

         if (Shift.Count <= index + 1) endDateShift = Shift[0];
         else endDateShift = Shift[index + 1];

         var HourMinutes = endDateShift.Split(':');

         if (now.Hour.CompareTo(int.Parse(HourMinutes[0])) > 0)
             now = now.AddDays(1);

         DateTime finisTreatmentDate = new DateTime(now.Year, now.Month, now.Day, int.Parse(HourMinutes[0]), int.Parse(HourMinutes[1]), 0);
        */
        treatment.EndTime = treatment.OrderTime?.AddHours(8) ?? DateTime.Now.AddHours(8);
        return treatment;
    }

    /// <summary>
    /// Resolves the medicines associated with a treatment (by matching its codes and notes against the
    /// medicine catalogue, falling back to <c>NotesIndicatingMedication</c> patterns), aggregates the
    /// patient's active treatments and medicines, detects parenteral nutrition (NPT) treatments, and
    /// triggers <c>CalculateMedicineObservation</c> to update the Medication and ERMedication observations.
    /// </summary>
    /// <param name="treatment">The treatment whose medicines should be checked.</param>
    /// <returns>A task that represents the asynchronous check operation.</returns>
    public async Task CheckTreatmentMedicines(PatientTreatment treatment)
    {
        //sI TIENE UNA NOTA CON NPT SON DE TIPO NUTRICIÓN PARENTERAL Y SUMAN 1
        var listNotesCode = treatment.Notes.Select(note => note.Comment).ToList();

        if (treatment.RequestedGiveCodes.Any())
            listNotesCode.AddRange(treatment.RequestedGiveCodes.Select(treatmentCodes => treatmentCodes.Identifier));

        //var medicines = medicineService.Value.GetByCodeOrNote(listNotesCode);
        var medicines = await _medicineService.GetByCodeOrNote(listNotesCode);
        var parentalNutritionMedicine = await CalculateParentalNutritionMedicine(treatment);
        medicines.Add(parentalNutritionMedicine);

        //If not detect any medicine in medicine table, check if contáis NTE with, if it's true, it's a medicine.
        /*
         NTE|||Medicación|^OrderDefinition\r

         NTE|||Perfusiones|^OrderDefinition\r

         NTE|||SUEROS Y HEMODER|^OrderDefinition\r
         */
        if (medicines is { Count: 0 })
        {
            var isMedicine = treatment.Notes.Any(n => _notesIndicatingMedication.Contains(n.Comment));

            if (isMedicine && treatment.RequestedGiveCodes is { Count: > 0 })
                treatment.RequestedGiveCodes.ForEach(c =>
                {
                    if (!string.IsNullOrEmpty(c.Identifier))
                        medicines.Add(new Medicine
                        {
                            Codes = [c.Identifier],
                            Name = c.Text
                        });
                });
        }

        if (medicines.Any())
        {
            if (!string.IsNullOrEmpty(treatment.RequestedGiveTreatment))
                try
                {
                    medicines =
                    [
                        new Medicine
                        {
                            Name = treatment.RequestedGiveTreatment,
                            Type = medicines.FindAll(t => t.Type.Any())
                                .Select(m => m.Type.Aggregate((x, y) => x + "," + y)).Distinct().ToList(),
                            Codes = medicines.FindAll(t => t.Codes.Any())
                                .Select(m => m.Codes.Aggregate((x, y) => x + "," + y)).Distinct().ToList(),
                            Group = medicines.FindAll(t => t.Group.Any())
                                .Select(m => m.Group.Aggregate((x, y) => x + "," + y)).Distinct().ToList(),
                            Notes = medicines.FindAll(t => t.Notes.Any())
                                .Select(m => m.Notes.Aggregate((x, y) => x + "," + y)).Distinct().ToList()
                        }
                    ];
                }
                catch (Exception ex)
                {
                    _logger.LogError("Error Checking Treatment Medicines. {medicines} . Exception {ex}",
                        string.Join(",", medicines), ex);
                }

            var _ = await GetActiveTreatmentsByPatient(treatment.PatientId);
            var activeTreatments = _.ToList();
            //var activeMedicines = medicineService.Value.GetMedicinesOfTreatments(activeTreatments);
            var __ = await _medicineService.GetMedicinesOfTreatments(activeTreatments);
            var activeMedicines = __.ToList();

            var activeNptTreatments =
                activeTreatments.FindAll(t => t?.Notes.FirstOrDefault(n => n.Comment == "NPT") != null);
            if (activeNptTreatments.Count > 0)
            {
                //activeMedicines.AddRange((IEnumerable<Medicine>)activeNPTTreatments.Select(async s => await CalculateParentalNutritionMedicine(s)).Where(parentalMedicine => parentalMedicine != null));
                //activeMedicines.AddRange(await Task.WhenAll(activeNPTTreatments.Select(async s => await CalculateParentalNutritionMedicine(s))).Where(parentalMedicine => parentalMedicine != null));

                var tasks = activeNptTreatments.Select(async s => await CalculateParentalNutritionMedicine(s));
                var taskResults = await Task.WhenAll(tasks);
                var filteredMedicines = taskResults.ToList();
                if (filteredMedicines.Any()) filteredMedicines.ForEach(m => { activeMedicines.Add(m); });
            }

            await CalculateMedicineObservation(activeMedicines, medicines, treatment);
        }
    }

    /// <summary>
    /// Builds a synthetic <see cref="Medicine"/> representing parenteral nutrition (NPT) when the
    /// treatment's notes indicate a NPT order. Lipid-based NPTs are tagged with
    /// <c>ParenteralNutritionLipids</c>; all other NPTs are tagged with <c>ParenteralNutrition</c>.
    /// </summary>
    /// <param name="treatment">The treatment to inspect for NPT indicators, or <see langword="null"/>.</param>
    /// <returns>A task that resolves to a <see cref="Medicine"/> instance describing the parenteral nutrition, or an unnamed, untyped instance when no NPT notes are present.</returns>
    private static Task<Medicine> CalculateParentalNutritionMedicine(PatientTreatment? treatment)
    {
        List<string> medicineType = [];
        Note? commentType = null;
        if (treatment?.Notes.FirstOrDefault(n => n.Comment == "NPT") != null)
        {
            commentType = treatment.Notes.FirstOrDefault(n => n.CommentType == "formularybaseformulation");


            if (treatment.Notes.FirstOrDefault(n =>
                    n.Comment is "LÍPIDOS NEONATALES AL 20%" or "LÍPIDOS NEONATALES AL 20% CON...") != null)
                medicineType = [nameof(MedicineEnum.Types.ParenteralNutritionLipids)];
            else
                medicineType = [nameof(MedicineEnum.Types.ParenteralNutrition)];
        }

        var medicine = new Medicine
        {
            Type = medicineType,
            Name = commentType != null ? commentType.Comment : "UnNamed"
        };
        return Task.FromResult(medicine);
    }

    /// <summary>
    /// Maintains the patient's active medicine list according to the treatment's <c>OrderControl</c>
    /// (<c>NW</c> adds, <c>XO</c> adds if missing, <c>DC</c> removes), counts the distinct medicine
    /// types, persists the <c>Medication</c> observation if its value changed, and then recalculates
    /// the <c>ERMedication</c> observation.
    /// </summary>
    /// <param name="activeMedicines">The list of active medicines for the patient. Modified in-place.</param>
    /// <param name="medicines">The medicines to add or remove depending on <c>OrderControl</c>.</param>
    /// <param name="treatment">The treatment that triggered the recalculation.</param>
    /// <returns>A task that represents the asynchronous calculation operation.</returns>
    public async Task CalculateMedicineObservation(List<Medicine> activeMedicines, List<Medicine> medicines,
        PatientTreatment treatment)
    {
        var medicinesWithType = activeMedicines.FindAll(m => m.Type.Any());

        foreach (var medicine in medicines)
            if (treatment.OrderControl is OrderControlType.Nw or OrderControlType.Xo)
            {
                if (treatment.StartTime != null && DateTime.UtcNow.CompareTo(treatment.StartTime) < 0) return;

                if (treatment.OrderControl == OrderControlType.Nw)
                    activeMedicines.Add(medicine);

                if (treatment.OrderControl == OrderControlType.Xo
                    && activeMedicines.All(m => m.Name != medicine.Name)) activeMedicines.Add(medicine);

                if (medicinesWithType != null &&
                    medicinesWithType.All(m => m.Type != medicine.Type))
                    medicinesWithType.Add(medicine);
            }
            else if (treatment.OrderControl == OrderControlType.Dc)
            {
                if (medicinesWithType != null)
                {
                    medicinesWithType.Remove(medicine);
                    activeMedicines.RemoveAll(m => m.Name == medicine.Name);
                }
            }

        var medicineTypes = medicinesWithType!.SelectMany(m => m.Type.Select(t => new { type = t }))
            .GroupBy(x => x.type).Count();

        var medicineObs = new PatientObservation
        {
            PatientId = treatment.PatientId,
            Name = "Medication",
            CodingSystem = "ADAS",
            Value = medicineTypes,
            Time = DateTime.Now
        };

        var lastMedicationsObs = await _observationService.Value.FindLastObservations(treatment.PatientId, 1,
            ["Medication"]);
        var lastMedicationObs = lastMedicationsObs.FirstOrDefault();
        if (lastMedicationObs == null || !lastMedicationObs.Value.Equals(medicineObs.Value))
            await _observationService.Value.InsertObservation(medicineObs, mapObs: false);

        await CalculateErMedication(activeMedicines, treatment);
    }

    /// <summary>
    /// Calculates the patient's risk level from their active medicines and persists the
    /// <c>ERMedication</c> observation with min/max bounds of 0 and 5, skipping persistence
    /// when the value matches the last stored observation.
    /// </summary>
    /// <param name="activeMedicines">The list of active medicines used to derive the risk level.</param>
    /// <param name="treatment">The treatment that triggered the recalculation.</param>
    /// <returns>A task that represents the asynchronous calculation operation.</returns>
    private async Task CalculateErMedication(List<Medicine> activeMedicines, PatientTreatment treatment)
    {
        //var activeTreatments = treatmentService.Value.GetActiveTreatmentsByPatient(treatment.patientid);
        var result = await CalculateErMedicineLevels(activeMedicines);


        var erMedicationObs = new PatientObservation
        {
            Name = "ERMedication",
            CodingSystem = "ADAS",
            PatientId = treatment.PatientId,
            Time = DateTime.Now,
            Min = 0,
            Max = 5,
            Value = result
        };

        var lastErMedicationsObsList = await _observationService.Value.FindLastObservations(treatment.PatientId, 1,
            ["ERMedication"]);
        var lastErMedicationObs = lastErMedicationsObsList.FirstOrDefault();
        if (lastErMedicationObs == null || !lastErMedicationObs.Value.Equals(erMedicationObs.Value))
            await _observationService.Value.InsertObservation(erMedicationObs, mapObs: false);
    }

    /// <summary>
    /// Maps the patient's active medicine list to a discrete risk level (0 to 5) based on medicine
    /// types and groups: lipidic parenteral nutrition or multiple high-risk medicines map to 5,
    /// non-lipid NPT or one or two high-risk medicines map to 4, metabolic medicines map to 2,
    /// remaining medicines map to 1, and a list containing only iron/vitamin D maps to 0.
    /// </summary>
    /// <param name="activeMedicines">The list of active medicines to evaluate.</param>
    /// <returns>A task that resolves to the integer risk level (0–5).</returns>
    private static Task<int> CalculateErMedicineLevels(List<Medicine> activeMedicines)
    {
        var ironVitaminDCodes = new List<string> { "374424002", "175041000140104" };

        //Aquí a cambiar que la nutrición parenteral siempre sume 4 no 5 quitar de medicinas de 5 solo suma 5 en caso de ser
        //Los lípidos son dos órdenes de NP con el segmento RXO6.2 (NPT: 1 ml LÍPIDOS NEONATALES AL 20% CON MEDICAMENTOS 1 o
        //NPT: 1 ml LÍPIDOS NEONATALES AL 20% 1)

        var medicines5Points = activeMedicines.Where(m =>
                m.Type.Contains(nameof(MedicineEnum.Types.Pge1)) ||
                m.Type.Contains(nameof(MedicineEnum.Types.Insulin)) ||
                m.Group.Contains(nameof(MedicineEnum.Group.DoubleSignature)) ||
                m.Group.Contains(nameof(MedicineEnum.Group.Vasoactive)) ||
                m.Type.Contains(nameof(MedicineEnum.Types.ParenteralNutritionLipids))
            )
            .ToList();

        if (medicines5Points.Count > 2 ||
            activeMedicines.FirstOrDefault(m =>
                m.Type.Contains(nameof(MedicineEnum.Types.ParenteralNutritionLipids))) !=
            null) return Task.FromResult(5);

        if (medicines5Points.Count is > 0 and <= 2 ||
            activeMedicines.FirstOrDefault(m => m.Type.Contains(nameof(MedicineEnum.Types.ParenteralNutrition))) !=
            null
           ) return Task.FromResult(4);

        var medicines2Points = activeMedicines.Where(m =>
            m.Group.Contains(nameof(MedicineEnum.Group.Metabolic))
        ).ToList();

        if (medicines2Points.Count > 0) return Task.FromResult(2);

        //All medicines without type/group and not vitamin D or Iron

        var medicines1Point = activeMedicines.FindAll(m => !m.Codes.Any(c => ironVitaminDCodes.Contains(c)));

        if (medicines1Point.Count > 0) return Task.FromResult(1);


        var ironVitaminDMedicines = activeMedicines.Where(m =>
            m.Codes.Any(c => ironVitaminDCodes.Contains(c))
        ).ToList();

        if (ironVitaminDMedicines.Count == activeMedicines.Count || activeMedicines.Count == 0)
            return Task.FromResult(0);

        return Task.FromResult(1);
    }

    /// <summary>
    /// Maps a <c>Resp_Mode</c> observation's value to a discrete <c>Resp_Type</c> (HighFrequencyVentilation,
    /// Invasive, NonInvasive, or None) by matching it against the configured catalogues for each mode.
    /// </summary>
    /// <param name="obs">The respiratory-mode observation expected to be a <see cref="PatientObservation"/>.</param>
    /// <returns>A task that represents the asynchronous insert operation.</returns>
    public async Task CalculateVentilationMode(BasePatientObservation obs)
    {
        var pobs = (PatientObservation)obs;
        var respTypeObs = new PatientObservation
        {
            Name = "Resp_Type",
            CodingSystem = "ADAS",
            PatientId = obs.PatientId,
            Time = obs.Time
        };

        var strValue = pobs.Value.ToString();


        respTypeObs.Value = _highFrequencyVentilation.Contains(strValue ?? string.Empty)
            ? nameof(RespirationType.HighFrequencyVentilation)
            : _invasiveVentilation.Contains(strValue ?? string.Empty)
                ? respTypeObs.Value = nameof(RespirationType.Invasive)
                : _nonInvasiveVentilation.Contains(strValue ?? string.Empty)
                    ? respTypeObs.Value = nameof(RespirationType.NonInvasive)
                    : nameof(RespirationType.None);

        await _observationService.Value.InsertIfChanged("Resp_Type", respTypeObs);
    }

    /// <summary>
    /// Calculates the patient's respiratory assistance score from the supplied observation's
    /// <c>Value</c> (matched against the configured respiratory-type catalogue) or from the latest
    /// stored <c>Resp_Mode</c> when the supplied observation is <c>ONi</c>. Active ONi treatment
    /// (or a non-expired last ONi observation) forces the score to 10. Persists the resulting
    /// <c>Respiratory</c> observation and returns the original input unchanged.
    /// </summary>
    /// <param name="obs">The base patient observation that triggered the calculation (expected to be a <see cref="PatientObservation"/>).</param>
    /// <returns>A task that resolves to the same base patient observation that was passed in.</returns>
    public async Task<BasePatientObservation> CalculateAsistResp(BasePatientObservation obs)
    {
        //Comprobar si hay un resp_type más nuevo y si no lo hay ignorar

        var pobs = (PatientObservation)obs;
        var lastsAsistResp = await _observationService.Value.FindLastObservations(obs.PatientId, 1, ["Resp_Mode"]);
        var lastAsistResp = lastsAsistResp.FirstOrDefault();
        if (lastAsistResp != null && lastAsistResp.Time.CompareTo(obs.Time) > 0 &&
            lastAsistResp.Name != "ONi") return obs;

        var calculatedRespiratory = new PatientObservation
        {
            PatientId = obs.PatientId,
            Time = DateTime.Now,
            CodingSystem = "ADAS",
            Name = "Respiratory",
            Value = 0
        };

        if (obs.Name == "ONi")
        {
            //find last assist resp obs
            if (lastAsistResp != null)
            {
                var respiratoryValue = _respiratoryTypeValue.FirstOrDefault(r => r.Item3.Contains(lastAsistResp.Value));
                if (respiratoryValue != null) calculatedRespiratory.Value = respiratoryValue.Item2;
            }
        }
        else
        {
            var respiratoryValue = _respiratoryTypeValue.FirstOrDefault(r => r.Item3.Contains(pobs.Value));

            if (respiratoryValue != null) calculatedRespiratory.Value = respiratoryValue.Item2;
        }


        var isIno = !string.IsNullOrEmpty(obs.Code) && _oniCodes.Contains(obs.Code);

        if (isIno && !pobs.Expired) calculatedRespiratory.Value = 10;


        //Check if last ino exist and is not expired 
        var lastsIno = await _observationService.Value.FindLastObservations(obs.PatientId, 1, ["ONi"]);
        var lastIno = lastsIno.FirstOrDefault();
        if (lastIno is { Expired: false }) calculatedRespiratory.Value = 10;

        await _observationService.Value.InsertObservation(calculatedRespiratory, mapObs: false);

        return obs;
    }

    /// <summary>
    /// Updates the synthetic <c>ECMO</c> observation according to the treatment's <c>OrderControl</c>:
    /// <c>NW</c> sets it to <c>NW</c>, <c>DC</c> sets it to <c>XO</c> if any other ECMO treatment
    /// remains active or to <c>DC</c> otherwise, and any other <c>OrderControl</c> is ignored.
    /// In every case the change is followed by a complexity recalculation via <c>CalculateComplexity</c>.
    /// </summary>
    /// <param name="treatment">The treatment whose ECMO state is being applied.</param>
    /// <returns>A task that represents the asynchronous recalculation operation.</returns>
    private async Task CalculateComplexityOnEcmo(PatientTreatment treatment)
    {
        var ecmoObs = new PatientObservation
        {
            PatientId = treatment.PatientId,
            Name = "ECMO"
        };

        switch (treatment.OrderControl)
        {
            case OrderControlType.Nw:
                ecmoObs.Value = OrderControlType.Nw;

                break;
            case OrderControlType.Dc:
                var haveEcmo = await _treatmentService.Value.GetActiveTreatmentsByPatient(treatment.PatientId);


                ecmoObs.Value = haveEcmo.Any(t => t != null && t.RequestedGiveCodes.Any(r =>
                    _ecmo.Contains(r.Identifier) &&
                    t.PlacerOrder?.EntityIdentifier != treatment.PlacerOrder?.EntityIdentifier))
                    ? OrderControlType.Xo
                    : OrderControlType.Dc;

                break;

            default:
                return;
        }

        await CalculateComplexity(ecmoObs);
    }

    /// <summary>
    /// Recalculates the patient's overall <c>Complexity</c> observation from scratch using the latest
    /// values for the configured complexity-contributing observations (weight, medication, surgery,
    /// intravenous lines, monitor, and respiratory), the active ECMO treatment state, and the
    /// optionally supplied <paramref name="obs"/> which is preferred over the database value when
    /// it is newer and not expired. The result is persisted only when its integer value differs
    /// from the last stored complexity observation, with time inconsistencies resolved via
    /// <c>FixTimeInconsistencyWithLast</c>. Returns the original observation unchanged.
    /// </summary>
    /// <param name="obs">The base patient observation that triggered the recalculation.</param>
    /// <param name="ignoreObs">When <see langword="true"/>, the supplied <paramref name="obs"/> is not added to the calculation inputs (useful for synthetic observations like ECMO).</param>
    /// <returns>A task that resolves to the same base patient observation that was passed in.</returns>
    public async Task<BasePatientObservation> CalculateComplexity(BasePatientObservation obs, bool ignoreObs = false)
    {
        try
        {
            var logUid = Guid.NewGuid();
            var complexity = new PatientObservation
            {
                PatientId = obs.PatientId,
                Time = DateTime.UtcNow,
                CodingSystem = "ADAS",
                Name = "Complexity",
                Value = 0
            };
            var complexityValue = 0;
            var activeTreatments = await _treatmentService.Value.GetActiveTreatmentsByPatient(obs.PatientId);
            var haveEcmo = activeTreatments.Any(t =>
                t != null && t.RequestedGiveCodes.Any(r => _ecmo.Contains(r.Identifier)));
            var pobs = obs as PatientObservation;

            var complexityObservations =
                await _observationService.Value.FindLastObservations(obs.PatientId, 1, _complexityObservations);

            //Nos acaba de entrar, la usamos por delante de la última de base de datos 
            //21-02-22 No es correcto, solo remover si el tiempo de esa observación que entra es más nuevo que la de bd
            var actualObsInBd = complexityObservations.FirstOrDefault(o => o.Name == obs.Name);
            if (actualObsInBd != null && obs.Time > actualObsInBd.Time)
                complexityObservations.RemoveAll(it =>
                    it.Name == obs.Name);

            if (!ignoreObs && pobs is { Expired: false }) complexityObservations.Add(pobs);

            foreach (var observation in complexityObservations)
                switch (observation.Name)
                {
                    case "Weight_Current":
                        //  if(observation.expired) break; A este no le importa que esté expirado, lo tiene que hacer siempre.
                        var weightCurrentValue = await CalculateWeight(observation);

                        _logger.LogDebug(
                            "complexity for patient {patientid} id log{logUID} =>  Weight_Current plus complexity: {WeightCurrentValue}",
                            observation.PatientId, logUid, weightCurrentValue);
                        complexityValue += weightCurrentValue;
                        break;

                    case "Weight_Newborn": //Only count newborn weight when weight current don't exist.
                        if (complexityObservations.FirstOrDefault(o => o.Name == "Weight_Current") == null)
                        {
                            var weightNewbornValue = await CalculateWeight(observation);
                            _logger.LogDebug(
                                "complexity for patient {patientid} id log{logUID} =>  Weight_Newborn plus complexity: {Weight_NewbornValue}",
                                observation.PatientId, logUid, weightNewbornValue);
                            complexityValue += weightNewbornValue;
                        }

                        break;

                    case "Medication":
                        if (observation.Expired) break;
                        var value = observation.Value.ToString();

                        if (!int.TryParse(value, out var valueParsed))
                            valueParsed = 0;

                        _logger.LogDebug(
                            "complexity for patient {patientid} id log{logUID} =>  Medication plus complexity: {valueParsed}",
                            observation.PatientId, logUid, valueParsed);
                        complexityValue += valueParsed;
                        break;

                    case "Surgery":
                        if (observation.Expired) break;
                        var surgeryValue = Convert.ToInt32(observation.Value);
                        _logger.LogDebug(
                            "complexity for patient {patientid} id log{logUID} =>  Surgery plus complexity: {surgeryValue}",
                            observation.PatientId, logUid, surgeryValue);
                        complexityValue += surgeryValue;
                        break;

                    case "IntravenousLines":
                        if (observation.Expired) break;
                        var intravenousLinesValue = Convert.ToInt32(observation.Value);
                        _logger.LogDebug(
                            "complexity for patient {patientid} id log{logUID} =>  IntravenousLines plus complexity: {IntravenousLinesValue}",
                            observation.PatientId, logUid, intravenousLinesValue);
                        complexityValue += intravenousLinesValue;
                        break;

                    case "Monitor":
                        if (observation.Expired) continue;
                        var monitorValue = Convert.ToInt32(observation.Value);
                        _logger.LogDebug(
                            "complexity for patient {patientid} id log{logUID} =>  monitor plus complexity: {monitorValue}",
                            observation.PatientId, logUid, monitorValue);
                        complexityValue += monitorValue;
                        break;

                    case "Respiratory":
                        if (observation.Expired) break;
                        var respiratoryValue = Convert.ToInt32(observation.Value);
                        _logger.LogDebug(
                            "complexity for patient {patientid} id log{logUID} =>  respiratory plus complexity: {respiratoryValue}",
                            observation.PatientId, logUid, respiratoryValue);
                        complexityValue += respiratoryValue;
                        break;
                }

            complexity.Value = complexityValue;

            if (haveEcmo && obs.Name != "ECMO")
                //complexity.value = $"ECMO ({complexityValue})";
                complexity.Value = $"{complexityValue} ECMO";

            if (complexityObservations.Any(o => o.Name == "ECMO"))
            {
                var ecmoObs = complexityObservations.FirstOrDefault(o => o.Name == "ECMO");

                if (Enum.TryParse(typeof(OrderControlType), ecmoObs?.Value.ToString(), out var treatmentControl))
                    switch (treatmentControl)
                    {
                        case OrderControlType.Dc:
                            break;
                        //case OrderControlType.XO:
                        //case OrderControlType.NW:
                        default:
                            //complexity.value = $"ECMO ({complexityValue})";
                            complexity.Value = $"{complexityValue} ECMO";
                            //var pObs = await FixTimeInconsistencyWithLast(complexity);
                            if (pobs != null)
                                await _observationService.Value.InsertObservation(pobs);
                            break;
                    }
            }

            var lastsObsComplexity =
                await _observationService.Value.FindLastObservations(obs.PatientId, 1, [complexity.Name]);
            var lastObsComplexity = lastsObsComplexity.FirstOrDefault();
            if (lastObsComplexity is not null)
            {
                var lastObsParsed = int.TryParse(lastObsComplexity.Value.ToString(), out var lastObsValue);

                if (lastObsParsed && int.TryParse(complexity.Value.ToString(), out var intValue) &&
                    lastObsValue == intValue) return obs;
            }

            var fixedTimeComplexity = await FixTimeInconsistencyWithLast(complexity);
            _logger.LogDebug(
                "complexity for patient {patientid} id log{logUID} =>  inserting complexity value: {complexityValue} for patient: {complexityPatientid} at time {fixedTimeComplexityTime}",
                complexity.PatientId, logUid, complexity.Value, complexity.PatientId, fixedTimeComplexity?.Time);

            if (fixedTimeComplexity != null)
                await _observationService.Value.InsertObservation(fixedTimeComplexity, mapObs: false);

            return obs;
        }
        catch (Exception ex)
        {
            _logger.LogError("Error calculating complexity. Exception: {ex}", ex);
            return obs;
        }
    }

    /// <summary>
    /// Recomputes the <c>IntravenousLines</c> complexity score by reconciling the incoming
    /// <c>IntravenousLinesObs</c> against the patient's active intravenous lines. Detects and
    /// corrects common ICCA mis-clicks (inserts incorrectly carrying a remove time), updates an
    /// existing line in place when only the duration changed, and otherwise adds the new line to
    /// the active set. The aggregated score is capped at 10 and persisted with bounds [0, 10].
    /// <c>InvalidCastException</c>s are logged and swallowed.
    /// </summary>
    /// <param name="obs">The base patient observation that triggered the calculation (expected to be a <see cref="PatientObservation"/> whose <c>Value</c> is a <see cref="PatientIntravenousLinesValue"/>).</param>
    /// <returns>
    /// A task that resolves to the original base patient observation when processing succeeds or
    /// fails gracefully with logging; resolves to <see langword="null"/> when the function returns
    /// early because the incoming observation is a duplicate insert (in which case the caller should
    /// not overwrite the stored observation).
    /// </returns>
    public async Task<BasePatientObservation?> CalculateIntravenousLineObservation(BasePatientObservation obs)
    {
        try
        {
            var pobs = (PatientObservation)obs;
            var intravenousLineValue = (PatientIntravenousLinesValue)pobs.Value;

            //Check for doctor miss click on ICCA. sometimes they put insert with remove time
            //when it has a remove time always is remove, never insert
            if (intravenousLineValue is { Action: "Insertado", RemoveTime: not null })
                intravenousLineValue.Action = "Retirado";


            //GetByCodeSysAndCode active Intravenous observations
            //var intraVenousLineObservations = observationService.Value.FindLastObservations(obs.patientid, 1, new List<string> { obs.name });
            List<PatientObservation?> activeIntraVenousLineObservations = [];
            try
            {
                activeIntraVenousLineObservations =
                    await _observationService.Value.FindLastIntravenousLinesObservationByLocation(obs.PatientId);
            }
            catch (Exception ex)
            {
                _logger.LogWarning(
                    "Cannot transform to PatientObservation value to PatientIntravenousLinesValue {exMessage}",
                    ex.Message);
            }

            if (activeIntraVenousLineObservations.Any())
                activeIntraVenousLineObservations = activeIntraVenousLineObservations.FindAll(v =>
                    v != null && ((PatientIntravenousLinesValue)v.Value).Action == "Insertado");
            /*
            var activeIntravenousLineObservations = intraVenousLineObservations.GroupBy(obs => (((PatientIntravenousLinesValue)obs.value).type,
             ((PatientIntravenousLinesValue)obs.value).insertTime,
             ((PatientIntravenousLinesValue)obs.value).location))
                .Where(grp => grp.All(o => ((PatientIntravenousLinesValue)o.value).RemoveTime == null))
                .SelectMany(group => group).ToList();
            */
            //If intravenous obs have remove time, remove it from activeIntravenousLines before calculating.

            if (intravenousLineValue.RemoveTime != null)
            {
                activeIntraVenousLineObservations.RemoveAll(o =>
                    o != null && ((PatientIntravenousLinesValue)o.Value).Location == intravenousLineValue.Location);
            }
            else
            {
                //If insert come with same data than other insert  and same insertTime is update. Don't need to register it again as insert.is updated
                var intravenousLineBeforeUpdate = activeIntraVenousLineObservations.FirstOrDefault(o => o != null &&
                    ((PatientIntravenousLinesValue)o.Value).Location == intravenousLineValue.Location &&
                    Equals(((PatientIntravenousLinesValue)o.Value).InsertTime, intravenousLineValue.InsertTime) &&
                    ((PatientIntravenousLinesValue)o.Value).Type == intravenousLineValue.Type &&
                    intravenousLineValue.RemoveTime == null);

                if (intravenousLineBeforeUpdate != null)
                {
                    //If duration changed Update register

                    if (((PatientIntravenousLinesValue)intravenousLineBeforeUpdate.Value).Duration ==
                        intravenousLineValue.Duration)
                        return null;

                    ((PatientIntravenousLinesValue)intravenousLineBeforeUpdate.Value).Duration =
                        intravenousLineValue.Duration ?? string.Empty;

                    await _observationService.Value.UpdateObservation(intravenousLineBeforeUpdate);

                    return null;
                }


                if (!activeIntraVenousLineObservations.Any(o =>
                        o != null &&
                        ((PatientIntravenousLinesValue)o.Value).Location == intravenousLineValue.Location &&
                        ((PatientIntravenousLinesValue)o.Value).Type == intravenousLineValue.Type))
                    activeIntraVenousLineObservations.Add(pobs);
            }

            //Generate calculad intravenousLine obs

            //var intraVenousLineScore2 = activeIntraVenousLineObservations.Select(activeObservation => IntraVenousLinesTypeValue.Where(i => i.Item3.Contains(((PatientIntravenousLinesValue)activeObservation.value).type))).Select(typeValue => typeValue.FirstOrDefault()?.Item2??0).Sum();

            // Step 1: Select active observations and cast to PatientIntravenousLinesValue
            var activeObservations = activeIntraVenousLineObservations.Select(activeObservation =>
                (PatientIntravenousLinesValue?)activeObservation?.Value);

            // Step 2: Match active observations with type-value mappings
            // We use .Replace("\u00A0", " ") on type to avoid special codification for spaces between characters
            var matchedValues = activeObservations.Select(activeObservation => _intraVenousLinesTypeValue.Where(i =>
                    activeObservation is { Type: not null } &&
                    i.Item3.Contains(activeObservation.Type.Replace("\u00A0", " "))))
                .ToList();

            // Step 3: Extract the values from matched tuples
            var values = 0;
            matchedValues.ForEach(typeValue => values += typeValue.FirstOrDefault()?.Item2 ?? 0);

            // Step 4: Calculate the sum
            var intraVenousLineScore = values;

            if (intraVenousLineScore > 10) intraVenousLineScore = 10;


            var intravenousObs = new PatientObservation
            {
                PatientId = obs.PatientId,
                Name = "IntravenousLines",
                Min = 0,
                CodingSystem = "ADAS",
                Max = 10,
                Time = DateTime.UtcNow,
                Value = intraVenousLineScore
            };

            var ob = await FixTimeInconsistencyWithLast(intravenousObs);
            if (ob != null)
                await _observationService.Value.InsertObservation(ob, mapObs: false);
        }
        catch (InvalidCastException ex)
        {
            _logger.LogError("CalculateIntravenousLineObservation {obs}: {exMessage}", obs, ex.Message);
        }

        return obs;
    }

    /// <summary>
    /// Derives the weight-related complexity contribution from a weight observation: values expressed
    /// in kilograms are first converted to grams via <c>ParseWeight</c>, and the final integer complexity
    /// contribution is mapped from the gram value using the standard UCIN brackets
    /// (&lt; 750 g → 7, 750–999 → 5, 1000–1249 → 2, 1250–1999 → 1, ≥ 2000 → 0). Returns 0 when the
    /// observation value cannot be parsed as a number.
    /// </summary>
    /// <param name="obs">The base patient observation whose value represents the patient's weight.</param>
    /// <returns>A task that resolves to the integer complexity contribution (0–7).</returns>
    private async Task<int> CalculateWeight(BasePatientObservation obs)
    {
        var complexityValueOfWeight = 0;
        if (obs.Units == "kg") obs = await ParseWeight(obs);

        var pobs = (PatientObservation)obs;


        if (!double.TryParse(pobs.Value.ToString(), out var valueParsed))
            return complexityValueOfWeight;

        complexityValueOfWeight = valueParsed switch
        {
            < 750 => 7,
            >= 750 and <= 999 => 5,
            >= 1000 and <= 1249 => 2,
            >= 1250 and <= 1999 => 1,
            >= 2000 => 0,
            _ => complexityValueOfWeight
        };

        return complexityValueOfWeight;
    }

    /// <summary>
    /// Converts a weight observation expressed in kilograms to grams in-place (updates <c>Units</c> to
    /// <c>"gr"</c> and multiplies <c>Value</c> by 1000). Returns the observation unchanged when it is
    /// not a <see cref="PatientObservation"/> or when its value cannot be parsed; the error is also
    /// logged.
    /// </summary>
    /// <param name="obs">The base patient observation expected to be a <see cref="PatientObservation"/> with a numeric value.</param>
    /// <returns>A task that resolves to the (possibly converted) base patient observation.</returns>
    public Task<BasePatientObservation> ParseWeight(BasePatientObservation obs)
    {
        if (obs is not PatientObservation pobs || !double.TryParse(pobs.Value.ToString(), out var dValue))
        {
            _logger.LogError("Error casting weight Observation {obs}:", obs);
            return Task.FromResult(obs);
        }

        pobs.Units = "gr";
        pobs.Value = dValue * 1000;
        return Task.FromResult(obs);
    }

    /// <summary>
    /// Recalculates the <c>Monitor</c> observation by adding 1 point for each active
    /// transcutaneous / regional-brain-saturation / EEG monitoring signal (codes from the configured
    /// catalogues) found in the most recent observations and active treatments. Persists the resulting
    /// observation with bounds [0, 3]. Returns early with a warning when no recent monitor observations
    /// are found.
    /// </summary>
    /// <param name="monitorObservation">
    /// The trigger for the calculation. Accepts either a <see cref="PatientObservation"/>
    /// (preferred when available) or a <see cref="PatientTreatment"/>. The patient identifier is
    /// derived from whichever type is supplied.
    /// </param>
    /// <returns>A task that represents the asynchronous calculation operation.</returns>
    public async Task CalculateMonitor(object monitorObservation)
    {
        var monitorValue = 0;
        BasePatientObservation? pobs = null;
        ObjectId patientId = new();
        PatientTreatment? tobs = null;

        if (monitorObservation.GetType() == typeof(PatientObservation))
        {
            pobs = (BasePatientObservation)monitorObservation;
            patientId = pobs.PatientId;
        }
        else if (monitorObservation.GetType() == typeof(PatientTreatment))
        {
            tobs = (PatientTreatment)monitorObservation;
            patientId = tobs.PatientId;
        }


        //Observations in last 30 min are active
        var activeMonitorObservationsList = await _observationService.Value.FindLastObservations(patientId, 1,
            ["Saturation_RegBrain", "Saturation_RegSomatic", "Transcutaneous_O2"]);
        if (!activeMonitorObservationsList.Any())
        {
            _logger.LogWarning("Active Monitor Observation List is null or empty for patientId: {patientId}",
                patientId);
            return;
        }

        var activeMonitorObservations = activeMonitorObservationsList.FindAll(o => !o.Expired);


        if (pobs != null && !((PatientObservation)pobs).Expired)
            activeMonitorObservations.Add((PatientObservation)pobs);

        if (activeMonitorObservations.Any(o => o.Code != null && _transcutanous.Contains(o.Code))) monitorValue += 1;
        if (activeMonitorObservations.Any(o => o.Code != null && _regionalBrainSaturation.Contains(o.Code)))
            monitorValue += 1;


        //Patient treatment
        var activeTreatments = await _treatmentService.Value.GetActiveTreatmentsByPatient(patientId);

        activeTreatments = activeTreatments.ToList().FindAll(p =>
            p != null && p.RequestedGiveCodes.Any(r =>
                _electroencephalogram.Contains(r.Identifier) && r.Text.Contains("Monitor EEGa")));

        if (tobs != null && tobs.OrderControl != OrderControlType.Dc && activeTreatments.Any()) monitorValue += 1;
        else if (tobs is { OrderControl: OrderControlType.Nw }) monitorValue += 1;


        //Generate calculad monitor obs
        var monitorObs = new PatientObservation
        {
            PatientId = patientId,
            Name = "Monitor",
            CodingSystem = "ADAS",
            Min = 0,
            Max = 3,
            Time = DateTime.Now,
            Value = monitorValue
        };

        await _observationService.Value.InsertObservation(monitorObs, mapObs: false);
    }

    /// <summary>
    /// Recalculates the <c>Surgery</c> complexity score by reconciling the supplied treatment's
    /// <c>OrderControl</c> (<c>NW</c> adds, <c>DC</c> removes by placer-order identifier) against the
    /// patient's other active surgery treatments, then persisting a <c>Surgery</c> observation
    /// with a score of 5 when any active surgery treatment remains, or 0 otherwise.
    /// </summary>
    /// <param name="treatment">The treatment that triggered the recalculation.</param>
    /// <returns>A task that represents the asynchronous calculation operation.</returns>
    private async Task CalculateSurgery(PatientTreatment treatment)
    {
        var surgeryScore = 0;
        var activeTreatments = await _treatmentService.Value.GetActiveTreatmentsByPatient(treatment.PatientId);
        //Filter only surgery treatments
        var activeSurgeryTreatments = activeTreatments.ToList().FindAll(t =>
            t != null && t.RequestedGiveCodes.Any(code =>
                _surgery.Contains(code.Identifier) && _surgeryText.Contains(code.Text)));

        switch (treatment.OrderControl)
        {
            //Refactor solo con los tratamientos activos de cirugía, un DC cancela a su entityIdentifier correspondiente.

            case OrderControlType.Nw:
                activeSurgeryTreatments.Add(treatment);
                break;
            case OrderControlType.Dc:
                activeSurgeryTreatments.RemoveAll(t =>
                    t != null && t.PlacerOrder?.EntityIdentifier == treatment.PlacerOrder?.EntityIdentifier);
                break;
        }


        if (activeSurgeryTreatments.Count > 0) surgeryScore = 5;

        var surgeryObs = new PatientObservation
        {
            PatientId = treatment.PatientId,
            Name = "Surgery",
            Min = 0,
            Max = 5,
            CodingSystem = "ADAS",
            Time = DateTime.Now,
            Value = surgeryScore
        };

        await _observationService.Value.InsertObservation(surgeryObs, mapObs: false);
    }

    /// <summary>
    /// Computes the patient's active bolus treatments by:
    /// (1) optionally including the newly arrived treatment, (2) grouping all bolus-eligible treatments
    /// by placer-order <c>NamespaceId</c>, (3) discarding treatments whose administration time was
    /// later overridden by an end-time, (4) keeping only the latest message-time entry per distinct
    /// administration time, and (5) filtering to the configured medication-bolus catalogue, an
    /// administration time within the last 12 hours, and a non-empty RXA status contained in
    /// <c>_rxaStatus</c>.
    /// </summary>
    /// <param name="patientId">The unique identifier of the patient.</param>
    /// <param name="newTreatment">The optional new treatment to be included in the active-bolus set.</param>
    /// <returns>A task that resolves to the list of treatments currently counted as active boluses.</returns>
    private async Task<List<PatientTreatment>> GetActiveBolus(ObjectId patientId, PatientTreatment? newTreatment)
    {
        var treatmentsProcessed = new List<PatientTreatment>();
        var activeTreatments = await _treatmentService.Value.GetBolusTreatments(patientId);


        if (newTreatment != null) activeTreatments.Add(newTreatment);

        //Agrupar por namespaceID y quedarme con el último message time que será el último que han reecho.
        //Si ese último es concluido suma 1.

        var treatmentsGroupedByNamespaceId = activeTreatments
            .GroupBy(t => t.PlacerOrder?.NamespaceId)
            //.Select(group => group.OrderByDescending(t => t.messageTime)).ToList();
            .Select(grp => grp.ToList())
            .ToList();
        //.Select(g => g.FirstOrDefault()).ToList();

        foreach (var treatmentGroup in treatmentsGroupedByNamespaceId)
        {
            var modificatedBolus = treatmentGroup.FindAll(t =>
                t.RequestedGiveCodesStatus.Any(r => r.EndAdministrationTime != DateTime.MinValue));
            //Eliminamos todos aquellos que han sido modificados con posterioridad, esto nos lo indica cuando tiene endAdministrationTime != null
            treatmentGroup.RemoveAll(t =>
                modificatedBolus.Any(m => m.RequestedGiveCodesStatus.FirstOrDefault()?.EndAdministrationTime
                                          == t.RequestedGiveCodesStatus.FirstOrDefault()?.AdministrationTime));
            /*  treatmentGroup.RemoveAll(t => t.requestedGiveCodesStatus
                  .Any(r => r.endAdministrationTime == null && modificatedBolus.FirstOrDefault(m =>
                          m.requestedGiveCodesStatus.Any(code => code.endAdministrationTime == r.administrationTime))
                      != null));
            */
            //modificatedBolus.Any(b =>
            //                  b.requestedGiveCodesStatus.Any(code => code.endAdministrationTime == r.administrationTime))));


            //Opción B a veces se cancelan sin endTime. Simplemente, agrupar por administrationTime y quedarme con el último por time del mensaje.
            treatmentGroup.RemoveAll(t => !t.RequestedGiveCodesStatus.Any());

            var groupByAdmTime = treatmentGroup.GroupBy(t =>
                    t.RequestedGiveCodesStatus.FirstOrDefault()!.AdministrationTime)
                .Select(grp => grp.OrderByDescending(t => t.MessageTime).First());


            treatmentsProcessed.AddRange(groupByAdmTime);
            //todos los que tengan startOfAdministration y no tenga esa fecha ninguno de modificatedBolus en endOfTreatment
        }


        return treatmentsProcessed
            .FindAll(p =>
                p.RequestedGiveCodes.Any(r => _medicationBolus.Contains(r.Identifier)) &&
                p.RequestedGiveCodesStatus.FirstOrDefault() != null &&
                p.RequestedGiveCodesStatus.FirstOrDefault()!.AdministrationTime?.ToUniversalTime() >
                DateTime.UtcNow.AddHours(-12) &&
                p.RequestedGiveCodesStatus.FirstOrDefault()!.AdministrationTime?.ToUniversalTime() <= DateTime.UtcNow &&
                p.RequestedGiveCodesStatus.Any(rxa => rxa.Status != null && _rxaStatus.Contains(rxa.Status)));
    }

    /// <summary>
    /// Persists the <c>OpiateBoluses</c> observation whose value is the count of currently active bolus
    /// treatments for the patient (as computed by <c>GetActiveBolus</c> including the new treatment).
    /// </summary>
    /// <param name="treatment">The treatment that triggered the recalculation and should be included in the active-bolus set.</param>
    /// <returns>A task that represents the asynchronous insert operation.</returns>
    private async Task CalculateBolus(PatientTreatment treatment)
    {
        var activeBolus = await GetActiveBolus(treatment.PatientId, treatment);

        var bolusObs = new PatientObservation
        {
            PatientId = treatment.PatientId,
            Time = DateTime.Now,
            Name = "OpiateBoluses",
            CodingSystem = "ADAS",
            Value = activeBolus.Count
        };
        await _observationService.Value.InsertObservation(bolusObs);
    }

    /// <summary>
    /// Applies the red-alert rule for mean arterial pressure (TAm) in neonates: when TAm is below
    /// the patient's gestational age (in weeks) for the first week, or below the fixed gestational
    /// age thereafter, the <c>TAm</c> observation's <c>Status</c> is set to <c>Alert</c>; otherwise
    /// it is set to <c>Ok</c>. If the supplied observation is a gestational-age value (not TAm),
    /// a fresh TAm observation is re-inserted with the recalculated status. The threshold is read
    /// from the latest <c>Age_Gestational</c> observation when its value is below 2 weeks,
    /// otherwise from the latest <c>Age_Gestational_Fixed</c>.
    /// </summary>
    /// <param name="obs">The base patient observation that triggered the alert evaluation (<c>TAm</c>, <c>Age_Gestational</c>, or <c>Age_Gestational_Fixed</c>).</param>
    /// <returns>A task that represents the asynchronous calculation operation.</returns>
    public async Task CalculateTAmAlert(BasePatientObservation obs)
    {
        var pobs = (PatientObservation)obs;
        var tamStatus = StatusEnum.Type.Ok;
        var groupedObservations = new List<PatientObservation> { pobs };

        var gestationalWeeksToCheck = 0;

        if (obs.Name == "Age_Gestational")
            groupedObservations = await _observationService.Value.FindLastObservations(obs.PatientId, 1,
                ["Age_Gestational_Fixed", "TAm"]);
        if (obs.Name == "Age_Gestational_Fixed")
            groupedObservations = await _observationService.Value.FindLastObservations(obs.PatientId, 1,
                ["Age_Gestational", "TAm"]);

        if (obs.Name == "TAm")
            groupedObservations = await _observationService.Value.FindLastObservations(obs.PatientId, 1,
                ["Age_Gestational", "Age_Gestational_Fixed"]);

        var ageGestational = groupedObservations.FirstOrDefault(o => o.Name == "Age_Gestational");
        if (ageGestational != null && int.TryParse(ageGestational.Value.ToString(), out var ageParsed) && ageParsed < 2)
        {
            gestationalWeeksToCheck = Convert.ToInt32(ageGestational.Value);
        }
        else
        {
            var ageGestationalFixed = groupedObservations.FirstOrDefault(o => o.Name == "Age_Gestational_Fixed");
            if (ageGestationalFixed != null) gestationalWeeksToCheck = Convert.ToInt32(ageGestationalFixed.Value);
            //var gestationalWeeks = int.Parse(ageGestationalFixed.value.ToString());
            //if (Convert.ToInt32(pobs.value) < gestationalWeeks) TAmStatus = ObservationStatus.Alert;
        }

        var tamObs = groupedObservations.FirstOrDefault(o => o.Name == "TAm");
        if (tamObs != null)
            if (Convert.ToInt32(tamObs.Value) < gestationalWeeksToCheck)
                tamStatus = StatusEnum.Type.Alert;

        if (pobs.Name == "TAm")
        {
            pobs.Status = tamStatus;
        }
        else
        {
            if (tamObs != null)
            {
                tamObs.Id = ObjectId.GenerateNewId();
                tamObs.Time = DateTime.Now;
                tamObs.Status = tamStatus;
                await _observationService.Value.InsertObservation(tamObs, mapObs: false);
            }
        }
    }

    /// <summary>
    /// Computes the temperature gradient between the incubator and the patient (incubator − patient)
    /// when both temperatures have been observed within 10 minutes of each other. Persists the
    /// <c>Temp_Gradient</c> observation with the resulting value, after shifting its timestamp via
    /// <c>CheckObsWithSameTimeExistsAndIncrementTime</c> to avoid colliding with a previous
    /// gradient observation. Returns early when the companion temperature is missing or the two
    /// readings fall outside the 10-minute window.
    /// </summary>
    /// <param name="obs">The base patient observation that triggered the calculation (<c>Temp_Patient</c> or <c>Temp_Incubator</c>).</param>
    /// <returns>A task that represents the asynchronous calculation operation.</returns>
    public async Task CalculateTempGradient(BasePatientObservation obs)
    {
        List<PatientObservation> tempRetrievedesFromBd;
        PatientObservation? tempRetrievedFromBd = null;
        var pobs = (PatientObservation)obs;
        if ("Temp_Patient".Equals(pobs.Name))
        {
            tempRetrievedesFromBd =
                await _observationService.Value.FindLastObservations(pobs.PatientId, 1, ["Temp_Incubator"]);
            tempRetrievedFromBd = tempRetrievedesFromBd.FirstOrDefault();
        }

        if ("Temp_Incubator".Equals(pobs.Name))
        {
            tempRetrievedesFromBd =
                await _observationService.Value.FindLastObservations(pobs.PatientId, 1, ["Temp_Patient"]);
            tempRetrievedFromBd = tempRetrievedesFromBd.FirstOrDefault();
        }


        //Now Temp Gradient is only calculated when temp_Patient and Temp_Incubator come in last 10 min.            
        //if (tempRetrievedFromBd != null && DateTime.UtcNow.CompareTo(tempRetrievedFromBd.time.ToUniversalTime().AddMinutes(10)) <= 0)
        if (tempRetrievedFromBd == null) return;
        var difFechas = pobs.Time.ToUniversalTime() - tempRetrievedFromBd.Time.ToUniversalTime();
        //if (tempRetrievedFromBd != null && pobs.time.ToUniversalTime().CompareTo(tempRetrievedFromBd.time.ToUniversalTime().AddMinutes(10)) <= 0)
        if (Math.Abs(difFechas.TotalMinutes) <= 10 &&
            double.TryParse(tempRetrievedFromBd.Value.ToString(), out var tempRetrievedFromBdParsed) &&
            double.TryParse(pobs.Value.ToString(), out var pobsParsed))
        {
            double tempGradientValue;
            if ("Temp_Patient".Equals(pobs.Name))
                tempGradientValue = tempRetrievedFromBdParsed - pobsParsed;
            else
                tempGradientValue = pobsParsed - tempRetrievedFromBdParsed;
            var tempGradientObs = new PatientObservation
            {
                PatientId = obs.PatientId,
                Name = "Temp_Gradient",
                CodingSystem = "ADAS",
                Time = obs.Time,
                Value = tempGradientValue
            };

            var obsToInsert = (PatientObservation)await CheckObsWithSameTimeExistsAndIncrementTime(tempGradientObs);

            obsToInsert.Id = ObjectId.GenerateNewId();

            await _observationService.Value.InsertObservation(obsToInsert, mapObs: false);
        }
    }

    /// <summary>
    /// Enumeration of the intravenous-line types recognised by the UCIN complexity model,
    /// ordered from most invasive (Artery) to least invasive (Peripheral).
    /// </summary>
    private enum IntraVenousLineTypes
    {
        Artery,
        CentralVein,
        Picc,
        MiddleLine,
        Peripheral
    }

    /// <summary>
    /// Enumeration of the respiratory-assistance types recognised by the UCIN complexity model,
    /// ordered from highest score (Ino) to lowest (None).
    /// </summary>
    // ReSharper disable once UnusedMember.Local
    private enum RespiratoryTypes
    {
        Ino,
        Vafo,
        Vmc,
        Vmni,
        NasalCannulas,
        None
    }
}