using System.Reflection;
using adas_core.Domain.Models.AppSettings;
using adas_core.Infrastructure.Utils.MongoMaps.Interfaz;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using MongoDB.Bson.Serialization.Conventions;
using MongoDB.Driver;
using MongoMigrations.Core;
using MongoClient = MongoDB.Driver.MongoClient;

namespace adas_core.Infrastructure.Utils;

/// <summary>
/// Provides extension methods for configuring MongoDB integration on a host builder.
/// </summary>
public static class MongoDbHostBuilderExtension
{
    /// <summary>
    /// Configures the host builder with the MongoDB conventions and class map registrations required for the application.
    /// </summary>
    /// <param name="hostBuilder">The host builder to extend with the MongoDB configuration.</param>
    /// <returns>The same <see cref="IHostBuilder"/> instance, allowing further fluent configuration.</returns>
    public static IHostBuilder UseMongo(this IHostBuilder hostBuilder)
        {
            ConfigureMongoDbConventions();
    
            ConfigureRegisterMapClass();
    
            return hostBuilder;
        }
    /// <summary>
    /// Runs all pending MongoDB migrations found in the migrations assembly, updating the database schema to the latest version.
    /// </summary>
    /// <param name="host">The host whose services are used to resolve the <see cref="IMongoDatabase"/> instance.</param>
    /// <returns>The same <see cref="IHost"/> instance, enabling fluent chaining.</returns>
    public static IHost RunMongoMigrations(this IHost host)
        {
            using var scope = host.Services.CreateScope();
    
            var database = scope.ServiceProvider.GetRequiredService<IMongoDatabase>();
    
            var locator = new MigrationLocator();
            locator.LookForMigrationsInAssembly(
                typeof(adas_core.Infrastructure.Migrations.MongoMigrations.U_0_1_0_UpdateDataPatien).Assembly
            );
    
            var runner = new MigrationRunner(
                database,
                collectionName: "migrations",
                migrationLocator: locator
            );
    
            runner.UpdateToLatest();
    
            return host;
        }
    /// <summary>
    /// Configures and returns a MongoDB database instance using the provided connection settings.
    /// Validates that the connection string and database name are provided, and verifies that the database exists.
    /// </summary>
    /// <param name="dbSettings">The database settings containing the MongoDB connection string and database name.</param>
    /// <returns>The configured <see cref="IMongoDatabase"/> instance.</returns>
    /// <exception cref="Exception">Thrown when the connection string or database name is null or empty.</exception>
    /// <exception cref="Exception">Thrown when the specified database does not exist.</exception>
    private static IMongoDatabase ConfigureMongoDbConnection(IOptions<DatabaseSettings> dbSettings)
        {
            var connectionString = dbSettings.Value.ConnectionString;
    
            var databaseName = dbSettings.Value.DatabaseName;
    
            if (string.IsNullOrEmpty(connectionString) || string.IsNullOrEmpty(databaseName))
                throw new Exception("DataBase connection string and name is requiered");
    
            _mongoClient = new MongoClient(connectionString);
    
            var mongoDb = _mongoClient.GetDatabase(databaseName);
    
            if (mongoDb == null) throw new Exception("DataBase doesn't exist");
    
            return mongoDb;
        }

    public static void ConfigureRegisterMapClass()
    {
        var assembly = Assembly.GetExecutingAssembly();

        // Busca todos los tipos que implementan la interfaz
        var contributors = assembly.GetTypes()
            .Where(t => typeof(IEntityMapContributor).IsAssignableFrom(t) &&
                        t is { IsInterface: false, IsAbstract: false });

        // Crea una instancia de cada contribuidor y ejecuta su método
        foreach (var contributorType in contributors)
        {
            var contributorInstance = (IEntityMapContributor)Activator.CreateInstance(contributorType)!;
            contributorInstance.RegisterMaps();
        }
    }

    /// <summary>
    /// Registers global MongoDB serialization conventions, including ignoring extra elements in BSON documents
    /// and using camelCase for element names, applied to all types in the application.
    /// </summary>
    public static void ConfigureMongoDbConventions()
        {
            var pack = new ConventionPack
            {
                new IgnoreExtraElementsConvention(true),
                new CamelCaseElementNameConvention()
            };
    
            ConventionRegistry.Register(
                "Ignore Extra Elements Convention",
                pack,
                _ => true);
    
            ConventionRegistry.Register(
                "Camel Case Convention",
                pack,
                _ => true);
        }

    #region MongoDB

    private static MongoClient? _mongoClient;

    #endregion
}