using adas_core.Application.Repositories.Interfaces;
using adas_core.Domain.Models.AppSettings;
using adas_core.Infrastructure.Utils;
using Microsoft.Extensions.Options;
using MongoDB.Bson;
using MongoDB.Driver;
using Serilog;

namespace adas_core.Infrastructure.Repositories;

public abstract class MongoRepository<T> : IMongoRepository<T>
{
    protected readonly IMongoDatabase Db;

    private IMongoCollection<T>? _collection;

    // protected MongoRepository(IOptions<DatabaseSettings> dbSettings)
    // {
    //     Db = MongoDbHostBuilderExtension.GetMongoDb(dbSettings);
    // }

    protected MongoRepository(IMongoDatabase database)
    {
        Db = database;
    }

    public abstract string GetCollectionName();

    public IMongoCollection<T> Collection
    {
        get
        {
            if (_collection == null)
            {
                var collectionName = GetCollectionName();
                if (!CollectionExists(collectionName)) Db.CreateCollection(collectionName);
                _collection = Db.GetCollection<T>(collectionName);
                _ = CreateIndexes();
                _ = InsertInitialLoad();
            }

            return _collection;
        }
        set => _collection = value;
    }

    public virtual async Task InsertOneAsync(T obj)
    {
        try
        {
            await Collection.InsertOneAsync(obj);
        }
        catch (Exception ex)
        {
            Log.Error("An error occurred: {ExMessage}", ex.Message);
        }
    }

    public async Task UpdateOneAsync(ObjectId id, T obj)
    {
        try
        {
            var filter = Builders<T>.Filter.Eq("_id", id);

            await Collection.ReplaceOneAsync(filter, obj, new ReplaceOptions { IsUpsert = true });
        }
        catch (Exception ex)
        {
            Log.Error("Error updating id: {Id}. Exception:{Ex}, stackTrace: {Trace}", id.ToString(), ex.Message,
                ex.StackTrace);
        }
    }

    public async Task<T?> DeleteAsync(ObjectId id)
    {
        try
        {
            var filter = Builders<T>.Filter.Eq("_id", id);
            return await Collection.FindOneAndDeleteAsync(filter);
        }
        catch (Exception ex)
        {
            Log.Error("An error occurred: {ExMessage}", ex.Message);
            return default;
        }
    }

    public virtual Task CreateIndexes()
    {
        return Task.CompletedTask;
    }

    public virtual Task InsertInitialLoad()
    {
        return Task.CompletedTask;
    }

    public virtual async Task InsertManyAsync(List<T> obj)
    {
        try
        {
            InsertManyOptions options = new() { IsOrdered = false };
            await Collection.InsertManyAsync(obj, options);
        }
        catch (Exception ex)
        {
            Log.Error("An error occurred: {ExMessage}", ex.Message);
        }
    }

    protected async Task UpdateManyObjectIdAsync(string nameId, ObjectId id, ObjectId oldId)
    {
        try
        {
            var update = Builders<T>.Update.Set(nameId, id);
            var filter = Builders<T>.Filter.Eq(nameId, oldId);

            await Collection.UpdateManyAsync(filter, update);
        }
        catch (Exception ex)
        {
            Log.Error("An error occurred: {ExMessage}", ex.Message);
        }
    }

    protected async Task<T?> DeleteAsync(string id)
    {
        try
        {
            var filter = Builders<T>.Filter.Eq("_id", id);
            return await Collection.FindOneAndDeleteAsync(filter);
        }
        catch (Exception ex)
        {
            Log.Error("An error occurred: {ExMessage}", ex.Message);
            return default;
        }
    }

    protected bool CollectionExists(string collectionName)
    {
        try
        {
            var filter = new BsonDocument("name", collectionName);
            var options = new ListCollectionNamesOptions { Filter = filter };

            return Db.ListCollectionNames(options).Any();
        }
        catch (Exception ex)
        {
            Log.Error("An error occurred: {ExMessage}", ex.Message);
            return false;
        }
    }
}