using adas_core.Domain.Enums;
using adas_core.Domain.Models.AppSettings;
using adas_core.Domain.Models.Pumps;
using adas_core.Infrastructure.Repositories;
using Microsoft.Extensions.Options;
using MongoDB.Bson;
using MongoDB.Driver;

namespace adas_core.Test.Repositories;

[TestFixture]
[Category("Integration")]
public class PumpObservationRepositoryTest
{
    private PumpObservationRepository _repo;
    private IOptions<ApiSettings> _apiSettings;

    private static readonly DateTime Now = DateTime.UtcNow;
    private static readonly ObjectId PatientId = ObjectId.GenerateNewId();

    // -------------------------------------------------------------------
    // INIT – SETUP DE LA COLECCIÓN CON DATOS DE PRUEBA
    // -------------------------------------------------------------------
    [OneTimeSetUp]
    public async Task Init()
    {
        _apiSettings = Options.Create(new ApiSettings
        {
            PumpObservations = "pump_observations"
        });

        await IntegrationDb.Database.DropCollectionAsync("pump_observations");
        await IntegrationDb.Database.CreateCollectionAsync("pump_observations");

        _repo = new PumpObservationRepository(
            _apiSettings,
            IntegrationDb.Database
        );

        await _repo.CreateIndexes();

        var samples = new List<PumpObservation>
        {
            new()
            {
                Id = ObjectId.GenerateNewId(),
                PatientId = PatientId,
                DeviceId = "Device-A",
                Name = "PumpX",
                Time = Now.AddSeconds(-1),
                MessageType = PumpEnum.PumpMessageType.Observation
            },
            new()
            {
                Id = ObjectId.GenerateNewId(),
                PatientId = PatientId,
                DeviceId = "Device-A",
                Name = "PumpY",
                Time = Now.AddSeconds(-2),
                MessageType = PumpEnum.PumpMessageType.Observation
            },
            new()
            {
                Id = ObjectId.GenerateNewId(),
                PatientId = PatientId,
                DeviceId = "Device-A",
                Name = "PumpX",
                Time = Now.AddSeconds(-3),
                MessageType = PumpEnum.PumpMessageType.Observation
            }
        };

        await _repo.InsertManyAsync(samples);

        var count = await _repo.Collection.CountDocumentsAsync(_ => true);
        Assert.That(count, Is.EqualTo(3));
    }

    // -------------------------------------------------------------------
    // FIND LAST OBSERVATIONS (LÍMITE = 2)
    // -------------------------------------------------------------------
    [Test]
    public async Task FindLastObservations_ReturnsTwo()
    {
        var list = await _repo.FindLastObservations(PatientId, "PumpX");

        Assert.That(list, Is.Not.Null);
        Assert.That(list, Has.Count.EqualTo(2));
        Assert.That(list[0].Time, Is.GreaterThanOrEqualTo(list[1].Time));
    }

    // -------------------------------------------------------------------
    // INSERT
    // -------------------------------------------------------------------
    [Test]
    public async Task InsertAsync_Works()
    {
        var pump = new PumpObservation
        {
            Id = ObjectId.GenerateNewId(),
            PatientId = ObjectId.GenerateNewId(),
            DeviceId = "Device-B",
            Time = Now
        };

        await _repo.InsertAsync(pump);

        var found = await _repo.Collection.FindAsync(x => x.Id == pump.Id);
        Assert.That(found.FirstOrDefault(), Is.Not.Null);
    }

    [Test]
    public async Task InsertManyAsync_Works()
    {
        var list = new List<PumpObservation>
        {
            new() { Id = ObjectId.GenerateNewId(), DeviceId="D", Time=Now },
            new() { Id = ObjectId.GenerateNewId(), DeviceId="D", Time=Now }
        };

        await _repo.InsertManyAsync(list);

        var count = await _repo.Collection.CountDocumentsAsync(x => list.Select(y => y.Id).Contains(x.Id));
        Assert.That(count, Is.EqualTo(2));
    }

