using AutoMapper;
using Marco.Pms.DataAccess.Data;
using Marco.Pms.Helpers;
using Marco.Pms.Helpers.CacheHelper;
using Marco.Pms.Model.Expenses;
using Marco.Pms.Model.Master;
using Marco.Pms.Model.MongoDBModels;
using Marco.Pms.Model.MongoDBModels.Employees;
using Marco.Pms.Model.MongoDBModels.Expenses;
using Marco.Pms.Model.MongoDBModels.Masters;
using Marco.Pms.Model.MongoDBModels.Project;
using Marco.Pms.Model.MongoDBModels.Utility;
using Marco.Pms.Model.OrganizationModel;
using Marco.Pms.Model.Projects;
using Marco.Pms.Model.Utilities;
using MarcoBMS.Services.Service;
using Microsoft.EntityFrameworkCore;
using MongoDB.Driver;
using Project = Marco.Pms.Model.Projects.Project;

namespace Marco.Pms.Services.Helpers
{
    public class CacheUpdateHelper
    {
        private readonly IDbContextFactory<ApplicationDbContext> _dbContextFactory;
        private readonly ApplicationDbContext _context;
        private readonly IMapper _mapper;
        private readonly ProjectCache _projectCache;
        private readonly EmployeeCache _employeeCache;
        private readonly ReportCache _reportCache;
        private readonly ExpenseCache _expenseCache;
        private readonly ILoggingService _logger;
        private readonly GeneralHelper _generalHelper;
        private static readonly Guid Draft = Guid.Parse("297e0d8f-f668-41b5-bfea-e03b354251c8");
        private static readonly Guid Rejected = Guid.Parse("d1ee5eec-24b6-4364-8673-a8f859c60729");

        public CacheUpdateHelper(
            IMapper mapper,
            ProjectCache projectCache,
            EmployeeCache employeeCache,
            ReportCache reportCache,
            ExpenseCache expenseCache,
            ILoggingService logger,
            IDbContextFactory<ApplicationDbContext> dbContextFactory,
            ApplicationDbContext context,
            GeneralHelper generalHelper)
        {
            _mapper = mapper;
            _projectCache = projectCache;
            _employeeCache = employeeCache;
            _reportCache = reportCache;
            _expenseCache = expenseCache;
            _logger = logger;
            _dbContextFactory = dbContextFactory;
            _context = context;
            _generalHelper = generalHelper;
        }
        #region ======================================================= Project Details Cache =======================================================

