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 PumpAlarmStateRepositoryTest
{
    private PumpAlarmStateRepository _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: preparar colección, índices y datos iniciales
    // -------------------------------------------------------------------
    /// <summary>
    /// One-time setup that prepares the integration test environment for <see cref="PumpAlarmStateRepository"/>.
    /// Configures the API settings, drops and recreates the "pump_alarm_state" collection, instantiates the repository with its indexes, and seeds three initial alarm state records across two devices, verifying the seeded count.
    /// </summary>
    [OneTimeSetUp]
        public async Task Init()
        {
            _apiSettings = Options.Create(new ApiSettings
            {
                PumpAlarmState = "pump_alarm_state"
            });
    
            await IntegrationDb.Database.DropCollectionAsync("pump_alarm_state");
            await IntegrationDb.Database.CreateCollectionAsync("pump_alarm_state");
    
            _repo = new PumpAlarmStateRepository(
                _apiSettings,
                IntegrationDb.Database
            );
    
            await _repo.CreateIndexes();
    
            // datos iniciales
            var initial = new List<PumpAlarmState>
            {
                new()
                {
                    Id = ObjectId.GenerateNewId(),
                    DeviceId = DeviceA,
                    PatientId = PatientId,
                    AlarmType = PumpEnum.AlarmType.Attention,
                    AlarmCodeMdc = "AC001",
                    LastUpdated = Now.AddSeconds(-3)
                },
                new()
                {
                    Id = ObjectId.GenerateNewId(),
                    DeviceId = DeviceA,
                    PatientId = PatientId,
                    AlarmType = PumpEnum.AlarmType.Occlusion,
                    AlarmCodeMdc = "AC002",
                    LastUpdated = Now.AddSeconds(-1)
                },
                new()
                {
                    Id = ObjectId.GenerateNewId(),
                    DeviceId = DeviceB,
                    PatientId = PatientId,
                    AlarmType = PumpEnum.AlarmType.Occlusion,
                    AlarmCodeMdc = "AC003",
                    LastUpdated = Now.AddSeconds(-2)
                }
            };
    
            await _repo.Collection.InsertManyAsync(initial);
    
            var count = await _repo.Collection.CountDocumentsAsync(_ => true);
            Assert.That(count, Is.EqualTo(3));
        }

    // -------------------------------------------------------------------
    // FIND ACTIVE
    // -------------------------------------------------------------------
    /// <summary>
    /// Verifies that <c>FindActiveAsync</c> returns the correct active alarm matching the specified device identifier, alarm type, and alarm code.
    /// </summary>
    [Test]
        public async Task FindActiveAsync_ReturnsCorrectAlarm()
        {
            var alarm = await _repo.FindActiveAsync(DeviceA, PumpEnum.AlarmType.Occlusion, "AC002");
    
            using (Assert.EnterMultipleScope())
            {
                Assert.That(alarm, Is.Not.Null);
                Assert.That(alarm!.DeviceId, Is.EqualTo(DeviceA));
                Assert.That(alarm.AlarmType, Is.EqualTo(PumpEnum.AlarmType.Occlusion));
                Assert.That(alarm.AlarmCodeMdc, Is.EqualTo("AC002"));
            }
        }

    /// <summary>
    /// Verifies that <c>FindActiveAsync</c> returns <c>null</c> when no active alarm matching the specified device, alarm type, and identifier exists.
    /// </summary>
    [Test]
        public async Task FindActiveAsync_ReturnsNull_WhenNotFound()
        {
            var alarm = await _repo.FindActiveAsync(DeviceA, PumpEnum.AlarmType.Occlusion, "XXX");
            Assert.That(alarm, Is.Null);
        }

    // -------------------------------------------------------------------
    // UPSERT ACTIVE
    // -------------------------------------------------------------------
    /// <summary>
    /// Verifies that <c>UpsertActiveAsync</c> correctly inserts a new active pump alarm into the repository
    /// when no existing alarm with the same device, type, and code is found, and that the inserted alarm
    /// can subsequently be retrieved via <c>FindActiveAsync</c>.
    /// </summary>
    [Test]
        public async Task UpsertActiveAsync_InsertsNew()
        {
            var alarm = new PumpAlarmState
            {
                Id = ObjectId.GenerateNewId(),
                DeviceId = "Device-C",
                AlarmType = PumpEnum.AlarmType.Attention,
                AlarmCodeMdc = "NEW",
                PatientId = PatientId,
                LastUpdated = Now
            };
    
            await _repo.UpsertActiveAsync(alarm);
    
            var found = await _repo.FindActiveAsync("Device-C", PumpEnum.AlarmType.Attention, "NEW");
    
            Assert.That(found, Is.Not.Null);
        }

    [Test]
    public async Task UpsertActiveAsync_UpdatesExisting()
    {
        var updated = new PumpAlarmState
        {
            Id = ObjectId.GenerateNewId(), // será ignorado por replace
            DeviceId = DeviceA,
            AlarmType = PumpEnum.AlarmType.Occlusion,
            AlarmCodeMdc = "AC002",
            PatientId = PatientId,
            LastUpdated = Now.AddMinutes(1)
        };

        await _repo.UpsertActiveAsync(updated);

        var found = await _repo.FindActiveAsync(DeviceA, PumpEnum.AlarmType.Occlusion, "AC002");

        Assert.That(found, Is.Not.Null);
        Assert.That(found!.LastUpdated, Is.EqualTo(updated.LastUpdated).Within(TimeSpan.FromMilliseconds(2)));
    }

    // -------------------------------------------------------------------
    // REMOVE ACTIVE ALARM
    // -------------------------------------------------------------------
    /// <summary>
    /// Verifies that <c>RemoveAsync</c> correctly deletes an active alarm by device, alarm type, and alarm code, and that a subsequent lookup returns no result.
    /// </summary>
    [Test]
        public async Task RemoveAsync_Works()
        {
            var alarm = new PumpAlarmState
            {
                Id = ObjectId.GenerateNewId(),
                DeviceId = "Device-D",
                AlarmType = PumpEnum.AlarmType.Attention,
                AlarmCodeMdc = "D1",
                PatientId = PatientId,
                LastUpdated = Now
            };
    
            await _repo.UpsertActiveAsync(alarm);
    
            await _repo.RemoveAsync("Device-D", PumpEnum.AlarmType.Attention, "D1");
    
            var found = await _repo.FindActiveAsync("Device-D", PumpEnum.AlarmType.Attention, "D1");
            Assert.That(found, Is.Null);
        }

    // -------------------------------------------------------------------
    // DELETE BY PATIENT ID
    // -------------------------------------------------------------------
    /// <summary>
    /// Verifies that the repository's <c>DeleteByPatientId</c> method successfully removes a previously upserted
    /// <see cref="PumpAlarmState"/> for the specified patient, ensuring no matching records remain in the collection.
    /// </summary>
    [Test]
        public async Task DeleteByPatientId_Works()
        {
            var pid = ObjectId.GenerateNewId();
    
            var alarm = new PumpAlarmState
            {
                Id = ObjectId.GenerateNewId(),
                DeviceId = "Device-E",
                AlarmType = PumpEnum.AlarmType.Occlusion,
                AlarmCodeMdc = "DDD",
                PatientId = pid,
                LastUpdated = Now
            };
    
            await _repo.UpsertActiveAsync(alarm);
    
            await _repo.DeleteByPatientId(pid);
    
            var list = await _repo.Collection.FindAsync(x => x.PatientId == pid);
            Assert.That(await list.AnyAsync(), Is.False);
        }

    // -------------------------------------------------------------------
    // FIND ALL ACTIVE BY DEVICE
    // -------------------------------------------------------------------
    /// <summary>
    /// Verifies that <c>FindAllActiveByDeviceAsync</c> returns only active pump alarm states that belong to the specified device.
    /// </summary>
    [Test]
        public async Task FindAllActiveByDeviceAsync_ReturnsCorrect()
        {
            var result = await _repo.FindAllActiveByDeviceAsync(DeviceA);
    
            var pumpAlarmStates = result.ToList();
            Assert.That(pumpAlarmStates, Is.Not.Empty);
            Assert.That(pumpAlarmStates.All(x => x.DeviceId == DeviceA), Is.True);
        }

    // -------------------------------------------------------------------
    // UPDATE MANY BY FIELD
    // -------------------------------------------------------------------
    /// <summary>
    /// Verifies that <c>UpdateManyObjectIdByFieldNameAsync</c> successfully updates the <c>PatientId</c> field
    /// from the old ObjectId to a new ObjectId for matching documents, returning the expected count of
    /// updated records and making the document retrievable by the new ObjectId.
    /// </summary>
    [Test]
        public async Task UpdateManyObjectIdByFieldNameAsync_Works()
        {
            var oldId = ObjectId.GenerateNewId();
            var newId = ObjectId.GenerateNewId();
    
            // Insert one alarm to update
            var alarm = new PumpAlarmState
            {
                Id = ObjectId.GenerateNewId(),
                DeviceId = "Device-F",
                AlarmType = PumpEnum.AlarmType.Occlusion,
                AlarmCodeMdc = "FFF",
                PatientId = oldId,
                LastUpdated = Now
            };
    
            await _repo.UpsertActiveAsync(alarm);
    
            var updated = await _repo.UpdateManyObjectIdByFieldNameAsync("PatientId", newId, oldId);
    
            using (Assert.EnterMultipleScope())
            {
                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 UpsertActiveAsync_NoDuplicateOnRepeatedCalls()
    {
        var alarm = new PumpAlarmState
        {
            Id = ObjectId.GenerateNewId(),
            DeviceId = "Device-Repeat",
            AlarmType = PumpEnum.AlarmType.Attention,
            AlarmCodeMdc = "R1",
            PatientId = PatientId,
            LastUpdated = Now
        };

        await _repo.UpsertActiveAsync(alarm);
        await _repo.UpsertActiveAsync(alarm); // segunda llamada idéntica

        var alarmsEnum = await _repo.FindAllActiveByDeviceAsync("Device-Repeat");
        var list = alarmsEnum.ToList();
        Assert.That(list, Has.Count.EqualTo(1));
    }
    
    /// <summary>
    /// Verifies that UpsertActiveAsync correctly inserts a PumpAlarmState record when the AlarmCodeMdc is null, and that the record can be retrieved via FindActiveAsync using a null alarm code.
    /// </summary>
    [Test]
        public async Task UpsertActiveAsync_InsertsWhenAlarmCodeIsNull()
        {
            var alarm = new PumpAlarmState
            {
                Id = ObjectId.GenerateNewId(),
                DeviceId = "Device-NullCode",
                AlarmType = PumpEnum.AlarmType.Occlusion,
                AlarmCodeMdc = null,
                PatientId = PatientId,
                LastUpdated = Now
            };
    
            await _repo.UpsertActiveAsync(alarm);
    
            var found = await _repo.FindActiveAsync("Device-NullCode", PumpEnum.AlarmType.Occlusion, null);
    
            Assert.That(found, Is.Not.Null);
            Assert.That(found!.AlarmCodeMdc, Is.Null);
        }
    
    /// <summary>
    /// Verifies that <c>RemoveAsync</c> removes every active alarm matching the specified device identifier and alarm type, not just a single record.
    /// </summary>
    [Test]
        public async Task RemoveAsync_RemovesAllMatching()
        {
            var alarms = new[]
            {
                new PumpAlarmState
                {
                    Id = ObjectId.GenerateNewId(),
                    DeviceId = "Device-Multi",
                    AlarmType = PumpEnum.AlarmType.AirInLine,
                    AlarmCodeMdc = "XX1",
                    PatientId = PatientId,
                    LastUpdated = Now
                },
                new PumpAlarmState
                {
                    Id = ObjectId.GenerateNewId(),
                    DeviceId = "Device-Multi",
                    AlarmType = PumpEnum.AlarmType.AirInLine,
                    AlarmCodeMdc = "XX2",
                    PatientId = PatientId,
                    LastUpdated = Now
                }
            };
    
            foreach (var a in alarms)
                await _repo.UpsertActiveAsync(a);
    
            await _repo.RemoveAsync("Device-Multi", PumpEnum.AlarmType.AirInLine);
    
            var remaining = await _repo.FindAllActiveByDeviceAsync("Device-Multi");
    
            Assert.That(remaining, Is.Empty);
        }

}