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 PumpAlarmEventRepositoryTest
{
    private PumpAlarmEventRepository _repo;
    private IOptions<ApiSettings> _apiSettings;

    private static readonly DateTime Now = DateTime.UtcNow;
    private const string DeviceA = "Device-A";
    private const string DeviceB = "Device-B";
    private static readonly ObjectId PatientId = ObjectId.GenerateNewId();

    // -------------------------------------------------------------------
    // INIT – Setup inicial de colección con índices y datos de prueba
    // -------------------------------------------------------------------
    [OneTimeSetUp]
    public async Task Init()
    {
        _apiSettings = Options.Create(new ApiSettings
        {
            PumpAlarmEvent = "pump_alarm_event"
        });

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

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

        await _repo.CreateIndexes();

        var initialEvents = new List<PumpAlarmEvent>
        {
            new()
            {
                Id = ObjectId.GenerateNewId(),
                DeviceId = DeviceA,
                PatientId = PatientId,
                AlarmType = PumpEnum.AlarmType.Attention,
                Time = Now.AddSeconds(-1)
            },
            new()
            {
                Id = ObjectId.GenerateNewId(),
                DeviceId = DeviceA,
                PatientId = PatientId,
                AlarmType = PumpEnum.AlarmType.Occlusion,
                Time = Now.AddSeconds(-3)
            },
            new()
            {
                Id = ObjectId.GenerateNewId(),
                DeviceId = DeviceB,
                PatientId = PatientId,
                AlarmType = PumpEnum.AlarmType.Occlusion,
                Time = Now.AddSeconds(-2)
            }
        };

        await IntegrationDb.Database
            .GetCollection<PumpAlarmEvent>("pump_alarm_event")
            .InsertManyAsync(initialEvents);

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

    // -------------------------------------------------------------------
    // INSERT
    // -------------------------------------------------------------------
    [Test]
    public async Task InsertAsync_Works()
    {
        var evt = new PumpAlarmEvent
        {
            Id = ObjectId.GenerateNewId(),
            DeviceId = "Device-C",
            PatientId = PatientId,
            AlarmType = PumpEnum.AlarmType.Occlusion,
            Time = Now
        };

        await _repo.InsertAsync(evt);

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

    // -------------------------------------------------------------------
    // FIND BY DEVICE ID
    // -------------------------------------------------------------------
    [Test]
    public async Task FindByDeviceIdAsync_ReturnsOrdered()
    {
        var result = await _repo.FindByDeviceIdAsync(DeviceA);

        var list = result.ToList();

        using (Assert.EnterMultipleScope())
        {
            Assert.That(list, Is.Not.Empty);
            Assert.That(list.First().Time, Is.GreaterThanOrEqualTo(list.Last().Time));
        }
    }

    [Test]
    public async Task FindByDeviceIdAsync_WithDateRange_Works()
    {
        var from = Now.AddSeconds(-2);
        var to = Now;

        var result = await _repo.FindByDeviceIdAsync(DeviceA, from, to);

        using (Assert.EnterMultipleScope())
        {
            var pumpAlarmEvents = result.ToList();
            Assert.That(pumpAlarmEvents, Is.Not.Empty);
            Assert.That(pumpAlarmEvents.All(x => x.Time >= from && x.Time <= to), Is.True);
        }
    }

    [Test]
    public async Task FindByDeviceIdAsync_WithLimit_Works()
    {
        var result = await _repo.FindByDeviceIdAsync(DeviceA, limit: 1);

        var list = result.ToList();
        Assert.That(list, Has.Count.EqualTo(1));
    }

    // -------------------------------------------------------------------
    // FIND LAST BY DEVICE
    // -------------------------------------------------------------------
    [Test]
    public async Task FindLastByDeviceIdAsync_Works()
    {
        // Arrange: dataset ya insertado en Init()

        var events = await _repo.FindByDeviceIdAsync("Device-A");
        var expected = events.OrderByDescending(e => e.Time).First();

        // Act
        var evt = await _repo.FindLastByDeviceIdAsync("Device-A");

        // Assert
        Assert.That(evt, Is.Not.Null);
        Assert.That(evt!.Id, Is.EqualTo(expected.Id)); // comparación robusta
    }

    // -------------------------------------------------------------------
    // DELETE BY PATIENT ID
    // -------------------------------------------------------------------
    [Test]
    public async Task DeleteByPatientId_Works()
    {
        var patient2 = ObjectId.GenerateNewId();

        var evt = new PumpAlarmEvent
        {
            Id = ObjectId.GenerateNewId(),
            DeviceId = "Device-X",
            PatientId = patient2,
            AlarmType = PumpEnum.AlarmType.Occlusion,
            Time = Now
        };

        await _repo.InsertAsync(evt);

        await _repo.DeleteByPatientId(patient2);

        var found = await _repo.Collection.FindAsync(x => x.PatientId == patient2);
        Assert.That(await found.AnyAsync(), Is.False);
    }

    // -------------------------------------------------------------------
    // UPDATE MANY (CAMBIO DE PATIENTID O SIMILAR)
    // -------------------------------------------------------------------

    [Test]
    public async Task UpdateManyObjectIdByFiledNameAsync_Works()
    {
        var oldId = ObjectId.GenerateNewId();
        var newId = ObjectId.GenerateNewId();

        var evt = new PumpAlarmEvent
        {
            Id = ObjectId.GenerateNewId(),
            DeviceId = "Device-U",
            PatientId = oldId,
            AlarmType = PumpEnum.AlarmType.Attention,
            Time = Now
        };

        await _repo.InsertAsync(evt);

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

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

        var found = await _repo.Collection.FindAsync(x => x.PatientId == newId);
        Assert.That(await found.AnyAsync(), Is.True);
    }
    
    [Test]
    public async Task FindByDeviceIdAsync_ReturnsEmpty_WhenOutOfRange()
    {
        var from = Now.AddYears(-2);
        var to = Now.AddYears(-2).AddDays(1);

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

        Assert.That(result, Is.Empty);
    }
    
    [Test]
    public async Task FindByDeviceIdAsync_RespectsLimitAndOrder()
    {
        // Semilla adicional para asegurar > 1
        var events = new[]
        {
            new PumpAlarmEvent { Id = ObjectId.GenerateNewId(), DeviceId = DeviceA, PatientId = PatientId, AlarmType = PumpEnum.AlarmType.AirInLine,  Time = Now.AddMilliseconds(-10) },
            new PumpAlarmEvent { Id = ObjectId.GenerateNewId(), DeviceId = DeviceA, PatientId = PatientId, AlarmType = PumpEnum.AlarmType.Occlusion, Time = Now.AddMilliseconds(-5) }
        };
        await _repo.Collection.InsertManyAsync(events);

        var result = await _repo.FindByDeviceIdAsync(DeviceA, limit: 1);
        var list = result.ToList();

        using (Assert.EnterMultipleScope())
        {
            Assert.That(list, Has.Count.EqualTo(1));
            var last = await _repo.FindLastByDeviceIdAsync(DeviceA);
            Assert.That(list[0].Id, Is.EqualTo(last!.Id));
        }
    }
    
    [Test]
    public async Task InsertAsync_AllowsNullOptionalFields()
    {
        var evt = new PumpAlarmEvent
        {
            Id = ObjectId.GenerateNewId(),
            DeviceId = "Device-Nulls",
            PatientId = null,                // opcional
            AlarmType = null,                // opcional
            Time = Now
        };

        await _repo.InsertAsync(evt);

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

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

        var docs = Enumerable.Range(0, 3).Select(i => new PumpAlarmEvent
        {
            Id = ObjectId.GenerateNewId(),
            DeviceId = "Device-Del",
            PatientId = pid,
            AlarmType = PumpEnum.AlarmType.Attention,
            Time = Now.AddSeconds(-i)
        });

        await _repo.Collection.InsertManyAsync(docs);

        // Primera vez: borra 3
        await _repo.DeleteByPatientId(pid);
        var left = await _repo.Collection.FindAsync(x => x.PatientId == pid);
        Assert.That(left.ToList(), Is.Empty);

        Func<Task> act = async () => await _repo.DeleteByPatientId(pid);
        Assert.That(act, Throws.Nothing);
    }

    
    [Test]
    public async Task UpdateManyObjectIdByFiledNameAsync_UpdatesMultiple()
    {
        var oldId = ObjectId.GenerateNewId();
        var newId = ObjectId.GenerateNewId();

        var events = Enumerable.Range(0, 4).Select(i => new PumpAlarmEvent
        {
            Id = ObjectId.GenerateNewId(),
            DeviceId = "Device-U",
            PatientId = oldId,
            AlarmType = PumpEnum.AlarmType.AirInLine,
            Time = Now.AddMilliseconds(-i)
        });

        await _repo.Collection.InsertManyAsync(events);

        var modified = await _repo.UpdateManyObjectIdByFiledNameAsync("PatientId", newId, oldId);

        using (Assert.EnterMultipleScope())
        {
            Assert.That(modified, Is.EqualTo(4));
            var foundNew = await _repo.Collection.FindAsync(x => x.PatientId == newId);
            Assert.That(foundNew.ToList(), Has.Count.EqualTo(4));
        }
    }
    [Test]
    public async Task FindByDeviceIdAsync_InclusiveRange_BoundsRespected()
    {
        var start = Now.AddMinutes(-30);
        var end = Now.AddMinutes(-29);

        var exactEvt = new PumpAlarmEvent
        {
            Id = ObjectId.GenerateNewId(),
            DeviceId = "Device-Range",
            PatientId = PatientId,
            AlarmType = PumpEnum.AlarmType.Attention,
            Time = end // exactamente igual al to
        };

        await _repo.InsertAsync(exactEvt);

        var result = await _repo.FindByDeviceIdAsync("Device-Range", from: start, to: end);

        using (Assert.EnterMultipleScope())
        {
            var pumpAlarmEvents = result as PumpAlarmEvent[] ?? result.ToArray();
            Assert.That(pumpAlarmEvents, Is.Not.Empty);
            Assert.That(pumpAlarmEvents.All(r => r.Time >= start && r.Time <= end), Is.True);
        }
    }
    
    [Test]
    public async Task FindLastByDeviceIdAsync_TracksNewest()
    {
        const string dev = "Device-LastCheck";

        var e1 = new PumpAlarmEvent { Id = ObjectId.GenerateNewId(), DeviceId = dev, PatientId = PatientId, AlarmType = PumpEnum.AlarmType.AirInLine,  Time = Now.AddSeconds(-10) };
        var e2 = new PumpAlarmEvent { Id = ObjectId.GenerateNewId(), DeviceId = dev, PatientId = PatientId, AlarmType = PumpEnum.AlarmType.Occlusion, Time = Now.AddSeconds(-5) };

        await _repo.Collection.InsertManyAsync([e1, e2]);

        var last1 = await _repo.FindLastByDeviceIdAsync(dev);
        Assert.That(last1!.Id, Is.EqualTo(e2.Id));

        var e3 = new PumpAlarmEvent { Id = ObjectId.GenerateNewId(), DeviceId = dev, PatientId = PatientId, AlarmType = PumpEnum.AlarmType.Attention, Time = Now };
        await _repo.InsertAsync(e3);

        var last2 = await _repo.FindLastByDeviceIdAsync(dev);
        Assert.That(last2!.Id, Is.EqualTo(e3.Id));
    }
    
    [Test]
    public async Task FindByDeviceIdAsync_MultipleOverlappingRanges()
    {
        const string dev = "Device-Timeline";

        var batch = new[]
        {
            new PumpAlarmEvent { Id = ObjectId.GenerateNewId(), DeviceId = dev, PatientId = PatientId, AlarmType = PumpEnum.AlarmType.AirInLine,  Time = Now.AddMinutes(-20) },
            new PumpAlarmEvent { Id = ObjectId.GenerateNewId(), DeviceId = dev, PatientId = PatientId, AlarmType = PumpEnum.AlarmType.Occlusion, Time = Now.AddMinutes(-10) },
            new PumpAlarmEvent { Id = ObjectId.GenerateNewId(), DeviceId = dev, PatientId = PatientId, AlarmType = PumpEnum.AlarmType.Attention, Time = Now.AddMinutes(-5)  }
        };

        await _repo.Collection.InsertManyAsync(batch);

        var r1 = await _repo.FindByDeviceIdAsync(dev, from: Now.AddMinutes(-30), to: Now.AddMinutes(-15));
        var r2 = await _repo.FindByDeviceIdAsync(dev, from: Now.AddMinutes(-12), to: Now.AddMinutes(-8));
        var r3 = await _repo.FindByDeviceIdAsync(dev, from: Now.AddMinutes(-6), to: Now);

        using (Assert.EnterMultipleScope())
        {
            var r1List = r1.ToList();
            Assert.That(r1List, Has.Count.EqualTo(1));  // -20
            var r2List = r2.ToList();
            Assert.That(r2List, Has.Count.EqualTo(1));  // -10
            var r3List = r3.ToList();
            Assert.That(r3List, Has.Count.EqualTo(1));  // -5
        }
    }
    
}
