using adas_core.Domain.Enums;
using adas_core.Domain.Models;
using adas_core.Domain.Models.AppSettings;
using adas_core.Domain.Models.MongoModels;
using adas_core.Infrastructure.Migrations.MongoMigrations;
using adas_core.Infrastructure.Repositories;
using Microsoft.Extensions.Options;
using MongoDB.Bson;
using MongoDB.Driver;
using MongoMigrations.Core;
using Options = Microsoft.Extensions.Options.Options;

namespace adas_core.Test.Repositories;

[TestFixture]
[Category("Integration")]
public class MongodbMigrationTest
{
    private PatientRepository _repository;
    private IOptions<ApiSettings> _optionsApiSettings;

    private readonly ApiSettings _apiSettings = new()
    {
        ArchivePatientsDiagnosis = "patients"
    };

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

    /// <summary>
    /// Performs one-time initialization for the test fixture by dropping and recreating the "patients" collection, instantiating the PatientRepository, and seeding it with three test Patient records (one of which has a discharge time set to the current time) to verify they are persisted.
    /// </summary>
    [OneTimeSetUp]
        public async Task Init()
        {
            _optionsApiSettings = Options.Create(_apiSettings);
    
            await IntegrationDb.Database.DropCollectionAsync("patients");
            await IntegrationDb.Database.CreateCollectionAsync("patients");
    
            _repository = new PatientRepository(_optionsApiSettings, IntegrationDb.Database);
    
            var patients = new List<Patient>
            {
                new ()
                {
                    Id = PatientId,
                    PatientId = "patientId1",
                    PointOfCareId = ObjectId.GenerateNewId(),
                    Bed = "bed1",
                    PatientNumber = "patientNumber1",
                    Person = new Person
                    {
                        FirstName = "firstName1",
                        LastName = "lastName1",
                        BirthDate = new DateTime(),
                        Gender = PatientEnum.Gender.Male
                    },
                    DisTime = Now
                },
                new ()
                {
                    PatientId = "patientId2",
                    PointOfCareId = ObjectId.GenerateNewId(),
                    Bed = "bed2",
                    PatientNumber = "patientNumber2",
                    Person = new Person
                    {
                        FirstName = "firstName2",
                        LastName = "lastName2",
                        BirthDate = new DateTime(),
                        Gender = PatientEnum.Gender.Male
                    }
                },
                new ()
                {
                    PatientId = "patientId3",
                    PointOfCareId = ObjectId.GenerateNewId(),
                    Bed = "bed3",
                    PatientNumber = "patientNumber3",
                    Person = new Person
                    {
                        FirstName = "firstName3",
                        LastName = "lastName3",
                        BirthDate = new DateTime(),
                        Gender = PatientEnum.Gender.Male
                    }
                }
            };
    
            await _repository.InsertManyAsync(patients);
            Assert.That(await _repository.Collection.CountDocumentsAsync(_ => true), Is.EqualTo(3));
        }

    /// <summary>
    /// Performs one-time cleanup after integration tests by dropping the <c>patients</c> and <c>__migrations</c> collections from the integration database, ensuring a clean state for subsequent test runs.
    /// </summary>
    [OneTimeTearDown]
        public void Teardown()
        {
            IntegrationDb.Database.DropCollection("patients");
            IntegrationDb.Database.DropCollection("__migrations");
        }

    /// <summary>
    /// Verifies that the database is detected as outdated by creating a runner and asserting that <c>IsDatabaseUpToDate</c> returns <c>false</c> when using the primary read preference.
    /// </summary>
    [Test]
        [Order(1)]
        public void IsDatabaseOutdated_ShouldReturnTrue()
        {
            var runner = CreateRunner();
    
            var isUpToDate = runner.IsDatabaseUpToDate(ReadPreference.Primary);
    
            Assert.That(isUpToDate, Is.False);
        }


    /// <summary>
    /// Verifies that after running the migration update to the latest version, the migration with ID 10 (version 0.1.0) is present among the applied migrations.
    /// </summary>
    [Test]
        [Order(2)]
        public void AfterUpdateToLatest_MigrationShouldBeApplied()
        {
            var runner = CreateRunner();
    
            runner.UpdateToLatest();
            var ids = GetAppliedMigrationIds();
            Assert.That(ids, Does.Contain(10)); // 0.1.0
        }

    [Test]
    public void RunningMigrationsTwice_ShouldNotFail()
    {
        var runner = CreateRunner();

        runner.UpdateToLatest();
        runner.UpdateToLatest(); // segunda ejecución

        var ids = GetAppliedMigrationIds();
        Assert.That(ids, Does.Contain(10));
    }

    // Helpers
    /// <summary>
    /// Creates and configures a <see cref="MigrationRunner"/> for executing database migrations, using a locator that scans the assembly containing the patient data update migration.
    /// </summary>
    /// <returns>A configured <see cref="MigrationRunner"/> bound to the integration database and the migrations collection.</returns>
    private MigrationRunner CreateRunner()
        {
            var locator = new MigrationLocator();
            locator.LookForMigrationsInAssembly(typeof(U_0_1_0_UpdateDataPatien).Assembly);
            return new MigrationRunner(
                IntegrationDb.Database,
                collectionName: "__migrations",
                migrationLocator: locator
            );
        }

    /// <summary>
    /// Retrieves the identifiers of all applied migrations from the "__migrations" collection in the integration database.
    /// </summary>
    /// <returns>A list of migration identifiers extracted from the "_id" field of each migration document.</returns>
    private List<int> GetAppliedMigrationIds()
        {
            var collection = IntegrationDb.Database.GetCollection<BsonDocument>("__migrations");
    
            return collection
                .Find(FilterDefinition<BsonDocument>.Empty)
                .ToList()
                .Select(d => d["_id"].AsInt32)
                .ToList();
        }
}