    // -------------------------------------------------------------------
    // FIND METHODS
    // -------------------------------------------------------------------
    [Test]
    public async Task FindByDeviceIdAsync_ReturnsOrdered()
    {
        var result = await _repo.FindByDeviceIdAsync("Device-A");

        var pumpObservations = result.ToList();
        using (Assert.EnterMultipleScope())
        {
            Assert.That(pumpObservations, Is.Not.Empty);
            Assert.That(pumpObservations.First().Time, Is.GreaterThanOrEqualTo(pumpObservations.Last().Time));
        }
    }

    [Test]
    public async Task FindByPatientAsync_ReturnsOrdered()
    {
        var result = await _repo.FindByPatientAsync(PatientId);

        var pumpObservations = result.ToList();
        using (Assert.EnterMultipleScope())
        {
            Assert.That(pumpObservations, Is.Not.Empty);
            Assert.That(pumpObservations.First().Time, Is.GreaterThanOrEqualTo(pumpObservations.Last().Time));
        }
    }

    [Test]
    public async Task FindLastByDeviceIdAsync_Works()
    {
        var obs = await _repo.FindLastByDeviceIdAsync("Device-A");

        Assert.That(obs, Is.Not.Null);
        Assert.That(obs.DeviceId, Is.EqualTo("Device-A"));
    }

    [Test]
    public async Task FindByPatientId_Works()
    {
        var list = await _repo.FindByPatientId(PatientId);

        Assert.That(list.Any(), Is.True);
    }

    // -------------------------------------------------------------------
    // AGGREGATED LAST OBSERVATIONS
    // -------------------------------------------------------------------
    [Test]
    public async Task AggregatedPatientLastObservations_DistinctByName()
    {
        var result = await _repo.AggregatedPatientLastObservations(PatientId);

        using (Assert.EnterMultipleScope())
        {
            Assert.That(result, Is.Not.Empty);
            Assert.That(result, Has.Count.LessThanOrEqualTo(2));
        }
    }

    // -------------------------------------------------------------------
    // DELETE METHODS
    // -------------------------------------------------------------------
    [Test]
    public async Task DeleteByPatientId_Works()
    {
        var pid = ObjectId.GenerateNewId();

        await _repo.InsertAsync(new PumpObservation
        {
            Id = ObjectId.GenerateNewId(),
            PatientId = pid,
            DeviceId = "Z",
            Time = Now
        });

        await _repo.DeleteByPatientId(pid);

        var list = await _repo.FindByPatientId(pid);
        Assert.That(list.Any(), Is.False);
    }

    [Test]
    public async Task DeleteOlderThanDaysAsync_Works()
    {
        var pid = ObjectId.GenerateNewId();

        await _repo.InsertAsync(new PumpObservation
        {
            Id = ObjectId.GenerateNewId(),
            PatientId = pid,
            Name = "TestOld",
            Time = Now.AddDays(-10)
        });

        var deleted = await _repo.DeleteOlderThanDaysAsync(5);

        Assert.That(deleted, Is.GreaterThanOrEqualTo(1));
    }

    [Test]
    public async Task DeleteOlderNumberAsync_Works()
    {
        const string name = "HistoryPump";

        var obs = Enumerable.Range(0, 5).Select(i =>
            new PumpObservation
            {
                Id = ObjectId.GenerateNewId(),
                DeviceId = "H",
                Name = name,
                Time = Now.AddSeconds(-i)
            });

        await _repo.InsertManyAsync(obs);

        var deleted = await _repo.DeleteOlderNumberAsync(name, 2);

        Assert.That(deleted, Is.EqualTo(3));
    }

    [Test]
    public async Task DeleteKeepLastNAsync_Works()
    {
        var items = Enumerable.Range(0, 8).Select(i =>
            new PumpObservation
            {
                Id = ObjectId.GenerateNewId(),
                DeviceId = "Device-Clean",
                Time = Now.AddSeconds(-i)
            });

        await _repo.InsertManyAsync(items);

        var deleted = await _repo.DeleteKeepLastNAsync(3);

        Assert.That(deleted, Is.EqualTo(5));
    }

