using Marco.Pms.Model.MongoDBModels.Expenses;
using Marco.Pms.Model.Utilities;
using Microsoft.Extensions.Configuration;
using MongoDB.Bson;
using MongoDB.Driver;

namespace Marco.Pms.Helpers.CacheHelper
{
    public class ExpenseCache
    {
        private readonly IMongoCollection<ExpenseDetailsMongoDB> _collection;
        public ExpenseCache(IConfiguration configuration)
        {

            var connectionString = configuration["MongoDB:ConnectionString"];
            var mongoUrl = new MongoUrl(connectionString);
            var client = new MongoClient(mongoUrl); // Your MongoDB connection string
            var mongoDB = client.GetDatabase(mongoUrl.DatabaseName);  // Your MongoDB Database name
            _collection = mongoDB.GetCollection<ExpenseDetailsMongoDB>("Expenses");
        }
        public async Task AddExpenseToCacheAsync(ExpenseDetailsMongoDB expense)
        {
            await _collection.InsertOneAsync(expense);

            await InitializeCollectionAsync();
        }
        public async Task AddExpensesListToCacheAsync(List<ExpenseDetailsMongoDB> expenses)
        {
            // 1. Add a guard clause to avoid an unnecessary database call for an empty list.
            if (expenses == null || !expenses.Any())
            {
                return;
            }

            // 2. Perform the insert operation. This is the only responsibility of this method.
            await _collection.InsertManyAsync(expenses);
            await InitializeCollectionAsync();
        }
        public async Task<(int totalPages, long totalCount, List<ExpenseDetailsMongoDB> expenseList)> GetExpenseListFromCacheAsync(Guid tenantId, Guid loggedInEmployeeId, bool viewAll,
            bool viewSelf, int pageNumber, int pageSize, ExpensesFilter? expenseFilter, string? searchString)
        {
            var filterBuilder = Builders<ExpenseDetailsMongoDB>.Filter;
            var filter = filterBuilder.Empty;

            // Permission-based filter
            if (!viewAll && viewSelf)
            {
                filter &= filterBuilder.Eq(e => e.CreatedBy.Id, loggedInEmployeeId.ToString());
            }
            else
            {
                filter &= filterBuilder.Or(
                        filterBuilder.Eq(e => e.CreatedBy.Id, loggedInEmployeeId.ToString()),
                        filterBuilder.Ne(e => e.Status.Id, "297e0d8f-f668-41b5-bfea-e03b354251c8")
                        );
            }

            // Apply filters

            if (expenseFilter != null)
            {
                if (expenseFilter.StartDate.HasValue && expenseFilter.EndDate.HasValue && expenseFilter.IsTransactionDate == false)
                {
                    filter &= filterBuilder.Gte(e => e.CreatedAt, expenseFilter.StartDate.Value.Date)
                           & filterBuilder.Lte(e => e.CreatedAt, expenseFilter.EndDate.Value.Date.AddDays(1).AddTicks(-1));
                }

                if (expenseFilter.StartDate.HasValue && expenseFilter.EndDate.HasValue && expenseFilter.IsTransactionDate)
                {
                    filter &= filterBuilder.Gte(e => e.TransactionDate, expenseFilter.StartDate.Value.Date)
                           & filterBuilder.Lte(e => e.TransactionDate, expenseFilter.EndDate.Value.Date.AddDays(1).AddTicks(-1));
                }

                if (expenseFilter.ProjectIds?.Any() == true)
                {
                    filter &= filterBuilder.In(e => e.Project.Id, expenseFilter.ProjectIds.Select(p => p.ToString()).ToList());
                }

                if (expenseFilter.StatusIds?.Any() == true)
                {
                    filter &= filterBuilder.In(e => e.Status.Id, expenseFilter.StatusIds.Select(p => p.ToString()).ToList());
                }

                if (expenseFilter.PaidById?.Any() == true)
                {
                    filter &= filterBuilder.In(e => e.PaidBy.Id, expenseFilter.PaidById.Select(p => p.ToString()).ToList());
                }

                if (expenseFilter.CreatedByIds?.Any() == true && viewAll)
                {
                    filter &= filterBuilder.In(e => e.CreatedBy.Id, expenseFilter.CreatedByIds.Select(p => p.ToString()).ToList());
                }
            }

            if (!string.IsNullOrWhiteSpace(searchString))
            {
                var searchPattern = new BsonRegularExpression(searchString, "i");

                // The base text searches remain the same
                var searchClauses = new List<FilterDefinition<ExpenseDetailsMongoDB>>
                {
                    filterBuilder.Regex(e => e.Description, searchPattern),
                    filterBuilder.Regex(e => e.TransactionId, searchPattern)
                };

                // Build the complex filter for PaidBy.FullName
                var paidByFilter = new BsonDocument("$expr",
                    new BsonDocument("$regexMatch", new BsonDocument
                        {
                            { "input", new BsonDocument("$concat", new BsonArray { "$PaidBy.FirstName", " ", "$PaidBy.LastName" }) },
                            { "regex", searchString }, // BsonRegularExpression can't be used here, pass the string
                            { "options", "i" }        // Case-insensitivity option
                        })
                    );
                searchClauses.Add(paidByFilter);

                // Build the complex filter for CreatedBy.FullName
                var createdByFilter = new BsonDocument("$expr",
                    new BsonDocument("$regexMatch", new BsonDocument
                        {
                            { "input", new BsonDocument("$concat", new BsonArray { "$CreatedBy.FirstName", " ", "$CreatedBy.LastName" }) },
                            { "regex", searchString },
                            { "options", "i" }
                        })
                    );
                searchClauses.Add(createdByFilter);

                // Combine all clauses with an OR
                filter &= filterBuilder.Or(searchClauses);
            }

            // Total count
            var totalCount = await _collection.CountDocumentsAsync(filter);
            var totalPages = (int)Math.Ceiling((double)totalCount / pageSize);

            // Fetch paginated data
            var expenses = await _collection
                .Find(filter)
                .Skip((pageNumber - 1) * pageSize)
                .Limit(pageSize)
                .SortByDescending(e => e.CreatedAt)
                .ToListAsync();

            return (totalPages, totalCount, expenses);
        }