        public async Task AddProjectDetails(Project project)
        {
            // --- Step 1: Fetch all required data from the database in parallel ---

            // Each task uses its own DbContext instance to avoid concurrency issues.
            var statusTask = Task.Run(async () =>
            {
                using var context = _dbContextFactory.CreateDbContext();
                return await context.StatusMasters
                    .AsNoTracking()
                    .Where(s => s.Id == project.ProjectStatusId)
                    .Select(s => new { s.Id, s.Status }) // Projection
                    .FirstOrDefaultAsync();

            });

            var promotorTask = Task.Run(async () =>
            {
                using var context = _dbContextFactory.CreateDbContext();
                return await context.Organizations
                    .AsNoTracking()
                    .Where(o => o.Id == project.PromoterId)
                    .Select(o => new OrganizationMongoDB
                    {
                        Id = o.Id.ToString(),
                        Name = o.Name,
                        ContactPerson = o.ContactPerson,
                        Email = o.Email,
                        Address = o.Address,
                        ContactNumber = o.ContactNumber,
                        SPRID = o.SPRID
                    }) // Projection
                    .FirstOrDefaultAsync();
            });

            var pmcTask = Task.Run(async () =>
            {
                using var context = _dbContextFactory.CreateDbContext();
                return await context.Organizations
                    .AsNoTracking()
                    .Where(o => o.Id == project.PMCId)
                    .Select(o => new OrganizationMongoDB
                    {
                        Id = o.Id.ToString(),
                        Name = o.Name,
                        ContactPerson = o.ContactPerson,
                        Email = o.Email,
                        Address = o.Address,
                        ContactNumber = o.ContactNumber,
                        SPRID = o.SPRID
                    }) // Projection
                    .FirstOrDefaultAsync();
            });

            var teamSizeTask = Task.Run(async () =>
            {
                using var context = _dbContextFactory.CreateDbContext();
                return await context.ProjectAllocations
                    .AsNoTracking()
                    .CountAsync(pa => pa.ProjectId == project.Id && pa.IsActive); // Server-side count is efficient
            });

            // This task fetches the entire infrastructure hierarchy and performs aggregations in the database.
            var infrastructureTask = Task.Run(async () =>
            {
                using var context = _dbContextFactory.CreateDbContext();

                // 1. Fetch all hierarchical data using projections.
                // This is still a chain, but it's inside one task and much faster due to projections.
                var buildings = await context.Buildings.AsNoTracking()
                    .Where(b => b.ProjectId == project.Id)
                    .Select(b => new { b.Id, b.ProjectId, b.Name, b.Description })
                    .ToListAsync();
                var buildingIds = buildings.Select(b => b.Id).ToList();

                var floors = await context.Floor.AsNoTracking()
                    .Where(f => buildingIds.Contains(f.BuildingId))
                    .Select(f => new { f.Id, f.BuildingId, f.FloorName })
                    .ToListAsync();
                var floorIds = floors.Select(f => f.Id).ToList();

                var workAreas = await context.WorkAreas.AsNoTracking()
                    .Where(wa => floorIds.Contains(wa.FloorId))
                    .Select(wa => new { wa.Id, wa.FloorId, wa.AreaName })
                    .ToListAsync();
                var workAreaIds = workAreas.Select(wa => wa.Id).ToList();

                // 2. THE KEY OPTIMIZATION: Aggregate work items in the database.
                var workSummaries = await context.WorkItems.AsNoTracking()
                    .Where(wi => workAreaIds.Contains(wi.WorkAreaId))
                    .GroupBy(wi => wi.WorkAreaId) // Group by parent on the DB server
                    .Select(g => new // Let the DB do the SUM
                    {
                        WorkAreaId = g.Key,
                        PlannedWork = g.Sum(i => i.PlannedWork),
                        CompletedWork = g.Sum(i => i.CompletedWork)
                    })
                    .ToDictionaryAsync(x => x.WorkAreaId); // Return a ready-to-use dictionary

                return (buildings, floors, workAreas, workSummaries);
            });

            // Wait for all parallel database operations to complete.
            await Task.WhenAll(statusTask, teamSizeTask, infrastructureTask, promotorTask, pmcTask);

            // Get the results from the completed tasks.

            var status = statusTask.Result;
            var teamSize = teamSizeTask.Result;
            var (allBuildings, allFloors, allWorkAreas, workSummariesByWorkAreaId) = infrastructureTask.Result;
            var promotor = promotorTask.Result;
            var pmc = pmcTask.Result;

            // --- Step 2: Process the fetched data and build the MongoDB model ---

            var projectDetails = new ProjectMongoDB
            {
                Id = project.Id.ToString(),
                Name = project.Name,
                ShortName = project.ShortName,
                ProjectAddress = project.ProjectAddress,
                StartDate = project.StartDate,
                EndDate = project.EndDate,
                ContactPerson = project.ContactPerson,
                TeamSize = teamSize
            };

            projectDetails.ProjectStatus = new StatusMasterMongoDB
            {
                Id = status!.Id.ToString(),
                Status = status.Status
            };

            // Use fast in-memory lookups instead of .Where() in loops.
            var floorsByBuildingId = allFloors.ToLookup(f => f.BuildingId);
            var workAreasByFloorId = allWorkAreas.ToLookup(wa => wa.FloorId);

            double totalPlannedWork = 0, totalCompletedWork = 0;
            var buildingMongoList = new List<BuildingMongoDB>();

            foreach (var building in allBuildings)
            {
                double buildingPlanned = 0, buildingCompleted = 0;
                var floorMongoList = new List<FloorMongoDB>();

                foreach (var floor in floorsByBuildingId[building.Id]) // Fast lookup
                {
                    double floorPlanned = 0, floorCompleted = 0;
                    var workAreaMongoList = new List<WorkAreaMongoDB>();

                    foreach (var wa in workAreasByFloorId[floor.Id]) // Fast lookup
                    {
                        // Get the pre-calculated summary from the dictionary. O(1) operation.
                        workSummariesByWorkAreaId.TryGetValue(wa.Id, out var summary);
                        var waPlanned = summary?.PlannedWork ?? 0;
                        var waCompleted = summary?.CompletedWork ?? 0;

                        workAreaMongoList.Add(new WorkAreaMongoDB
                        {
                            Id = wa.Id.ToString(),
                            FloorId = wa.FloorId.ToString(),
                            AreaName = wa.AreaName,
                            PlannedWork = waPlanned,
                            CompletedWork = waCompleted
                        });

                        floorPlanned += waPlanned;
                        floorCompleted += waCompleted;
                    }

                    floorMongoList.Add(new FloorMongoDB
                    {
                        Id = floor.Id.ToString(),
                        BuildingId = floor.BuildingId.ToString(),
                        FloorName = floor.FloorName,
                        PlannedWork = floorPlanned,
                        CompletedWork = floorCompleted,
                        WorkAreas = workAreaMongoList
                    });

                    buildingPlanned += floorPlanned;
                    buildingCompleted += floorCompleted;
                }

                buildingMongoList.Add(new BuildingMongoDB
                {
                    Id = building.Id.ToString(),
                    ProjectId = building.ProjectId.ToString(),
                    BuildingName = building.Name,
                    Description = building.Description,
                    PlannedWork = buildingPlanned,
                    CompletedWork = buildingCompleted,
                    Floors = floorMongoList
                });

                totalPlannedWork += buildingPlanned;
                totalCompletedWork += buildingCompleted;
            }

            projectDetails.Buildings = buildingMongoList;
            projectDetails.PlannedWork = totalPlannedWork;
            projectDetails.CompletedWork = totalCompletedWork;
            projectDetails.Promoter = promotor;
            projectDetails.PMC = pmc;

            try
            {
                await _projectCache.AddProjectDetailsToCache(projectDetails);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error occurred while adding project {ProjectId} to Cache", project.Id);
            }
        }
        public async Task AddProjectDetailsList(List<Project> projects)
        {
            var projectIds = projects.Select(p => p.Id).ToList();
            if (!projectIds.Any())
            {
                return; // Nothing to do
            }
            var projectStatusIds = projects.Select(p => p.ProjectStatusId).Distinct().ToList();
            var promotorIds = projects.Select(p => p.PromoterId).Distinct().ToList();
            var pmcsIds = projects.Select(p => p.PMCId).Distinct().ToList();

            // --- Step 1: Fetch all required data in maximum parallel ---
            // Each task uses its own DbContext and selects only the required columns (projection).

            var statusTask = Task.Run(async () =>
            {
                using var context = _dbContextFactory.CreateDbContext();
                return await context.StatusMasters
                    .AsNoTracking()
                    .Where(s => projectStatusIds.Contains(s.Id))
                    .Select(s => new { s.Id, s.Status }) // Projection
                    .ToDictionaryAsync(s => s.Id);
            });

            var organizationTask = Task.Run(async () =>
            {
                using var context = _dbContextFactory.CreateDbContext();
                return await context.Organizations
                    .AsNoTracking()
                    .Where(o => promotorIds.Contains(o.Id) || pmcsIds.Contains(o.Id))
                    .Select(o => new OrganizationMongoDB
                    {
                        Id = o.Id.ToString(),
                        Name = o.Name,
                        ContactPerson = o.ContactPerson,
                        Email = o.Email,
                        Address = o.Address,
                        ContactNumber = o.ContactNumber,
                        SPRID = o.SPRID
                    }) // Projection
                    .ToListAsync();
            });

            var teamSizeTask = Task.Run(async () =>
            {
                using var context = _dbContextFactory.CreateDbContext();
                // Server-side aggregation and projection into a dictionary
                return await context.ProjectAllocations
                    .AsNoTracking()
                    .Where(pa => projectIds.Contains(pa.ProjectId) && pa.IsActive)
                    .GroupBy(pa => pa.ProjectId)
                    .Select(g => new { ProjectId = g.Key, Count = g.Count() })
                    .ToDictionaryAsync(x => x.ProjectId, x => x.Count);
            });

            var buildingsTask = Task.Run(async () =>
            {
                using var context = _dbContextFactory.CreateDbContext();
                return await context.Buildings
                    .AsNoTracking()
                    .Where(b => projectIds.Contains(b.ProjectId))
                    .Select(b => new { b.Id, b.ProjectId, b.Name, b.Description }) // Projection
                    .ToListAsync();
            });

            // We need the building IDs for the next level, so we must await this one first.
            var allBuildings = await buildingsTask;
            var buildingIds = allBuildings.Select(b => b.Id).ToList();

            var floorsTask = Task.Run(async () =>
            {
                using var context = _dbContextFactory.CreateDbContext();
                return await context.Floor
                    .AsNoTracking()
                    .Where(f => buildingIds.Contains(f.BuildingId))
                    .Select(f => new { f.Id, f.BuildingId, f.FloorName }) // Projection
                    .ToListAsync();
            });

            // We need floor IDs for the next level.
            var allFloors = await floorsTask;
            var floorIds = allFloors.Select(f => f.Id).ToList();

            var workAreasTask = Task.Run(async () =>
            {
                using var context = _dbContextFactory.CreateDbContext();
                return await context.WorkAreas
                    .AsNoTracking()
                    .Where(wa => floorIds.Contains(wa.FloorId))
                    .Select(wa => new { wa.Id, wa.FloorId, wa.AreaName }) // Projection
                    .ToListAsync();
            });

            // The most powerful optimization: Aggregate work items in the database.
            var workSummaryTask = Task.Run(async () =>
            {
                using var context = _dbContextFactory.CreateDbContext();
                var workAreaIds = await context.WorkAreas
                                        .Where(wa => floorIds.Contains(wa.FloorId))
                                        .Select(wa => wa.Id)
                                        .ToListAsync();

                // Let the DB do the SUM. This is much faster and transfers less data.
                return await context.WorkItems
                    .AsNoTracking()
                    .Where(wi => workAreaIds.Contains(wi.WorkAreaId))
                    .GroupBy(wi => wi.WorkAreaId)
                    .Select(g => new
                    {
                        WorkAreaId = g.Key,
                        PlannedWork = g.Sum(wi => wi.PlannedWork),
                        CompletedWork = g.Sum(wi => wi.CompletedWork)
                    })
                    .ToDictionaryAsync(x => x.WorkAreaId);
            });

            // Await the remaining parallel tasks.
            await Task.WhenAll(statusTask, teamSizeTask, workAreasTask, workSummaryTask, organizationTask);

            // --- Step 2: Process the fetched data and build the MongoDB models ---

            var allStatuses = statusTask.Result;
            var teamSizesByProjectId = teamSizeTask.Result;
            var allWorkAreas = workAreasTask.Result;
            var workSummariesByWorkAreaId = workSummaryTask.Result;
            var organizations = organizationTask.Result;

            // Create fast in-memory lookups for hierarchical data
            var buildingsByProjectId = allBuildings.ToLookup(b => b.ProjectId);
            var floorsByBuildingId = allFloors.ToLookup(f => f.BuildingId);
            var workAreasByFloorId = allWorkAreas.ToLookup(wa => wa.FloorId);


            var projectDetailsList = new List<ProjectMongoDB>(projects.Count);
            foreach (var project in projects)
            {
                var projectDetails = new ProjectMongoDB
                {
                    Id = project.Id.ToString(),
                    Name = project.Name,
                    ShortName = project.ShortName,
                    ProjectAddress = project.ProjectAddress,
                    StartDate = project.StartDate,
                    EndDate = project.EndDate,
                    ContactPerson = project.ContactPerson,
                    TeamSize = teamSizesByProjectId.GetValueOrDefault(project.Id, 0)
                };

                if (allStatuses.TryGetValue(project.ProjectStatusId, out var status))
                {
                    projectDetails.ProjectStatus = new StatusMasterMongoDB
                    {
                        Id = status.Id.ToString(),
                        Status = status.Status
                    };
                }

                double totalPlannedWork = 0, totalCompletedWork = 0;
                var buildingMongoList = new List<BuildingMongoDB>();

                foreach (var building in buildingsByProjectId[project.Id])
                {
                    double buildingPlanned = 0, buildingCompleted = 0;
                    var floorMongoList = new List<FloorMongoDB>();

                    foreach (var floor in floorsByBuildingId[building.Id])
                    {
                        double floorPlanned = 0, floorCompleted = 0;
                        var workAreaMongoList = new List<WorkAreaMongoDB>();

                        foreach (var wa in workAreasByFloorId[floor.Id])
                        {
                            double waPlanned = 0, waCompleted = 0;
                            if (workSummariesByWorkAreaId.TryGetValue(wa.Id, out var summary))
                            {
                                waPlanned = summary.PlannedWork;
                                waCompleted = summary.CompletedWork;
                            }

                            workAreaMongoList.Add(new WorkAreaMongoDB
                            {
                                Id = wa.Id.ToString(),
                                FloorId = wa.FloorId.ToString(),
                                AreaName = wa.AreaName,
                                PlannedWork = waPlanned,
                                CompletedWork = waCompleted
                            });

                            floorPlanned += waPlanned;
                            floorCompleted += waCompleted;
                        }

                        floorMongoList.Add(new FloorMongoDB
                        {
                            Id = floor.Id.ToString(),
                            BuildingId = floor.BuildingId.ToString(),
                            FloorName = floor.FloorName,
                            PlannedWork = floorPlanned,
                            CompletedWork = floorCompleted,
                            WorkAreas = workAreaMongoList
                        });

                        buildingPlanned += floorPlanned;
                        buildingCompleted += floorCompleted;
                    }

                    buildingMongoList.Add(new BuildingMongoDB
                    {
                        Id = building.Id.ToString(),
                        ProjectId = building.ProjectId.ToString(),
                        BuildingName = building.Name,
                        Description = building.Description,
                        PlannedWork = buildingPlanned,
                        CompletedWork = buildingCompleted,
                        Floors = floorMongoList
                    });

                    totalPlannedWork += buildingPlanned;
                    totalCompletedWork += buildingCompleted;
                }

                projectDetails.Buildings = buildingMongoList;
                projectDetails.PlannedWork = totalPlannedWork;
                projectDetails.CompletedWork = totalCompletedWork;
                projectDetails.Promoter = organizations.FirstOrDefault(o => o.Id == project.PromoterId.ToString());
                projectDetails.PMC = organizations.FirstOrDefault(o => o.Id == project.PMCId.ToString());

                projectDetailsList.Add(projectDetails);
            }

            // --- Step 3: Update the cache ---
            try
            {
                await _projectCache.AddProjectDetailsListToCache(projectDetailsList);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error occurred while adding project list to Cache");
            }
        }
        public async Task<bool> UpdateProjectDetailsOnly(Project project)
        {
            var projectStatusTask = Task.Run(async () =>
            {
                await using var context = await _dbContextFactory.CreateDbContextAsync();
                return await context.StatusMasters
                .FirstOrDefaultAsync(s => s.Id == project.ProjectStatusId) ?? new StatusMaster();
            });
            var promotorTask = Task.Run(async () =>
            {
                await using var context = await _dbContextFactory.CreateDbContextAsync();
                return await context.Organizations.FirstOrDefaultAsync(o => o.Id == project.PromoterId) ?? new Organization();
            });
            var pmcTask = Task.Run(async () =>
            {
                await using var context = await _dbContextFactory.CreateDbContextAsync();
                return await context.Organizations.FirstOrDefaultAsync(o => o.Id == project.PMCId) ?? new Organization();
            });

            await Task.WhenAll(projectStatusTask, promotorTask, pmcTask);

            var projectStatus = projectStatusTask.Result;
            var promotor = promotorTask.Result;
            var pmc = pmcTask.Result;

            try
            {
                bool response = await _projectCache.UpdateProjectDetailsOnlyToCache(project, projectStatus, promotor, pmc);
                return response;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error occured while updating project {ProjectId} to Cache", project.Id);
                return false;
            }
        }
        public async Task<ProjectMongoDB?> GetProjectDetails(Guid projectId)
        {
            try
            {
                var response = await _projectCache.GetProjectDetailsFromCache(projectId);
                return response;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error occured while getting project {ProjectId} to Cache");
                return null;
            }
        }
        public async Task<ProjectMongoDB?> GetProjectDetailsWithBuildings(Guid projectId)
        {
            try
            {
                var response = await _projectCache.GetProjectDetailsWithBuildingsFromCache(projectId);
                return response;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error occured while getting project {ProjectId} to Cache");
                return null;
            }
        }
        public async Task<List<ProjectMongoDB>?> GetProjectDetailsList(List<Guid> projectIds)
        {
            try
            {
                var response = await _projectCache.GetProjectDetailsListFromCache(projectIds);
                if (response.Any())
                {
                    return response;
                }
                else
                {
                    return null;
                }
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error occured while getting list of project details from to Cache");
                return null;
            }
        }
        public async Task DeleteProjectByIdAsync(Guid projectId)
        {
            try
            {
                var response = await _projectCache.DeleteProjectByIdFromCacheAsync(projectId);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error occured while deleting project from to Cache");

            }
        }
        public async Task RemoveProjectsAsync(List<Guid> projectIds)
        {
            try
            {
                var response = await _projectCache.RemoveProjectsFromCacheAsync(projectIds);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error occured while deleting project list from to Cache");

            }
        }