    // -------------------------------------------------------------------
    // UPDATE METHOD
    // -------------------------------------------------------------------
    [Test]
    public async Task UpdateManyObjectIdByFieldAsync_Works()
    {
        var oldId = ObjectId.GenerateNewId();
        var newId = ObjectId.GenerateNewId();

        await _repo.InsertAsync(new PumpObservation
        {
            Id = ObjectId.GenerateNewId(),
            PatientId = oldId,
            DeviceId = "UpdateTest",
            Time = Now
        });

        var updated = await _repo.UpdateManyObjectIdByFieldAsync("PatientId", newId, oldId);

        Assert.That(updated, Is.EqualTo(1));

        var found = await _repo.FindByPatientId(newId);
        Assert.That(found.Any(), Is.True);
    }

    // -------------------------------------------------------------------
    // AGGREGATION: LAST OBSERVATION TIME PER PATIENT
    // -------------------------------------------------------------------
    [Test]
    public async Task FindAllLastPatientObservationTimeAsync_Works()
    {
        var dict = await _repo.FindAllLastPatientObservationTimeAsync();

        Assert.That(dict, Is.Not.Null);
        using (Assert.EnterMultipleScope())
        {
            Assert.That(dict.ContainsKey(PatientId), Is.True);
            Assert.That(dict[PatientId], Is.LessThanOrEqualTo(DateTime.UtcNow));
        }
    }
    
    [Test]
    public async Task FindByDeviceIdAsync_EmptyWhenOutOfDateRange()
    {
        var from = Now.AddYears(-1);
        var to = Now.AddYears(-1).AddHours(1);

        var result = await _repo.FindByDeviceIdAsync("Device-A", from, to);

        Assert.That(result, Is.Empty);
    }
    
    [Test]
    public async Task FindByPatientAsync_RespectsLimit()
    {
        var result = await _repo.FindByPatientAsync(PatientId, limit: 1);
        var list =  result.ToList();
        Assert.That(list, Has.Count.EqualTo(1));
    }
    
    [Test]
    public async Task FindLastByDeviceIdAsync_ReturnsNullWhenNotExists()
    {
        var obs = await _repo.FindLastByDeviceIdAsync("Device-DoesNotExist");

        Assert.That(obs, Is.Null);
    }

    [Test]
    public async Task InsertManyAsync_IgnoresEmptyList()
    {
        var empty = new List<PumpObservation>();

        Func<Task> act = () => _repo.InsertManyAsync(empty);

        Assert.That(act, Throws.Nothing);
    }

    
    [Test]
    public async Task DeleteOlderNumberAsync_DoesNothingWhenCountBelowLimit()
    {
        const string name = "LimitTestPump";

        var obs = new PumpObservation
        {
            Id = ObjectId.GenerateNewId(),
            DeviceId = "Device-X",
            Name = name,
            Time = Now
        };

        await _repo.InsertAsync(obs);

        var deleted = await _repo.DeleteOlderNumberAsync(name, maxCount: 5);

        Assert.That(deleted, Is.Zero);
    }
    
    [Test]
    public async Task DeleteOlderThanDaysAsync_DoesNotDeleteRecentObservations()
    {
        var obs = new PumpObservation
        {
            Id = ObjectId.GenerateNewId(),
            PatientId = PatientId,
            DeviceId = "Device-Recent",
            Name = "RecentPump",
            Time = Now.AddMinutes(-1) // reciente
        };

        await _repo.InsertAsync(obs);

        var deleted = await _repo.DeleteOlderThanDaysAsync(10);

        Assert.That(deleted, Is.Zero);
    }
    
    [Test]
    public async Task AggregatedPatientLastObservations_RemovesDuplicatesByCodeAndName()
    {
        var patient = ObjectId.GenerateNewId();

        var p1 = new PumpObservation
        {
            Id = ObjectId.GenerateNewId(),
            PatientId = patient,
            DeviceId = "DevA",
            Code = "C1",
            Name = "PumpA",
            Time = Now
        };

        var p2 = new PumpObservation
        {
            Id = ObjectId.GenerateNewId(),
            PatientId = patient,
            DeviceId = "DevA",
            Code = "C1",
            Name = "PumpA",
            Time = Now.AddSeconds(-1)
        };

        await _repo.InsertManyAsync([p1, p2]);

        var result = await _repo.AggregatedPatientLastObservations(patient);

        Assert.That(result, Has.Count.EqualTo(1));
    }
}