using adas_core.Application.Repositories.Interfaces;
using adas_core.Application.Services;
using adas_core.Application.Services.Interfaces;
using adas_core.Domain.Models.AppSettings;
using adas_core.Domain.Models.GroupedObservations;
using adas_core.Domain.Utils;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using MongoDB.Bson;
using Moq;
using static adas_core.Domain.Enums.GroupedObservationEnum;

namespace adas_core.Test.Services;

[TestFixture]
public class GroupedObservationServiceTest
{
    /// <summary>
    /// Initializes the test fixture before each test by creating mocked dependencies
    /// (configuration observation service, observation repository, logger, and cache service)
    /// and instantiating the <see cref="GroupedObservationService"/> under test with default API and cache settings.
    /// </summary>
    [SetUp]
        public void Setup()
        {
            var configObservationService = new Mock<IConfigObservationService>();
            var observationRepository = new Mock<IObservationRepository>();
    
            _logger = new Mock<ILogger<GroupedObservationService>>();
    
            _groupedObservationService = new GroupedObservationService(
                observationRepository.Object, configObservationService.Object,
                _logger.Object, Mock.Of<ICacheService>(), Options.Create(new ApiSettings()), Options.Create(new CacheSettings()));
        }

    private GroupedObservationService _groupedObservationService;
    private Mock<ILogger<GroupedObservationService>> _logger;


    /// <summary>
    /// Tests that shift observations are correctly generated and grouped by shift time intervals (08:00, 15:00, 22:00) across multiple days,
    /// verifying the distribution of observations across shifts for the current day, the previous day, and two days prior when the
    /// Result.Last aggregation is applied with a maximum count of 36.
    /// </summary>
    [Test]
        public void Generate_Shift_Observations()
        {
            var dateTime = DateTime.Now;
            if (dateTime.Day <= 2) dateTime = dateTime.AddDays(2);
    
            var result = new List<BsonDocument>
            {
                GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day, 12, 30, "FC", 100, Result.Last, null),
                GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day, 10, 30, "FC", 108, Result.Last, null),
                GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day, 9, 30, "FC", 106, Result.Last, null),
                GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day, 5, 30, "FC", 112, Result.Last, null),
                GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day, 4, 30, "FC", 105, Result.Last, null),
                GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day - 1, 23, 30, "FC", 190, Result.Last, null),
                GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day - 1, 19, 30, "FC", 120, Result.Last, null),
                GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day - 1, 15, 30, "FC", 130, Result.Last, null),
                GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day - 1, 12, 30, "FC", 140, Result.Last, null),
                GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day - 1, 09, 30, "FC", 150, Result.Last, null),
                GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day - 2, 15, 30, "FC", 150, Result.Last, null)
            };
    
            var groupedField = new GroupedField
            {
                StartTimeShift = ["08:00", "15:00", "22:00"],
                Max = 36,
                Result = [Result.Last]
            };
    
            var shiftObservations = _groupedObservationService.GenerateShiftObservations(result, groupedField);
    
            var shift1ObsToday = shiftObservations.Count(s => s.Get("shift") == 0 && s.Get("day") == dateTime.Day);
    
            var shift1ObsYesterday = shiftObservations.Count(s => s.Get("shift") == 0 && s.Get("day") == dateTime.Day - 1);
    
            var shift2ObsYesterday = shiftObservations.Count(s => s.Get("shift") == 1 && s.Get("day") == dateTime.Day - 1);
    
            var shif3ObsYesterday = shiftObservations.Count(s => s.Get("shift") == 2 && s.Get("day") == dateTime.Day - 1);
    
            var shiftTwoDaysAgo = shiftObservations.Count(s => s.Get("shift") == 1 && s.Get("day") == dateTime.Day - 2);
            using (Assert.EnterMultipleScope())
            {
                Assert.That(shift1ObsToday, Is.EqualTo(3));
                Assert.That(shif3ObsYesterday, Is.EqualTo(3));
                Assert.That(shift2ObsYesterday, Is.EqualTo(2));
                Assert.That(shift1ObsYesterday, Is.EqualTo(2));
                Assert.That(shiftTwoDaysAgo, Is.EqualTo(1));
            };
        }

    /// <summary>
    /// Verifies that the grouped observation service correctly calculates sum values for shift observations
    /// across multiple days and shift periods (08:00, 15:00, 22:00), including observations that fall within
    /// the current day, the previous day, and two days prior, ensuring that data is aggregated into the
    /// correct shift and day buckets.
    /// </summary>
    [Test]
        public void Calculate_Shift_Observations()
        {
            var dateTime = DateTime.Now;
    
            if (dateTime.Day <= 2) dateTime = dateTime.AddDays(2);
    
    
            var groupedField = new GroupedField
            {
                StartTimeShift = ["08:00", "15:00", "22:00"],
                Max = 36,
                Result = [Result.Sum]
            };
    
            var result = new List<BsonDocument>
            {
                GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day, 12, 30, "FC", 100, Result.Sum, null),
                GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day, 10, 30, "FC", 108, Result.Sum, null),
                GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day, 9, 30, "FC", 106, Result.Sum, null),
                GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day, 5, 30, "FC", 112, Result.Sum, null),
                GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day, 4, 30, "FC", 105, Result.Sum, null),
                GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day - 1, 23, 30, "FC", 190, Result.Sum, null),
                GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day - 1, 19, 30, "FC", 120, Result.Sum, null),
                GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day - 1, 15, 30, "FC", 130, Result.Sum, null),
                GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day - 1, 12, 30, "FC", 140, Result.Sum, null),
                GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day - 1, 09, 30, "FC", 150, Result.Sum, null),
                GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day - 2, 15, 30, "FC", 150, Result.Sum, null)
            };
    
    
            var shiftObservations = _groupedObservationService.GenerateShiftObservations(result, groupedField);
    
            _groupedObservationService.CalculateShiftObservations(shiftObservations, groupedField);
            var shift1ObsToday = shiftObservations.First(s => s.Get("shift") == 0 && s.Get("day") == dateTime.Day);
            var shift1ObsYesterday = shiftObservations.First(s => s.Get("shift") == 0 && s.Get("day") == dateTime.Day - 1);
    
            var shift2ObsYesterday = shiftObservations.First(s => s.Get("shift") == 1 && s.Get("day") == dateTime.Day - 1);
    
            var shif3ObsYesterday = shiftObservations.First(s => s.Get("shift") == 2 && s.Get("day") == dateTime.Day - 1);
    
            var shiftTwoDaysAgo = shiftObservations.First(s => s.Get("shift") == 1 && s.Get("day") == dateTime.Day - 2);
            using (Assert.EnterMultipleScope())
            {
                Assert.That(shift1ObsToday.Get("sum")?.ToInt32(), Is.EqualTo(314));
                Assert.That(shif3ObsYesterday.Get("sum")?.ToInt32(), Is.EqualTo(407));
    
                Assert.That(shift2ObsYesterday.Get("sum")?.ToInt32(), Is.EqualTo(250));
                Assert.That(shift1ObsYesterday.Get("sum")?.ToInt32(), Is.EqualTo(290));
                Assert.That(shiftTwoDaysAgo.Get("sum")?.ToInt32(), Is.EqualTo(150));
            };
        }