        #endregion

        #region ======================================================= Project Infrastructure Cache =======================================================

        public async Task AddBuildngInfra(Guid projectId, Building? building = null, Floor? floor = null, WorkArea? workArea = null, Guid? buildingId = null)
        {
            try
            {
                await _projectCache.AddBuildngInfraToCache(projectId, building, floor, workArea, buildingId);
            }
            catch (Exception ex)
            {
                _logger.LogWarning("Error occured while adding project infra for project {ProjectId} to Cache: {Error}", projectId, ex.Message);
            }
        }
        public async Task UpdateBuildngInfra(Guid projectId, Building? building = null, Floor? floor = null, WorkArea? workArea = null, Guid? buildingId = null)
        {
            try
            {
                var response = await _projectCache.UpdateBuildngInfraToCache(projectId, building, floor, workArea, buildingId);
                if (!response)
                {
                    await _projectCache.AddBuildngInfraToCache(projectId, building, floor, workArea, buildingId);
                }
            }
            catch (Exception ex)
            {
                _logger.LogWarning("Error occured while updating project infra for project {ProjectId} to Cache: {Error}", projectId, ex.Message);
            }
        }
        public async Task<List<BuildingMongoDB>?> GetBuildingInfra(Guid projectId)
        {
            try
            {
                var response = await _projectCache.GetBuildingInfraFromCache(projectId);
                return response;
            }
            catch (Exception ex)
            {
                _logger.LogWarning("Error occured while getting project infra for project {ProjectId} form Cache: {Error}", projectId, ex.Message);
                return null;
            }
        }
        public async Task UpdatePlannedAndCompleteWorksInBuilding(Guid workAreaId, double plannedWork = 0, double completedWork = 0)
        {
            try
            {
                await _projectCache.UpdatePlannedAndCompleteWorksInBuildingFromCache(workAreaId, plannedWork, completedWork);
            }
            catch (Exception ex)
            {
                _logger.LogWarning("Error occured while updating planned work and completed work in building infra form Cache: {Error}", ex.Message);
            }
        }
        public async Task<WorkAreaInfoMongoDB?> GetBuildingAndFloorByWorkAreaId(Guid workAreaId)
        {
            try
            {
                var response = await _projectCache.GetBuildingAndFloorByWorkAreaIdFromCache(workAreaId);
                return response;
            }
            catch (Exception ex)
            {
                _logger.LogWarning("Error occured while fetching workArea Details using its ID form Cache: {Error}", ex.Message);
                return null;
            }
        }

