using MongoDB.Bson;
using MongoDB.Driver;
using MongoDB.Driver.Linq;
using System.Linq.Expressions;
using System.Reflection;
namespace Core.Blueprint.Mongo
{
    /// 
    /// Provides methods for interacting with a MongoDB collection for a specific document type.
    /// Inherits from  and implements .
    /// This class encapsulates common database operations such as querying, inserting, updating, and deleting documents.
    /// 
    /// The type of document stored in the collection, which must implement .
    public class CollectionRepository(IMongoDatabase database) : ICollectionsRepository where TDocument : IDocument
    {
        private IMongoCollection _collection;
        /// 
        /// Initializes the MongoDB collection based on the  attribute
        /// applied to the  type. Throws an exception if the attribute is not present.
        /// 
        public void CollectionInitialization()
        {
            var collectionAttribute = typeof(TDocument).GetCustomAttribute();
            if (collectionAttribute == null)
            {
                throw new InvalidOperationException($"The class {typeof(TDocument).Name} is missing the CollectionAttributeName attribute.");
            }
            string collectionName = collectionAttribute.Name;
            _collection = database.GetCollection(collectionName);
        }
        /// 
        /// Returns all documents from the collection as an enumerable list.
        /// 
        /// A task that represents the asynchronous operation. The task result contains an enumerable list of documents.
        public virtual async ValueTask> AsQueryable()
        {
            return await _collection.AsQueryable().ToListAsync();
        }
        /// 
        /// Filters documents in the collection based on the provided filter expression.
        /// 
        /// A lambda expression that defines the filter criteria for the documents.
        /// A task that represents the asynchronous operation. The task result contains a list of documents that match the filter.
        public virtual async Task> FilterBy(
            Expression> filterExpression)
        {
            var objectResult = await _collection.FindAsync(filterExpression).ConfigureAwait(false);
            return objectResult.ToList();
        }
        /// 
        /// Filters documents in the collection based on the provided filter and projection expressions.
        /// Projects the filtered documents into a new type.
        /// 
        /// The type to project the documents into.
        /// A lambda expression that defines the filter criteria for the documents.
        /// A lambda expression that defines how the documents should be projected.
        /// An enumerable collection of projected documents.
        public virtual IEnumerable FilterBy(
            Expression> filterExpression,
            Expression> projectionExpression)
        {
            return _collection.Find(filterExpression).Project(projectionExpression).ToEnumerable();
        }
        /// 
        /// Finds a single document that matches the provided filter expression.
        /// 
        /// A lambda expression that defines the filter criteria for the document.
        /// The document that matches the filter, or null if no document is found.
        public virtual TDocument FindOne(Expression> filterExpression)
        {
            return _collection.Find(filterExpression).FirstOrDefault();
        }
        /// 
        /// Filters documents in the collection based on the provided MongoDB filter definition.
        /// 
        /// A filter definition for MongoDB query.
        /// A task that represents the asynchronous operation. The task result contains a list of documents that match the filter.
        public virtual async Task> FilterByMongoFilterAsync(FilterDefinition filterDefinition)
        {
            var objectResult = await _collection.Find(filterDefinition).ToListAsync().ConfigureAwait(false);
            return objectResult;
        }
        /// 
        /// Asynchronously finds a single document that matches the provided filter expression.
        /// 
        /// A lambda expression that defines the filter criteria for the document.
        /// A task that represents the asynchronous operation. The task result contains the document that matches the filter, or null if no document is found.
        public virtual Task FindOneAsync(Expression> filterExpression)
        {
            return Task.Run(() => _collection.Find(filterExpression).FirstOrDefaultAsync());
        }
        /// 
        /// Finds a document by its unique identifier (ID).
        /// 
        /// The unique identifier of the document.
        /// The document that matches the specified ID, or null if no document is found.
        public virtual TDocument FindById(string id)
        {
            var filter = Builders.Filter.Eq(doc => doc._Id, id);
            return _collection.Find(filter).SingleOrDefault();
        }
        /// 
        /// Asynchronously finds a document by its unique identifier (ID).
        /// 
        /// The unique identifier of the document.
        /// A task that represents the asynchronous operation. The task result contains the document that matches the specified ID, or null if no document is found.
        public virtual Task FindByIdAsync(string id)
        {
            return Task.Run(() =>
            {
                var objectId = new ObjectId(id);
                var filter = Builders.Filter.Eq(doc => doc._Id, id);
                return _collection.Find(filter).SingleOrDefaultAsync();
            });
        }
        /// 
        /// Inserts a single document into the collection.
        /// 
        /// The document to insert.
        public virtual void InsertOne(TDocument document)
        {
            _collection.InsertOne(document);
        }
        /// 
        /// Asynchronously inserts a single document into the collection.
        /// 
        /// The document to insert.
        /// A task that represents the asynchronous operation.
        public virtual Task InsertOneAsync(TDocument document)
        {
            return Task.Run(() => _collection.InsertOneAsync(document));
        }
        /// 
        /// Inserts multiple documents into the collection.
        /// 
        /// The collection of documents to insert.
        public void InsertMany(ICollection documents)
        {
            _collection.InsertMany(documents);
        }
        /// 
        /// Asynchronously inserts multiple documents into the collection.
        /// 
        /// The collection of documents to insert.
        /// A task that represents the asynchronous operation.
        public virtual async Task InsertManyAsync(ICollection documents)
        {
            await _collection.InsertManyAsync(documents);
        }
        /// 
        /// Replaces an existing document in the collection.
        /// 
        /// The document with the updated data.
        public void ReplaceOne(TDocument document)
        {
            var filter = Builders.Filter.Eq(doc => doc._Id, document._Id);
            _collection.FindOneAndReplace(filter, document);
        }
        /// 
        /// Asynchronously replaces an existing document in the collection.
        /// 
        /// The document with the updated data.
        /// A task that represents the asynchronous operation.
        public virtual async Task ReplaceOneAsync(TDocument document)
        {
            var filter = Builders.Filter.Eq(doc => doc._Id, document._Id);
            await _collection.FindOneAndReplaceAsync(filter, document);
        }
        /// 
        /// Deletes a single document from the collection based on the provided filter expression.
        /// 
        /// A lambda expression that defines the filter criteria for the document to delete.
        public void DeleteOne(Expression> filterExpression)
        {
            _collection.FindOneAndDelete(filterExpression);
        }
        /// 
        /// Asynchronously deletes a single document from the collection based on the provided filter expression.
        /// 
        /// A lambda expression that defines the filter criteria for the document to delete.
        /// A task that represents the asynchronous operation.
        public async Task DeleteOneAsync(Expression> filterExpression)
        {
            return await _collection.FindOneAndDeleteAsync(filterExpression);
        }
        /// 
        /// Deletes a single document from the collection based on its unique identifier (ID).
        /// 
        /// The unique identifier of the document to delete.
        public void DeleteById(string id)
        {
            var objectId = new ObjectId(id);
            var filter = Builders.Filter.Eq(doc => doc._Id, id);
            _collection.FindOneAndDelete(filter);
        }
        /// 
        /// Asynchronously deletes a single document from the collection based on its unique identifier (ID).
        /// 
        /// The unique identifier of the document to delete.
        /// A task that represents the asynchronous operation.
        public Task DeleteByIdAsync(string id)
        {
            return Task.Run(() =>
            {
                var objectId = new ObjectId(id);
                var filter = Builders.Filter.Eq(doc => doc._Id, id);
                _collection.FindOneAndDeleteAsync(filter);
            });
        }
        /// 
        /// Deletes multiple documents from the collection based on the provided filter expression.
        /// 
        /// A lambda expression that defines the filter criteria for the documents to delete.
        public void DeleteMany(Expression> filterExpression)
        {
            _collection.DeleteMany(filterExpression);
        }
        /// 
        /// Asynchronously deletes multiple documents from the collection based on the provided filter expression.
        /// 
        /// A lambda expression that defines the filter criteria for the documents to delete.
        /// A task that represents the asynchronous operation.
        public Task DeleteManyAsync(Expression> filterExpression)
        {
            return Task.Run(() => _collection.DeleteManyAsync(filterExpression));
        }
    }
}