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;
using adas_core.Domain.Enums;

namespace adas_core.Test.Repositories;

[TestFixture]
[Category("Integration")]
public class PumpStateRepositoryTest
{
    private PumpStateRepository _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
    // ============================================================
    /// <summary>
    /// One-time setup that prepares the integration test environment for the PumpStateRepository by recreating the
    /// "pump_states" MongoDB collection, building the repository indexes, and seeding two initial pump states (one
    /// actively infusing for a known patient/device and one not infusing) to verify the baseline document count.
    /// </summary>
    [OneTimeSetUp]
    public async Task Init()
    {
        _apiSettings = Options.Create(new ApiSettings
        {
            PumpStates = "pump_states"
        });

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

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

        await _repo.CreateIndexes();

        // Insertar estados iniciales
        var initialStates = new List<PumpState>
            {
                new()
                {
                    Id = ObjectId.GenerateNewId(),
                    DeviceId = DeviceA,
                    PatientId = PatientId,
                    LastUpdated = Now,
                    IsInfusing = true,
                    Status = PumpEnum.Status.Infusing,
                    PumpMode = PumpEnum.Mode.Infusing
                },
                new()
                {
                    Id = ObjectId.GenerateNewId(),
                    DeviceId = DeviceB,
                    LastUpdated = Now.AddSeconds(-10),
                    IsInfusing = false,
                    Status = PumpEnum.Status.NotInfusing
                }
            };

        await IntegrationDb.Database.GetCollection<PumpState>("pump_states")
            .InsertManyAsync(initialStates);

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

    // ============================================================
    // FIND BY DEVICE ID
    // ============================================================
    /// <summary>
    /// Verifies that FindByDeviceIdAsync retrieves the correct infusion state for a given device, returning a non-null result that matches the requested device identifier and reflects the expected infusion status.
    /// </summary>
    [Test]
    public async Task FindByDeviceIdAsync_ReturnsCorrectState()
    {
        var state = await _repo.FindByDeviceIdAsync(DeviceA);

        Assert.That(state, Is.Not.Null);
        using (Assert.EnterMultipleScope())
        {
            Assert.That(state!.DeviceId, Is.EqualTo(DeviceA));
            Assert.That(state.IsInfusing, Is.True);
        }
    }

    // ============================================================
    // UPSERT (INSERT + UPDATE)
    // ============================================================
    /// <summary>
    /// Verifies that <c>UpsertAsync</c> inserts a new <see cref="PumpState"/> record when no existing entry is found for the specified <c>DeviceId</c>, and that the persisted state retains the provided property values.
    /// </summary>
    [Test]
    public async Task UpsertAsync_InsertsNewWhenNotExists()
    {
        var newState = new PumpState
        {
            DeviceId = "Device-C",
            PatientId = ObjectId.GenerateNewId(),
            LastUpdated = Now,
            IsInfusing = false
        };

        await _repo.UpsertAsync(newState);

        var found = await _repo.FindByDeviceIdAsync("Device-C");
        Assert.That(found, Is.Not.Null);
        Assert.That(found!.IsInfusing, Is.False);
    }

    /// <summary>
    /// Verifies that <c>UpsertAsync</c> correctly updates an existing pump state in the repository, ensuring
    /// the updated <see cref="PumpState"/> is persisted and retrievable with the modified <c>IsInfusing</c>,
    /// <c>Status</c>, and <c>LastUpdated</c> values.
    /// </summary>
    [Test]
    public async Task UpsertAsync_UpdatesExistingState()
    {
        var updated = new PumpState
        {
            DeviceId = DeviceA,
            PatientId = PatientId,
            LastUpdated = Now.AddMinutes(1),
            IsInfusing = false,
            Status = PumpEnum.Status.NotInfusing
        };

        await _repo.UpsertAsync(updated);

        var found = await _repo.FindByDeviceIdAsync(DeviceA);

        Assert.That(found, Is.Not.Null);
        using (Assert.EnterMultipleScope())
        {
            Assert.That(found!.IsInfusing, Is.False);
            Assert.That(found.Status, Is.EqualTo(PumpEnum.Status.NotInfusing));
            Assert.That(
                found.LastUpdated,
                Is.EqualTo(updated.LastUpdated).Within(TimeSpan.FromMilliseconds(1))
            );
        }
    }

    // ============================================================
    // GET ALL
    // ============================================================
    /// <summary>
    /// Verifies that <c>GetAllAsync</c> returns a non-null collection of pump states containing at least two entries, including those associated with the seeded devices A and B.
    /// </summary>
    [Test]
    public async Task GetAllAsync_ReturnsAllStates()
    {
        var list = await _repo.GetAllAsync();

        var pumpStates = list.ToList();
        Assert.That(pumpStates, Is.Not.Null);
        Assert.That(pumpStates, Has.Count.GreaterThanOrEqualTo(2));

        var containsA = pumpStates.Any(x => x.DeviceId == DeviceA);
        var containsB = pumpStates.Any(x => x.DeviceId == DeviceB);

        using (Assert.EnterMultipleScope())
        {
            Assert.That(containsA, Is.True);
            Assert.That(containsB, Is.True);
        }
    }

    // ============================================================
    // UNIQUE INDEX: UPSERT OVERWRITES (NO DUPLICADOS)
    // ============================================================
    /// <summary>
    /// Verifies that UpsertAsync updates an existing entity instead of inserting a duplicate, ensuring the total record count remains unchanged when upserting a <see cref="PumpState"/> for an already-known device identifier.
    /// </summary>
    [Test]
    public async Task UpsertAsync_DoesNotCreateDuplicates()
    {
        var before = await _repo.GetAllAsync();
        var countBefore = before.Count();

        // Insert (upsert) for existing DeviceA
        var state = new PumpState
        {
            DeviceId = DeviceA,
            LastUpdated = Now.AddMinutes(5)
        };

        await _repo.UpsertAsync(state);

        var after = await _repo.GetAllAsync();
        var countAfter = after.Count();

        // No debe aumentar, porque es un update, no un insert
        Assert.That(countAfter, Is.EqualTo(countBefore));
    }
}