        #endregion

        #region ======================================================= WorkItem Cache =======================================================

        public async Task<List<WorkItemMongoDB>?> GetWorkItemsByWorkAreaIds(List<Guid> workAreaIds, List<Guid> serviceIds)
        {
            try
            {
                var response = await _projectCache.GetWorkItemsByWorkAreaIdsFromCache(workAreaIds, serviceIds);
                if (response.Count > 0)
                {
                    return response;
                }
                return null;
            }
            catch (Exception ex)
            {
                _logger.LogWarning("Error occured while fetching workItems list using workArea IDs list form Cache: {Error}", ex.Message);
                return null;
            }
        }
        public async Task ManageWorkItemDetails(List<WorkItem> workItems)
        {
            try
            {
                var workAreaId = workItems.First().WorkAreaId;
                var workItemDB = await _generalHelper.GetWorkItemsListFromDB(workAreaId);
                await _projectCache.ManageWorkItemDetailsToCache(workItemDB);
            }
            catch (Exception ex)
            {
                _logger.LogWarning("Error occured while saving workItems form Cache: {Error}", ex.Message);
            }
        }
        public async Task ManageWorkItemDetailsByVM(List<WorkItemMongoDB> workItems)
        {
            try
            {
                await _projectCache.ManageWorkItemDetailsToCache(workItems);
            }
            catch (Exception ex)
            {
                _logger.LogWarning("Error occured while saving workItems form Cache: {Error}", ex.Message);
            }
        }
        public async Task<List<WorkItemMongoDB>?> GetWorkItemDetailsByWorkArea(Guid workAreaId, List<Guid> serviceIds)
        {
            try
            {
                var workItems = await _projectCache.GetWorkItemDetailsByWorkAreaFromCache(workAreaId, serviceIds);
                if (workItems.Count > 0)
                {
                    return workItems;
                }
                else
                {
                    return null;
                }
            }
            catch (Exception ex)
            {
                _logger.LogWarning("Error occured while fetching list of workItems form Cache: {Error}", ex.Message);
                return null;
            }
        }
        public async Task<WorkItemMongoDB?> GetWorkItemDetailsById(Guid id)
        {
            try
            {
                var workItem = await _projectCache.GetWorkItemDetailsByIdFromCache(id);
                if (workItem.Id != "")
                {
                    return workItem;
                }
                else
                {
                    return null;
                }
            }
            catch (Exception ex)
            {
                _logger.LogWarning("Error occured while fetching list of workItems form Cache: {Error}", ex.Message);
                return null;
            }
        }
        public async Task UpdatePlannedAndCompleteWorksInWorkItem(Guid id, double plannedWork = 0, double completedWork = 0, double todaysAssigned = 0)
        {
            try
            {
                var response = await _projectCache.UpdatePlannedAndCompleteWorksInWorkItemToCache(id, plannedWork, completedWork, todaysAssigned);
            }
            catch (Exception ex)
            {
                _logger.LogWarning("Error occured while updating planned work, completed work, and today's assigned work in workItems in Cache: {Error}", ex.Message);
            }
        }
        public async Task DeleteWorkItemByIdAsync(Guid workItemId)
        {
            try
            {
                var response = await _projectCache.DeleteWorkItemByIdFromCacheAsync(workItemId);
            }
            catch (Exception ex)
            {
                _logger.LogWarning("Error occured while deleting work item from to Cache: {Error}", ex.Message);

            }
        }

