using MongoDB.Bson;
using MongoDB.Bson.Serialization;
using MongoDB.Driver;

namespace adas_core.Infrastructure.Utils;

/// <summary>
/// Provides utility methods for interacting with MongoDB.
/// </summary>
public class MongoUtils
{
    public static async Task EnsureIndexes<TDocument>(IMongoCollection<TDocument> collection,
        List<CreateIndexModel<TDocument>> expectedIndexes)
    {
        var existingIndexes = await (await collection.Indexes.ListAsync()).ToListAsync();
        var documentSerializer = BsonSerializer.SerializerRegistry.GetSerializer<TDocument>();
        var serializerRegistry = BsonSerializer.SerializerRegistry;
        var renderArgs = new RenderArgs<TDocument>(documentSerializer, serializerRegistry);
        foreach (var expectedIndexModel in expectedIndexes)
        {
            var expectedIndexKeys = expectedIndexModel.Keys
                .Render(renderArgs).ToString();
            var existingIndexWithSameKeys = existingIndexes.FirstOrDefault(index =>
            {
                var keys = index.Elements.FirstOrDefault(e => e.Name == "key").Value?.ToString();
                return keys == expectedIndexKeys;
            });

            if (existingIndexWithSameKeys != null)
            {
                // Existe un índice con las mismas claves, verificar las opciones
                if (!IndexOptionsMatch(expectedIndexModel, existingIndexWithSameKeys, renderArgs))
                {
                    // Las opciones no coinciden, eliminar el índice existente y crear el nuevo
                    var indexName = existingIndexWithSameKeys.Elements.FirstOrDefault(e => e.Name == "name").Value
                        ?.AsString;
                    if (!string.IsNullOrEmpty(indexName) && indexName != "_id_")
                        try
                        {
                            await collection.Indexes.DropOneAsync(indexName);
                            await collection.Indexes.CreateOneAsync(expectedIndexModel);
                            Console.WriteLine($"Info: Índice con claves '{expectedIndexKeys}' actualizado.");
                        }
                        catch (MongoCommandException ex) when (ex.Code == 85)
                        {
                            Console.WriteLine(
                                $"Warning: No se pudo actualizar el índice con claves '{expectedIndexKeys}'. Error: {ex.Message}");
                        }
                    else
                        try
                        {
                            await collection.Indexes.CreateOneAsync(expectedIndexModel);
                        }
                        catch (MongoCommandException ex) when (ex.Code == 85)
                        {
                            Console.WriteLine(
                                $"Warning: El índice con claves '{expectedIndexKeys}' ya existe con diferentes opciones y no se pudo actualizar automáticamente.");
                        }
                }
                // Si las opciones coinciden, no se hace nada
            }
            else
            {
                // No existe un índice con estas claves, crear el nuevo
                try
                {
                    await collection.Indexes.CreateOneAsync(expectedIndexModel);
                }
                catch (MongoCommandException ex) when (ex.Code == 85)
                {
                    Console.WriteLine($"Warning: El índice con claves '{expectedIndexKeys}' ya existe.");
                }
            }
        }
    }

    private static bool IndexOptionsMatch<TDocument>(CreateIndexModel<TDocument> expectedIndexModel,
        BsonDocument existingIndex, RenderArgs<TDocument> renderArgs)
    {
        var expectedIndexOptions = expectedIndexModel.Options;

        var unique = existingIndex.Elements.FirstOrDefault(e => e.Name == "unique").Value?.AsBoolean ?? false;
        var background = existingIndex.Elements.FirstOrDefault(e => e.Name == "background").Value?.AsBoolean ?? false;
        var partialFilter = existingIndex.Elements.FirstOrDefault(e => e.Name == "partialFilterExpression").Value
            ?.ToString();
        var expectedPartialFilter = expectedIndexOptions.PartialFilterExpression
            ?.Render(renderArgs).ToString();

        return unique == expectedIndexOptions.Unique &&
               background == expectedIndexOptions.Background &&
               partialFilter == expectedPartialFilter;
    }
}