using audit_logs.Models.AppSettings;
using Microsoft.Extensions.Options;
using MongoDB.Bson;
using MongoDB.Driver;
using audit_logs.Utils;
using audit_logs.Repositories.Interfaces;
using Serilog;

namespace audit.Repositories;

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

        protected MongoRepository(IOptions<MongoDbSettingsAudit> dbSettings)
        {
            _db = MongoDbHostBuilderExtension.GetMongoDB(dbSettings);
        }

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

        public abstract string GetCollectionName();

        protected IMongoCollection<T>? _collection;
        public IMongoCollection<T> Collection
        {
            get
            {
                if (_collection == null)
                {
                    var collectionName = GetCollectionName();
                    if(!this.CollectionExists(collectionName)) _db.CreateCollection(collectionName);
                    _collection = _db.GetCollection<T>(collectionName);
                    _ = CreateIndexes();

                }
                return _collection;
            }
            set
            {
                _collection = value;
            }
        }

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

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

        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);
            }
             
        }

        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);
            }
            
        }


        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);
            }
                       
        }

        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;
            }
            
        }

        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; 
            }
            
        }

        private 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;
            }
            
        }
        public async Task<IEnumerable<T>> GetPaginatedAsync(int pageNumber, int pageSize,FilterDefinition<T>? filter = null,SortDefinition<T> sort=null)
        {
            if (pageNumber <= 0) throw new ArgumentException("Page number must be greater than 0.", nameof(pageNumber));
            if (pageSize <= 0) throw new ArgumentException("Page size must be greater than 0.", nameof(pageSize));
            filter ??= FilterDefinition<T>.Empty; // Usa un filtro vacío si no se proporcionó ninguno

            try
            {
                return await Collection
                    .Find(filter)
                    .Skip((pageNumber - 1) * pageSize)
                    .Sort(sort)
                    .Limit(pageSize)
                    .ToListAsync();
            }
            catch (Exception ex)
            {
                Log.Error("Error retrieving paginated data: {ExMessage}", ex.Message);
                return Enumerable.Empty<T>();
            }
        }

        
        public async Task<long> CountDocumentsAsync(FilterDefinition<T>? filter = null)
        {
            filter ??= FilterDefinition<T>.Empty; // Usa un filtro vacío porsi no se pasa como parametro

            return await Collection.CountDocumentsAsync(filter);
        }




    }