        public async Task<ExpenseDetailsMongoDB> GetExpenseDetailsByIdAsync(Guid id, Guid tenantId)
        {
            var filter = Builders<ExpenseDetailsMongoDB>.Filter.And(
                    Builders<ExpenseDetailsMongoDB>.Filter.Eq(e => e.Id, id.ToString()),
                    Builders<ExpenseDetailsMongoDB>.Filter.Eq(e => e.TenantId, tenantId.ToString())
                );
            var expense = await _collection.Find(filter).FirstOrDefaultAsync();

            return expense;
        }

        public async Task<bool> DeleteExpenseFromCacheAsync(Guid id, Guid tenantId)
        {
            var filter = Builders<ExpenseDetailsMongoDB>.Filter.And(
                    Builders<ExpenseDetailsMongoDB>.Filter.Eq(e => e.Id, id.ToString()),
                    Builders<ExpenseDetailsMongoDB>.Filter.Eq(e => e.TenantId, tenantId.ToString())
                );
            var result = await _collection.DeleteOneAsync(filter);
            return result.DeletedCount > 0;
        }
        private async Task InitializeCollectionAsync()
        {
            var indexKeys = Builders<ExpenseDetailsMongoDB>.IndexKeys.Ascending(x => x.ExpireAt);
            var indexOptions = new CreateIndexOptions
            {
                ExpireAfter = TimeSpan.Zero // required for fixed expiration time
            };
            var indexModel = new CreateIndexModel<ExpenseDetailsMongoDB>(indexKeys, indexOptions);
            await _collection.Indexes.CreateOneAsync(indexModel);
        }
    }
}