        #endregion

        #region ======================================================= Employee Profile Cache =======================================================

        public async Task AddApplicationRole(Guid employeeId, List<Guid> roleIds, Guid tenantId)
        {
            // 1. Guard Clause: Avoid unnecessary database work if there are no roles to add.
            if (roleIds == null || !roleIds.Any())
            {
                return; // Nothing to add, so the operation did not result in a change.
            }
            Task<List<string>> getPermissionIdsTask = Task.Run(async () =>
            {
                using var context = _dbContextFactory.CreateDbContext();

                return await context.RolePermissionMappings
                .Where(rp => roleIds.Contains(rp.ApplicationRoleId))
                .Select(p => p.FeaturePermissionId.ToString())
                .Distinct()
                .ToListAsync();
            });

            // 3. Prepare role IDs in parallel with the database query.
            var newRoleIds = roleIds.Select(r => r.ToString()).ToList();

            // 4. Await the database query result.
            var newPermissionIds = await getPermissionIdsTask;
            try
            {
                var response = await _employeeCache.AddApplicationRoleToCache(employeeId, newRoleIds, newPermissionIds, tenantId);
            }
            catch (Exception ex)
            {
                _logger.LogWarning("Error occured while adding Application roleIds to Cache to employee {Employee}: {Error}", employeeId, ex.Message);
            }
        }
        public async Task<bool> AddProjects(Guid employeeId, List<Guid> projectIds, Guid tenantId)
        {
            try
            {
                var response = await _employeeCache.AddProjectsToCache(employeeId, projectIds, tenantId);
                return response;
            }
            catch (Exception ex)
            {
                _logger.LogWarning("Error occured while adding projectIds for employee {EmployeeId} to Cache: {Error}", employeeId, ex.Message);
                return false;
            }
        }
        public async Task<List<Guid>?> GetProjects(Guid employeeId, Guid tenantId)
        {
            try
            {
                var response = await _employeeCache.GetProjectsFromCache(employeeId, tenantId);
                if (response.Count > 0)
                {
                    return response;
                }
                return null;
            }
            catch (Exception ex)
            {
                _logger.LogWarning("Error occured while getting projectIds for employee {EmployeeId} from Cache: {Error}", employeeId, ex.Message);
                return null;
            }
        }
        public async Task<List<Guid>?> GetPermissions(Guid employeeId, Guid tenantId)
        {
            try
            {
                var response = await _employeeCache.GetPermissionsFromCache(employeeId, tenantId);
                if (response.Count > 0)
                {
                    return response;
                }
                return null;
            }
            catch (Exception ex)
            {
                _logger.LogWarning("Error occured while getting permissionIds for employee {EmployeeId} from Cache: {Error}", employeeId, ex.Message);
                return null;
            }
        }
        public async Task ClearAllProjectIds(Guid employeeId, Guid tenantId)
        {
            try
            {
                var response = await _employeeCache.ClearAllProjectIdsFromCache(employeeId, tenantId);
            }
            catch (Exception ex)
            {
                _logger.LogWarning("Error occured while deleting projectIds from Cache for employee {EmployeeId}: {Error}", employeeId, ex.Message);
            }
        }
        public async Task ClearAllProjectIdsByRoleId(Guid roleId)
        {
            try
            {
                await _employeeCache.ClearAllProjectIdsByRoleIdFromCache(roleId);
            }
            catch (Exception ex)
            {
                _logger.LogWarning("Error occured while deleting projectIds from Cache for Application Role {RoleId}: {Error}", roleId, ex.Message);
            }
        }
        public async Task ClearAllProjectIdsByPermissionId(Guid permissionId, Guid tenantId)
        {
            try
            {
                await _employeeCache.ClearAllProjectIdsByPermissionIdFromCache(permissionId, tenantId);
            }
            catch (Exception ex)
            {
                _logger.LogWarning("Error occured while deleting projectIds from Cache for Permission {PermissionId}: {Error}", permissionId, ex.Message);
            }
        }
        public async Task ClearAllPermissionIdsByEmployeeID(Guid employeeId, Guid tenantId)
        {
            try
            {
                var response = await _employeeCache.ClearAllPermissionIdsByEmployeeIDFromCache(employeeId, tenantId);
            }
            catch (Exception ex)
            {
                _logger.LogWarning("Error occured while deleting permissionIds from Cache for employee {EmployeeId}: {Error}", employeeId, ex.Message);
            }
        }
        public async Task ClearAllPermissionIdsByRoleId(Guid roleId)
        {
            try
            {
                var response = await _employeeCache.ClearAllPermissionIdsByRoleIdFromCache(roleId);
            }
            catch (Exception ex)
            {
                _logger.LogWarning("Error occured while deleting permissionIds from Cache for Application role {RoleId}: {Error}", roleId, ex.Message);
            }
        }
        public async Task RemoveRoleId(Guid employeeId, Guid roleId, Guid tenantId)
        {
            try
            {
                var response = await _employeeCache.RemoveRoleIdFromCache(employeeId, roleId, tenantId);
            }
            catch (Exception ex)
            {
                _logger.LogWarning("Error occured while deleting Application role {RoleId} from Cache for employee {EmployeeId}: {Error}", roleId, employeeId, ex.Message);
            }
        }
        public async Task ClearAllEmployeesFromCacheByEmployeeIds(List<Guid> employeeIds, Guid tenantId)
        {
            try
            {
                var stringEmployeeIds = employeeIds.Select(e => e.ToString()).ToList();
                var response = await _employeeCache.ClearAllEmployeesFromCacheByEmployeeIds(stringEmployeeIds, tenantId);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error occured while deleting all employees from Cache");
            }
        }
        public async Task ClearAllEmployeesFromCacheByOnlyEmployeeId(Guid employeeId)
        {
            try
            {
                var response = await _employeeCache.ClearAllEmployeesFromCacheByOnlyEmployeeId(employeeId);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error occured while deleting all employees from Cache");
            }
        }
        public async Task ClearAllEmployeesFromCacheByTenantId(Guid tenantId)
        {
            try
            {
                var response = await _employeeCache.ClearAllEmployeesFromCacheByTenantId(tenantId);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error occured while deleting all employees from Cache");
            }
        }
        public async Task ClearAllEmployees()
        {
            try
            {
                var response = await _employeeCache.ClearAllEmployeesFromCache();
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error occured while deleting all employees from Cache");
            }
        }

