using System.Collections;
using adas_core.Application.Services.Interfaces;
using adas_core.Domain.Enums;
using adas_core.Domain.Models;
using adas_core.Domain.Models.GroupedObservations;
using adas_core.Domain.Models.MongoModels;
using adas_core.Domain.Utils;

namespace adas_core.Application.Services;

public class ObservationDemoService(
    IConfigObservationService configObservationService,
    Lazy<IAlarmService> alarmService)
    : IObservationDemoService
{
    public async Task<List<PatientObservationAlarm>> GenerateAlarmByField(Patient patient, List<Field> dataAlarmfields)
    {
        var listToReturn = new List<PatientObservationAlarm>();
        foreach (var field in dataAlarmfields)
        {
            var sendingRandom = new Random();

            if (sendingRandom.Next(0, 2) == 0 || string.IsNullOrEmpty(field.Name))
                continue;

            var firstCof = await configObservationService.GetConfigObservationItemsByName(field.Name);
            var conf = firstCof.FirstOrDefault();
            if (conf == null)
                continue;

            var alarmObs = new PatientObservationAlarm
            {
                Patient = patient,
                PatientId = patient.Id,
                AlarmConfig = conf.Alarm,
                InactivationState = new InactivationState
                {
                    Audio = AlarmEnum.AudioVideoState.Enabled, Acknowledge = true,
                    Visual = AlarmEnum.AudioVideoState.Enabled
                },
                EventPhase = AlarmEnum.EventPhase.Continue,
                Type = AlarmEnum.ObservationAlarmType.Sp,
                Expires = conf.Expires,
                State = AlarmEnum.ObservationAlarmState.Active,
                Time = DateTime.UtcNow,
                Persist = false,
                MessageTime = DateTime.UtcNow,
                Code = conf.Code,
                PriorityLevel = conf.DemoConfig?.PriorityAlarm,
                Name = conf.Name,
                ParentData = new ParentDataClass
                {
                    Code = conf.ParentCode,
                    Name = conf.ParentName,
                    CodingSystem = conf.CodingSystem
                },
                Units = conf.Units,
                Value = GenerateValueRandom(conf, null) ?? 0
            };
            var obs = await alarmService.Value.MapObservationsByName(alarmObs);


            if (obs != null)
                listToReturn.Add(obs);
        }

        return listToReturn;
    }

    public async Task<List<PatientObservation>> GenerateObservationByField(Patient patient, List<Field> dataFields)
    {
        var listToReturn = new List<PatientObservation>();
        var rnd = new Random();
        foreach (var field in dataFields)
        {
            if (string.IsNullOrEmpty(field.Name))
                continue;

            var conf = await configObservationService.GetConfigObservationItemsByName(field.Name);
            var firstCof = conf.FirstOrDefault();
            if (firstCof == null)
                continue;

            var patientObs = new PatientObservation
            {
                Patient = patient,
                PatientId = patient.Id,
                Max = firstCof.MaxAlert,
                Min = firstCof.MinAlert,
                MaxWarn = firstCof.MaxWarn,
                MinWarn = firstCof.MinWarn,
                ShowOnExpired = firstCof.ShowOnExpired,
                Expires = firstCof.Expires,
                UiConfiguration = firstCof.UiConfiguration,
                MessageTime = DateTime.UtcNow,
                Code = firstCof.Code,
                ColorOnExpired = firstCof.ColorOnExpired,
                AlertColor = firstCof.AlertColor,
                WarnColor = firstCof.WarnColor,
                CodingSystem = firstCof.CodingSystem,
                Alarm = firstCof.Alarm,
                InsertMode = firstCof.InsertMode,
                Name = field.Name,
                Time = DateTime.UtcNow,
                ParentData = new ParentDataClass
                {
                    Code = firstCof.ParentCode,
                    Name = firstCof.ParentName,
                    CodingSystem = firstCof.CodingSystem
                },
                Units = firstCof.Units,
                Value = GenerateValueRandom(firstCof, null) ?? 0
            };
            var result = await configObservationService.Map(patientObs, true);
            if (firstCof.DemoConfig?.RequireInitDate == true && result != null)
            {
                var rndRestNmbHours = rnd.Next(3, 23);
                var initDate = DateTime.UtcNow.AddHours(-rndRestNmbHours);

                // Si se requiere fecha de fin, devolvemos un objeto con ambas
                if (firstCof.DemoConfig?.RequireEndDate == true)
                {
                    result.Time = initDate;
                    result.EndTime = initDate.AddHours(1);
                }

                // Si no, solo devolvemos la fecha inicial
                result.Time = initDate;
            }

            if (result != null)
                listToReturn.Add(result);
        }

        return listToReturn;
    }

    public async Task<GroupedObservation> GenerateGroupedObservation(Patient patient, GroupedField groupedField)
    {
        if (groupedField.Names.IsNullOrEmpty() && groupedField.Name != null) groupedField.Names.Add(groupedField.Name);
        var rnd = new Random();
        var startTime = DateTime.UtcNow;
        var go = new GroupedObservation
        {
            Name = groupedField.Name,
            PatientId = patient.Id,
            Group = groupedField.Group,
            Observations = []
        };
        foreach (var groupedFieldName in groupedField.Names)
        {
            var conf = await configObservationService.GetConfigObservationItemsByName(groupedFieldName);
            var firstCof = conf.FirstOrDefault();
            if (firstCof == null)
                continue;

            foreach (var result in groupedField.Result)
            {
                object? prevValue = null;
                for (var i = 0; i <= groupedField.Max; i++)
                {
                    var time = startTime;
                    time = groupedField.Regularity switch
                    {
                        GroupedObservationEnum.Regularity.Day => time.AddDays(-i),
                        GroupedObservationEnum.Regularity.Minute => time.AddMinutes(-i),
                        GroupedObservationEnum.Regularity.Second => time.AddSeconds(-i),
                        _ => time.AddHours(-i)
                    };
                    var val = new GroupedObservation.GroupedObservationObs
                    {
                        Name = groupedFieldName,
                        MaxAlert = firstCof?.MaxAlert,
                        MinAlert = firstCof?.MinAlert,
                        Time = time,
                        IsFilled = false
                    };
                    var value = new GroupedObservation.GroupedObservationObsValue(
                        GenerateValueRandom(firstCof, prevValue) ?? rnd.Next(0, 30), time);
                    prevValue = value.Value;
                    var propertyInfo = val.GetType().GetProperty(result.ToString());

                    if (propertyInfo != null && propertyInfo.CanWrite) propertyInfo.SetValue(val, value);
                    var valueType = await configObservationService.GroupedObservationStatus(
                        groupedField,
                        result,
                        groupedFieldName,
                        val,
                        firstCof?.MaxAlert,
                        firstCof?.MinAlert
                    );
                    value.Type = valueType;
                    go.Observations.Add(
                        val
                    );
                }

                if (firstCof?.DemoConfig?.RequireInitDate.HasValue == true && firstCof.DemoConfig.RequireInitDate.Value)
                    break;
            }
        }

        return go;
    }

    private static object? GenerateValueRandom(ConfigObservation? firstCof, object? prevValue)
    {
        if (firstCof?.DemoConfig == null)
            return "-";

        // 1. Determinar cuántos valores necesitamos
        var count = firstCof.DemoConfig?.ValueIsArray == true ? firstCof.DemoConfig?.RandomCount ?? 1 : 1;
        var results = new List<object>();

        for (var i = 0; i < count; i++)
        {
            var singleValue = PickSingleValue(firstCof.DemoConfig, prevValue);
            if (singleValue != null)
                results.Add(singleValue);
        }

        // 2. Si no es un array, devolvemos solo el primer elemento
        return firstCof.DemoConfig?.ValueIsArray == true ? results : results.FirstOrDefault();
    }

    private static object? PickSingleValue(DemoConfig? config, object? prevValue)
    {
        if (config == null) return null;

        var rnd = new Random();
        // Prioridad 1: Selección de una lista de opciones (SetInValue)
        if (config is { SetInValue: true, ValueOption: not null })
        {
            if (config.ValueOption is List<object> { Count: > 0 } options)
            {
                var index = rnd.Next(options.Count);
                return options.ElementAt(index);
            }

            // Caso especial si ValueOption llega como un JArray o lista genérica
            if (config.ValueOption is IEnumerable list)
            {
                var tempList = list.Cast<object>().ToList();
                return tempList[rnd.Next(tempList.Count)];
            }
        }

        // Prioridad 2: Generación numérica por rango (SetRandomValue)
        if (config.SetRandomValue == false)
            return null;

        // 1. Extraemos los límites primero para usarlos en cualquier caso
        var minLimit = config.MinValue ?? 0;
        var maxLimit = config.MaxValue ?? 100;

        if (prevValue is not int values)
            // Caso base: Si no hay valor previo, generamos uno aleatorio dentro del rango permitido
            return rnd.Next(minLimit, maxLimit + 1);

        if (values == 0) values = 1;
        // 2. Calculamos la variación (entre 5% y 10%)
        var porcentaje = rnd.Next(5, 10) / 100.0;
        var direccion = rnd.Next(0, 2) == 0 ? -1 : 1;

        // 3. Calculamos el valor base redondeado
        var calculado = (int)Math.Round(values + values * porcentaje * direccion);

        // 4. Forzamos que esté dentro del rango [minLimit, maxLimit]
        return Math.Clamp(calculado, minLimit, maxLimit);
    }
}