//         [Test]
//         public void Calculate_Half_Time_Observations()
//         {
//             /*
//        09:18 - Toma 1: 50/100/75
//        09:38 - Toma 2: 52/102/76
//        10:07 - Toma 3: 54/101/72
//        10:18 - Toma 4: 51/102/73
//        10:58 - Toma 5: 52/107/79
//        12:04 - Toma 6: 60/120/90
//
//        El api convierte a
//
//        09h - Toma 2: 52/102/76
//        10h - Toma 4: 51/102/73
//        11h - Toma 5: 52/107/79
//        12h- Toma 6: 60/120/90
//    */
//
//
//             var dateTime = DateTime.Now;
//
//             var values9 = new BsonArray {
//                 new BsonDocument
//                 {
//                     { "value", 50},
//                     { "time", new BsonDateTime(new DateTime(2021,3,10,9,18,0))},
//
//                 },
//                  new BsonDocument
//                 {
//                     { "value", 100},
//                     { "time", new BsonDateTime(new DateTime(2021,3,10,9,38,0))}
//                 }
//             };
//
//             var values10 = new BsonArray {
//                new BsonDocument
//                 {
//                     { "value", 50},
//                     { "time", new BsonDateTime(new DateTime(2021,3,10,10,7,0))}
//                 },
//                    new BsonDocument
//                 {
//                     { "value", 60},
//                     { "time", new BsonDateTime(new DateTime(2021,3,10,10,18,0))}
//                 },
//                                       new BsonDocument
//                 {
//                     { "value", 70},
//                     { "time", new BsonDateTime(new DateTime(2021,3,10,10,58,0))}
//                 }
//             };
//
//             var values11 = new BsonArray
//             {
//
//             };
//
//             var values12 = new BsonArray
//             {
//                      new BsonDocument
//                 {
//                     { "value", 50},
//                     { "time", new BsonDateTime(new DateTime(2021,3,10,12,04,0))}
//                 }
//             };
//
//
//             var result = new List<BsonDocument>()
//             {
//                  GenerateBsonDocument(dateTime.Year,dateTime.Month,dateTime.Day,9,0,"TAM", 100, Result.HalfHour,values9),
//                  GenerateBsonDocument(dateTime.Year,dateTime.Month,dateTime.Day,10,0,"TAM", 100, Result.HalfHour,values10),
//                  GenerateBsonDocument(dateTime.Year,dateTime.Month,dateTime.Day,11,0,"TAM", 100, Result.HalfHour,values11),
//                  GenerateBsonDocument(dateTime.Year,dateTime.Month,dateTime.Day,12,0,"TAM", 100, Result.HalfHour,values12)
//             };
//
//             var halfObservations = _groupedObservationService.CalculateHalfHourObservations(result);
//
//             Assert.That(halfObservations, Has.Count.EqualTo(4));
//             Assert.Multiple(() =>
//             {
//                 Assert.That(halfObservations[0].GetByCodeSysAndCode("halfhour")?.AsBsonDocument.GetByCodeSysAndCode("value")?.AsInt32, Is.EqualTo(100));
//                 Assert.That(halfObservations[1].GetByCodeSysAndCode("halfhour")?.AsBsonDocument.GetByCodeSysAndCode("value")?.AsInt32, Is.EqualTo(60));
//                 Assert.That(halfObservations[2].GetByCodeSysAndCode("halfhour")?.AsBsonDocument.GetByCodeSysAndCode("value")?.AsInt32, Is.EqualTo(70));
//                 Assert.That(halfObservations[3].GetByCodeSysAndCode("halfhour")?.AsBsonDocument.GetByCodeSysAndCode("value")?.AsInt32, Is.EqualTo(50));
//             });
//         }

    /// <summary>
    /// Verifies that <c>CalculateLastFilledObservations</c> fills the missing hourly slot at <c>now - 4 hours</c>
    /// so that the returned sequence contains a complete set of five consecutive hourly observations
    /// (covering the last five hours relative to the current time) for a group with hourly regularity and a maximum of five entries.
    /// </summary>
    [Test]
        public async Task Calculate_Last_Filled_Observations_should_add_Minus_4_hour_to_complete_last_five_hours()
        {
            var dateTime = DateTime.Now;
    
            //Falta el -4 tiene que crearlo el calculate last filled observation
            var result = new List<BsonDocument>
            {
                GenerateBsonDocument(dateTime.AddHours(-5).Year, dateTime.AddHours(-5).Month, dateTime.AddHours(-5).Day,
                    dateTime.AddHours(-5).Hour, 0, "TAM", 100, Result.LastFilled, null),
                GenerateBsonDocument(dateTime.AddHours(-3).Year, dateTime.AddHours(-3).Month, dateTime.AddHours(-3).Day,
                    dateTime.AddHours(-3).Hour, 0, "TAM", 100, Result.LastFilled, null),
                GenerateBsonDocument(dateTime.AddHours(-2).Year, dateTime.AddHours(-2).Month, dateTime.AddHours(-2).Day,
                    dateTime.AddHours(-2).Hour, 0, "TAM", 100, Result.LastFilled, null),
                GenerateBsonDocument(dateTime.AddHours(-1).Year, dateTime.AddHours(-1).Month, dateTime.AddHours(-1).Day,
                    dateTime.AddHours(-1).Hour, 0, "TAM", 100, Result.LastFilled, null)
            };
    
    
            var groupedField = new GroupedField
            {
                Max = 5,
                Name = "TAM",
                Regularity = Regularity.Hour
            };
            var patientId = ObjectId.GenerateNewId();
    
            var lastFilledObservations =
                await _groupedObservationService.CalculateLastFilledObservations(result, groupedField, patientId);
            using (Assert.EnterMultipleScope())
            {
                Assert.That(lastFilledObservations, Has.Count.EqualTo(5));
                Assert.That(
                    lastFilledObservations.Exists(i => i.Get("time")?.ToLocalTime().Hour == dateTime.AddHours(-5).Hour),
                    Is.True);
                Assert.That(
                    lastFilledObservations.Exists(i => i.Get("time")?.ToLocalTime().Hour == dateTime.AddHours(-4).Hour),
                    Is.True);
                Assert.That(
                    lastFilledObservations.Exists(i => i.Get("time")?.ToLocalTime().Hour == dateTime.AddHours(-3).Hour),
                    Is.True);
                Assert.That(
                    lastFilledObservations.Exists(i => i.Get("time")?.ToLocalTime().Hour == dateTime.AddHours(-2).Hour),
                    Is.True);
                Assert.That(
                    lastFilledObservations.Exists(i => i.Get("time")?.ToLocalTime().Hour == dateTime.AddHours(-1).Hour),
                    Is.True);
            };
        }

    /// <summary>
    /// Verifies that <c>CalculateLastFilledObservations</c> fills missing hourly entries in the last five hours,
    /// adding the <c>-1 hour</c> observation when it is not present in the provided list so that the returned
    /// collection always contains a complete five-hour window of last filled observations.
    /// </summary>
    [Test]
        public async Task Calculate_Last_Filled_Observations_should_add_Minus_1_hour_to_complete_last_five_hours()
        {
            var dateTime = DateTime.Now;
    
            //Falta el -4 tiene que crearlo el calculate last filled observation
            var result = new List<BsonDocument>
            {
                GenerateBsonDocument(dateTime.AddHours(-5).Year, dateTime.AddHours(-5).Month, dateTime.AddHours(-5).Day,
                    dateTime.AddHours(-5).Hour, 0, "TAM", 100, Result.LastFilled, null),
                GenerateBsonDocument(dateTime.AddHours(-4).Year, dateTime.AddHours(-4).Month, dateTime.AddHours(-4).Day,
                    dateTime.AddHours(-4).Hour, 0, "TAM", 100, Result.LastFilled, null),
                GenerateBsonDocument(dateTime.AddHours(-3).Year, dateTime.AddHours(-3).Month, dateTime.AddHours(-3).Day,
                    dateTime.AddHours(-3).Hour, 0, "TAM", 100, Result.LastFilled, null),
                GenerateBsonDocument(dateTime.AddHours(-2).Year, dateTime.AddHours(-2).Month, dateTime.AddHours(-2).Day,
                    dateTime.AddHours(-2).Hour, 0, "TAM", 100, Result.LastFilled, null)
            };
    
    
            var groupedField = new GroupedField
            {
                Max = 5,
                Name = "TAM",
                Regularity = Regularity.Hour
            };
            var patientId = ObjectId.GenerateNewId();
    
            var lastFilledObservations =
                await _groupedObservationService.CalculateLastFilledObservations(result, groupedField, patientId);
            using (Assert.EnterMultipleScope())
            {
                Assert.That(lastFilledObservations, Has.Count.EqualTo(5));
                Assert.That(
                    lastFilledObservations.Exists(i => i.Get("time")?.ToLocalTime().Hour == dateTime.AddHours(-5).Hour),
                    Is.True);
                Assert.That(
                    lastFilledObservations.Exists(i => i.Get("time")?.ToLocalTime().Hour == dateTime.AddHours(-4).Hour),
                    Is.True);
                Assert.That(
                    lastFilledObservations.Exists(i => i.Get("time")?.ToLocalTime().Hour == dateTime.AddHours(-3).Hour),
                    Is.True);
                Assert.That(
                    lastFilledObservations.Exists(i => i.Get("time")?.ToLocalTime().Hour == dateTime.AddHours(-2).Hour),
                    Is.True);
                Assert.That(
                    lastFilledObservations.Exists(i => i.Get("time")?.ToLocalTime().Hour == dateTime.AddHours(-1).Hour),
                    Is.True);
            };
        }

    /// <summary>
    /// Verifies that the service correctly fills hourly observations from half-hour observation data, grouping timestamped values by hour and forwarding missing slots up to the configured maximum (5) per hour.
    /// </summary>
    [Test]
        public void FillHoursObservations()
        {
            /*
                09:18 - Toma 1: 50/100/75
                09:38 - Toma 2: 52/102/76
                10:07 - Toma 3: 54/101/72
                10:18 - Toma 4: 51/102/73
                10:58 - Toma 5: 52/107/79
                12:04 - Toma 6: 60/120/90
    
                El api convierte a
    
                09h - Toma 2: 52/102/76
                10h - Toma 4: 51/102/73
                11h - Toma 5: 52/107/79
                12h- Toma 6: 60/120/90
            */
    
            var groupedField = new GroupedField
            {
                Max = 5,
                Name = "TAM",
                Regularity = Regularity.Hour
            };
    
    
            var dateTime =
                new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, 12, 30, 0).ToUniversalTime();
    
            var values9 = new BsonArray
            {
                new BsonDocument
                {
                    { "value", 50 },
                    {
                        "time",
                        new BsonDateTime(
                            new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, 9, 18, 0).ToUniversalTime())
                    }
                },
                new BsonDocument
                {
                    { "value", 100 },
                    {
                        "time",
                        new BsonDateTime(
                            new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, 9, 38, 0).ToUniversalTime())
                    }
                }
            };
    
            var values10 = new BsonArray
            {
                new BsonDocument
                {
                    { "value", 50 },
                    {
                        "time",
                        new BsonDateTime(
                            new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, 10, 7, 0).ToUniversalTime())
                    }
                },
                new BsonDocument
                {
                    { "value", 60 },
                    {
                        "time",
                        new BsonDateTime(new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, 10, 18, 0)
                            .ToUniversalTime())
                    }
                },
                new BsonDocument
                {
                    { "value", 70 },
                    {
                        "time",
                        new BsonDateTime(new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, 10, 58, 0)
                            .ToUniversalTime())
                    }
                }
            };
    
            var values12 = new BsonArray
            {
                new BsonDocument
                {
                    { "value", 50 },
                    {
                        "time",
                        new BsonDateTime(new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, 12, 04, 0)
                            .ToUniversalTime())
                    }
                }
            };
    
    
            var result = new List<BsonDocument>
            {
                GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day, 9, 0, "TAM", 100, Result.HalfHour,
                    values9),
                GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day, 10, 0, "TAM", 100, Result.HalfHour,
                    values10),
    
                GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day, 12, 0, "TAM", 100, Result.HalfHour,
                    values12)
            };
    
            var halfObservations = _groupedObservationService.CalculateHalfHourObservations(result);
    
            var fillHoursObservations = _groupedObservationService.FillHours(halfObservations, groupedField);
    
    
            Assert.That(fillHoursObservations, Is.Not.Empty);
        }


    /// <summary>
    /// Generates a BsonDocument representing a time-stamped result entry, with date component fallbacks to the current date when null and conditional payload structure based on the result type.
    /// </summary>
    /// <param name="year">The year component of the entry timestamp, or null to default to the current year.</param>
    /// <param name="month">The month component of the entry timestamp, or null to default to the current month.</param>
    /// <param name="day">The day component of the entry timestamp, or null to default to the current day.</param>
    /// <param name="hour">The hour component of the entry timestamp, or null to default to the current hour.</param>
    /// <param name="minute">The minute component of the entry timestamp, or null to default to the current minute.</param>
    /// <param name="name">The name identifier included in the document's _id.</param>
    /// <param name="value">The raw value associated with the result.</param>
    /// <param name="resultType">The result type that determines how the value is stored; aggregate types (HalfHour, Sum, Count, Min, Average, Max) are stored directly, while others are wrapped in a sub-document with time and stringified value.</param>
    /// <param name="all">Optional BsonArray of additional entries; when provided, it is appended to the result under the "all" key.</param>
    /// <returns>A BsonDocument containing the _id composite key, a time field, the result-type-specific payload, and optionally the "all" array.</returns>
    private static BsonDocument GenerateBsonDocument(int? year, int? month, int? day, int? hour, int? minute,
            string name, object value, Result resultType, BsonArray? all)
        {
            var result = new BsonDocument
            {
                {
                    "_id", new BsonDocument
                    {
                        { "year", year ?? new DateTime().Year },
                        { "month", month ?? new DateTime().Month },
                        { "day", day ?? new DateTime().Day },
                        { "hour", hour ?? new DateTime().Hour },
                        { "minute", minute ?? new DateTime().Minute },
                        { "name", name }
                    }
                },
                { "time", new BsonDateTime(new DateTime((int)year!, (int)month!, (int)day!, (int)hour!, (int)minute!, 0)) }
            };
    
    
            /*
            {resultType.ToString().ToLower(), new BsonDocument{
                {"time", new BsonDateTime(new System.DateTime((int)year, (int)month, (int)day, (int)hour, (int)minute,0)) },
                {"value",  value.ToString() }
            }},
            { "time", new BsonDateTime(new System.DateTime((int)year, (int)month, (int)day, (int)hour, (int)minute,0)) },
    
            };
            */
    
            if (resultType != Result.HalfHour && resultType != Result.Sum && resultType != Result.Count
                && resultType != Result.Min && resultType != Result.Average && resultType != Result.Max)
                result.Add(resultType.ToString().ToLower(), new BsonDocument
                {
                    { "time", new BsonDateTime(new DateTime((int)year, (int)month, (int)day, (int)hour, (int)minute, 0)) },
                    { "value", value.ToString() }
                });
            else
                result.AddRange(new Dictionary<string, object> { { resultType.ToString().ToLower(), value } });
    
            if (all != null) result.Add("all", all);
    
            return result;
        }
}