        #endregion

        #region ======================================================= Expenses  Cache =======================================================
        public async Task AddExpenseByObjectAsync(Expenses expense)
        {
            var expenseCache = await GetAllExpnesRelatedTablesForSingle(expense, expense.TenantId);

            try
            {
                await _expenseCache.AddExpenseToCacheAsync(expenseCache);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error occurd while storing expense related table in cahce");
                return;
            }

            return;
        }
        public async Task<ExpenseDetailsMongoDB?> AddExpenseByIdAsync(Guid Id, Guid tenantId)
        {
            var expense = await _context.Expenses.AsNoTracking().FirstOrDefaultAsync(e => e.Id == Id && e.TenantId == tenantId);

            if (expense == null)
            {
                return null;
            }
            var expenseCache = await GetAllExpnesRelatedTablesForSingle(expense, expense.TenantId);

            try
            {
                await _expenseCache.AddExpenseToCacheAsync(expenseCache);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error occurd while storing expense related table in cahce");
                return null;
            }

            return expenseCache;


        }
        public async Task AddExpensesListToCache(List<Expenses> expenses, Guid tenantId)
        {
            var expensesCache = await GetAllExpnesRelatedTablesForList(expenses, tenantId);

            try
            {
                await _expenseCache.AddExpensesListToCacheAsync(expensesCache);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error occured while saving the list of expenses to Cache");
            }
        }

        public async Task<(int totalPages, long totalCount, List<ExpenseDetailsMongoDB>? expenseList)> GetExpenseListAsync(Guid tenantId, Guid loggedInEmployeeId, bool viewAll,
            bool viewSelf, int pageNumber, int pageSize, ExpensesFilter? filter, string? searchString)
        {
            try
            {
                var (totalPages, totalCount, expenseList) = await _expenseCache.GetExpenseListFromCacheAsync(tenantId, loggedInEmployeeId, viewAll, viewSelf, pageNumber, pageSize, filter, searchString);
                if (expenseList.Any())
                {


                    return (totalPages, totalCount, expenseList);
                }

            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error occured while fetching the list of expenses to Cache");
            }
            return (0, 0, null);
        }

        public async Task<ExpenseDetailsMongoDB?> GetExpenseDetailsById(Guid id, Guid tenantId)
        {
            try
            {
                var response = await _expenseCache.GetExpenseDetailsByIdAsync(id, tenantId);
                if (response != null && response.Id != string.Empty)
                {
                    return response;
                }
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error occured while fetching expense details from cache");
            }
            return null;
        }

        public async Task ReplaceExpenseAsync(Expenses expense)
        {
            bool response = false;
            try
            {
                response = await _expenseCache.DeleteExpenseFromCacheAsync(expense.Id, expense.TenantId);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error occured while deleting expense from cache");
            }
            if (response)
            {
                await AddExpenseByObjectAsync(expense);
            }

        }
        public async Task DeleteExpenseAsync(Guid id, Guid tenantId)
        {
            try
            {
                var response = await _expenseCache.DeleteExpenseFromCacheAsync(id, tenantId);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error occured while deleting expense from cache");
            }

        }

        #endregion

        #region ======================================================= Report Cache =======================================================

        public async Task<List<ProjectReportEmailMongoDB>?> GetProjectReportMail(bool IsSend)
        {
            try
            {
                var response = await _reportCache.GetProjectReportMailFromCache(IsSend);
                return response;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error occured while fetching project report mail bodys");
                return null;
            }
        }
        public async Task AddProjectReportMail(ProjectReportEmailMongoDB report)
        {
            try
            {
                await _reportCache.AddProjectReportMailToCache(report);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error occured while adding project report mail bodys");
            }
        }

        #endregion


        #region ======================================================= Helper Functions =======================================================
        private async Task<List<ExpenseDetailsMongoDB>> GetAllExpnesRelatedTablesForList(List<Expenses> model, Guid tenantId)
        {
            List<ExpenseDetailsMongoDB> expenseList = new List<ExpenseDetailsMongoDB>();
            var expenseIds = model.Select(m => m.Id).ToList();
            var projectIds = model.Select(m => m.ProjectId).ToList();
            var statusIds = model.Select(m => m.StatusId).ToList();
            var expenseCategoryIds = model.Select(m => m.ExpenseCategoryId).ToList();
            var paymentModeIds = model.Select(m => m.PaymentModeId).ToList();
            var createdByIds = model.Select(m => m.CreatedById).ToList();
            var reviewedByIds = model.Select(m => m.ReviewedById).ToList();
            var approvedByIds = model.Select(m => m.ApprovedById).ToList();
            var processedByIds = model.Select(m => m.ProcessedById).ToList();
            var paidByIds = model.Select(m => m.PaidById).ToList();

            var projectTask = Task.Run(async () =>
            {
                await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                return await dbContext.Projects.AsNoTracking().Where(p => projectIds.Contains(p.Id) && p.TenantId == tenantId).ToListAsync();
            });
            var paidByTask = Task.Run(async () =>
            {
                await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                return await dbContext.Employees.Include(e => e.JobRole).AsNoTracking().Where(e => paidByIds.Contains(e.Id) && e.TenantId == tenantId).ToListAsync();
            });
            var createdByTask = Task.Run(async () =>
            {
                await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                return await dbContext.Employees.Include(e => e.JobRole).AsNoTracking().Where(e => createdByIds.Contains(e.Id) && e.TenantId == tenantId).ToListAsync();
            });
            var reviewedByTask = Task.Run(async () =>
            {
                await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                return await dbContext.Employees.Include(e => e.JobRole).AsNoTracking().Where(e => reviewedByIds.Contains(e.Id) && e.TenantId == tenantId).ToListAsync();
            });
            var approvedByTask = Task.Run(async () =>
            {
                await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                return await dbContext.Employees.Include(e => e.JobRole).AsNoTracking().Where(e => approvedByIds.Contains(e.Id) && e.TenantId == tenantId).ToListAsync();
            });
            var processedByTask = Task.Run(async () =>
            {
                await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                return await dbContext.Employees.Include(e => e.JobRole).AsNoTracking().Where(e => processedByIds.Contains(e.Id) && e.TenantId == tenantId).ToListAsync();
            });
            var expenseCategoryTask = Task.Run(async () =>
            {
                await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                return await dbContext.ExpenseCategoryMasters.AsNoTracking().Where(et => expenseCategoryIds.Contains(et.Id) && et.TenantId == tenantId).ToListAsync();
            });
            var paymentModeTask = Task.Run(async () =>
            {
                await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                return await dbContext.PaymentModeMatser.AsNoTracking().Where(pm => paymentModeIds.Contains(pm.Id)).ToListAsync();
            });
            var statusMappingTask = Task.Run(async () =>
            {
                await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                return await dbContext.ExpensesStatusMapping
                .Include(s => s.Status)
                .Include(s => s.NextStatus)
                .AsNoTracking()
                .Where(es => statusIds.Contains(es.StatusId) && es.Status != null)
                .GroupBy(s => s.StatusId)
                .Select(g => new
                {
                    StatusId = g.Key,
                    Status = g.Select(s => s.Status).FirstOrDefault(),
                    NextStatus = g.Select(s => s.NextStatus).OrderBy(s => s!.Name).ToList()
                }).ToListAsync();
            });
            var statusTask = Task.Run(async () =>
            {
                await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                return await dbContext.ExpensesStatusMaster
                .AsNoTracking()
                .Where(es => statusIds.Contains(es.Id))
                .ToListAsync();
            });
            var billAttachmentsTask = Task.Run(async () =>
            {
                await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                return await dbContext.BillAttachments
                .Include(ba => ba.Document)
                .AsNoTracking()
                .Where(ba => expenseIds.Contains(ba.ExpensesId) && ba.Document != null)
                .GroupBy(ba => ba.ExpensesId)
                .Select(g => new
                {
                    ExpensesId = g.Key,
                    Documents = g.Select(ba => new DocumentMongoDB
                    {
                        DocumentId = ba.Document!.Id.ToString(),
                        FileName = ba.Document.FileName,
                        ContentType = ba.Document.ContentType,
                        S3Key = ba.Document.S3Key,
                        ThumbS3Key = ba.Document.ThumbS3Key ?? ba.Document.S3Key
                    }).ToList()
                })
                .ToListAsync();
            });

            // Await all prerequisite checks at once.
            await Task.WhenAll(projectTask, expenseCategoryTask, paymentModeTask, statusMappingTask, paidByTask, createdByTask, reviewedByTask, approvedByTask,
                processedByTask, statusTask, billAttachmentsTask);

            var projects = projectTask.Result;
            var expenseCategories = expenseCategoryTask.Result;
            var paymentModes = paymentModeTask.Result;
            var statusMappings = statusMappingTask.Result;
            var paidBys = paidByTask.Result;
            var createdBys = createdByTask.Result;
            var reviewedBys = reviewedByTask.Result;
            var approvedBys = approvedByTask.Result;
            var processedBy = processedByTask.Result;
            var billAttachments = billAttachmentsTask.Result;

            expenseList = model.Select(m =>
            {
                var response = _mapper.Map<ExpenseDetailsMongoDB>(m);

                response.Project = projects.Where(p => p.Id == m.ProjectId).Select(p => _mapper.Map<ProjectBasicMongoDB>(p)).FirstOrDefault() ?? new ProjectBasicMongoDB();
                response.PaidBy = paidBys.Where(p => p.Id == m.PaidById).Select(p => _mapper.Map<BasicEmployeeMongoDB>(p)).FirstOrDefault() ?? new BasicEmployeeMongoDB();
                response.CreatedBy = createdBys.Where(e => e.Id == m.CreatedById).Select(e => _mapper.Map<BasicEmployeeMongoDB>(e)).FirstOrDefault() ?? new BasicEmployeeMongoDB();
                response.ReviewedBy = reviewedBys.Where(e => e.Id == m.CreatedById).Select(e => _mapper.Map<BasicEmployeeMongoDB>(e)).FirstOrDefault();
                response.ApprovedBy = approvedBys.Where(e => e.Id == m.CreatedById).Select(e => _mapper.Map<BasicEmployeeMongoDB>(e)).FirstOrDefault();
                response.ProcessedBy = processedBy.Where(e => e.Id == m.CreatedById).Select(e => _mapper.Map<BasicEmployeeMongoDB>(e)).FirstOrDefault();
                response.Status = statusMappings.Where(s => s.StatusId == m.StatusId).Select(s => _mapper.Map<ExpensesStatusMasterMongoDB>(s.Status)).FirstOrDefault() ?? new ExpensesStatusMasterMongoDB();
                if (response.Status.Id == string.Empty)
                {
                    var status = statusTask.Result;
                    response.Status = status.Where(s => s.Id == m.StatusId).Select(s => _mapper.Map<ExpensesStatusMasterMongoDB>(s)).FirstOrDefault() ?? new ExpensesStatusMasterMongoDB();
                }

                response.NextStatus = statusMappings.Where(s => s.StatusId == m.StatusId).Select(s => _mapper.Map<List<ExpensesStatusMasterMongoDB>>(s.NextStatus)).FirstOrDefault() ?? new List<ExpensesStatusMasterMongoDB>();
                response.PaymentMode = paymentModes.Where(pm => pm.Id == m.PaymentModeId).Select(pm => _mapper.Map<PaymentModeMatserMongoDB>(pm)).FirstOrDefault() ?? new PaymentModeMatserMongoDB();
                response.ExpenseCategory = expenseCategories.Where(et => et.Id == m.ExpenseCategoryId).Select(et => _mapper.Map<ExpenseCategoryMasterMongoDB>(et)).FirstOrDefault() ?? new ExpenseCategoryMasterMongoDB();
                response.Documents = billAttachments.Where(ba => ba.ExpensesId == m.Id).Select(ba => ba.Documents).FirstOrDefault() ?? new List<DocumentMongoDB>();
                return response;
            }).ToList();

            return expenseList;
        }
        private async Task<ExpenseDetailsMongoDB> GetAllExpnesRelatedTablesForSingle(Expenses model, Guid tenantId)
        {
            var projectTask = Task.Run(async () =>
            {
                await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                return await dbContext.Projects.AsNoTracking().FirstOrDefaultAsync(p => p.Id == model.ProjectId && p.TenantId == tenantId);
            });
            var paidByTask = Task.Run(async () =>
            {
                await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                return await dbContext.Employees.Include(e => e.JobRole).AsNoTracking().FirstOrDefaultAsync(e => e.Id == model.PaidById && e.TenantId == tenantId);
            });
            var createdByTask = Task.Run(async () =>
            {
                await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                return await dbContext.Employees.Include(e => e.JobRole).AsNoTracking().FirstOrDefaultAsync(e => e.Id == model.CreatedById && e.TenantId == tenantId);
            });
            var reviewedByTask = Task.Run(async () =>
            {
                await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                return await dbContext.Employees.Include(e => e.JobRole).AsNoTracking().FirstOrDefaultAsync(e => e.Id == model.ReviewedById && e.TenantId == tenantId);
            });
            var approvedByTask = Task.Run(async () =>
            {
                await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                return await dbContext.Employees.Include(e => e.JobRole).AsNoTracking().FirstOrDefaultAsync(e => e.Id == model.ApprovedById && e.TenantId == tenantId);
            });
            var processedByTask = Task.Run(async () =>
            {
                await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                return await dbContext.Employees.Include(e => e.JobRole).AsNoTracking().FirstOrDefaultAsync(e => e.Id == model.ProcessedById && e.TenantId == tenantId);
            });
            var expenseCategoryTask = Task.Run(async () =>
            {
                await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                return await dbContext.ExpenseCategoryMasters.AsNoTracking().FirstOrDefaultAsync(et => et.Id == model.ExpenseCategoryId && et.TenantId == tenantId);
            });
            var paymentModeTask = Task.Run(async () =>
            {
                await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                return await dbContext.PaymentModeMatser.AsNoTracking().FirstOrDefaultAsync(pm => pm.Id == model.PaymentModeId);
            });
            var statusMappingTask = Task.Run(async () =>
            {
                await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                return await dbContext.ExpensesStatusMapping
                .Include(s => s.Status)
                .Include(s => s.NextStatus)
                .AsNoTracking()
                .Where(es => es.StatusId == model.StatusId && es.Status != null)
                .GroupBy(s => s.StatusId)
                .Select(g => new
                {
                    StatusId = g.Key,
                    Status = g.Select(s => s.Status).FirstOrDefault(),
                    NextStatus = g.Select(s => s.NextStatus).OrderBy(s => s!.Name).ToList()
                }).FirstOrDefaultAsync();
            });
            var statusTask = Task.Run(async () =>
            {
                await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                return await dbContext.ExpensesStatusMaster
                .AsNoTracking()
                .FirstOrDefaultAsync(es => es.Id == model.StatusId);
            });
            var billAttachmentsTask = Task.Run(async () =>
            {
                await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                return await dbContext.BillAttachments
                .Include(ba => ba.Document)
                .AsNoTracking()
                .Where(ba => ba.ExpensesId == model.Id && ba.Document != null)
                .GroupBy(ba => ba.ExpensesId)
                .Select(g => new
                {
                    ExpensesId = g.Key,
                    Documents = g.Select(ba => new DocumentMongoDB
                    {
                        DocumentId = ba.Document!.Id.ToString(),
                        FileName = ba.Document.FileName,
                        ContentType = ba.Document.ContentType,
                        S3Key = ba.Document.S3Key,
                        ThumbS3Key = ba.Document.ThumbS3Key ?? ba.Document.S3Key
                    }).ToList()
                })
                .FirstOrDefaultAsync();
            });

            // Await all prerequisite checks at once.
            await Task.WhenAll(projectTask, expenseCategoryTask, paymentModeTask, statusMappingTask, paidByTask, createdByTask, reviewedByTask, approvedByTask,
                processedByTask, statusTask, billAttachmentsTask);

            var project = projectTask.Result;
            var expenseCategory = expenseCategoryTask.Result;
            var paymentMode = paymentModeTask.Result;
            var statusMapping = statusMappingTask.Result;
            var paidBy = paidByTask.Result;
            var createdBy = createdByTask.Result;
            var reviewedBy = reviewedByTask.Result;
            var approvedBy = approvedByTask.Result;
            var processedBy = processedByTask.Result;
            var billAttachment = billAttachmentsTask.Result;


            var response = _mapper.Map<ExpenseDetailsMongoDB>(model);

            response.Project = _mapper.Map<ProjectBasicMongoDB>(project);
            response.PaidBy = _mapper.Map<BasicEmployeeMongoDB>(paidBy);
            response.CreatedBy = _mapper.Map<BasicEmployeeMongoDB>(createdBy);
            response.ReviewedBy = _mapper.Map<BasicEmployeeMongoDB>(reviewedBy);
            response.ApprovedBy = _mapper.Map<BasicEmployeeMongoDB>(approvedBy);
            response.ProcessedBy = _mapper.Map<BasicEmployeeMongoDB>(processedBy);
            if (statusMapping != null)
            {
                response.Status = _mapper.Map<ExpensesStatusMasterMongoDB>(statusMapping.Status);
                response.NextStatus = _mapper.Map<List<ExpensesStatusMasterMongoDB>>(statusMapping.NextStatus);
            }
            if (response.Status == null)
            {
                var status = statusTask.Result;
                response.Status = _mapper.Map<ExpensesStatusMasterMongoDB>(status);
            }
            response.PaymentMode = _mapper.Map<PaymentModeMatserMongoDB>(paymentMode);
            response.ExpenseCategory = _mapper.Map<ExpenseCategoryMasterMongoDB>(expenseCategory);
            if (billAttachment != null) response.Documents = billAttachment.Documents;

            return response;

        }

        #endregion
    }
}
