using Marco.Pms.DataAccess.Data;
using Marco.Pms.Model.Master;
using Marco.Pms.Model.MongoDBModels;
using Marco.Pms.Model.MongoDBModels.Masters;
using Marco.Pms.Model.MongoDBModels.Project;
using Marco.Pms.Model.OrganizationModel;
using Marco.Pms.Model.Projects;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using MongoDB.Bson;
using MongoDB.Driver;

namespace Marco.Pms.Helpers
{
    public class ProjectCache
    {
        private readonly IMongoCollection<ProjectMongoDB> _projectCollection;
        private readonly IMongoCollection<WorkItemMongoDB> _taskCollection;
        public ProjectCache(ApplicationDbContext context, 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
            _projectCollection = mongoDB.GetCollection<ProjectMongoDB>("ProjectDetails");
            _taskCollection = mongoDB.GetCollection<WorkItemMongoDB>("WorkItemDetails");
        }

        #region=================================================================== Project Cache Helper ===================================================================

        public async Task AddProjectDetailsToCache(ProjectMongoDB projectDetails)
        {
            await _projectCollection.InsertOneAsync(projectDetails);

            await InitializeCollectionAsync();

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

            // 2. Perform the insert operation. This is the only responsibility of this method.
            await _projectCollection.InsertManyAsync(projectDetailsList);
            await InitializeCollectionAsync();
        }
        private async Task InitializeCollectionAsync()
        {
            var indexKeys = Builders<ProjectMongoDB>.IndexKeys.Ascending(x => x.ExpireAt);
            var indexOptions = new CreateIndexOptions
            {
                ExpireAfter = TimeSpan.Zero // required for fixed expiration time
            };
            var indexModel = new CreateIndexModel<ProjectMongoDB>(indexKeys, indexOptions);
            await _projectCollection.Indexes.CreateOneAsync(indexModel);
        }
        public async Task<bool> UpdateProjectDetailsOnlyToCache(Project project, StatusMaster projectStatus, Organization promotor, Organization pmc)
        {
            // Build the update definition
            var updates = Builders<ProjectMongoDB>.Update.Combine(
                Builders<ProjectMongoDB>.Update.Set(r => r.Name, project.Name),
                Builders<ProjectMongoDB>.Update.Set(r => r.ProjectAddress, project.ProjectAddress),
                Builders<ProjectMongoDB>.Update.Set(r => r.ShortName, project.ShortName),
                Builders<ProjectMongoDB>.Update.Set(r => r.ProjectStatus, new StatusMasterMongoDB
                {
                    Id = projectStatus.Id.ToString(),
                    Status = projectStatus.Status
                }),
                Builders<ProjectMongoDB>.Update.Set(r => r.Promoter, new OrganizationMongoDB
                {
                    Id = promotor.Id.ToString(),
                    Name = promotor.Name,
                    ContactPerson = promotor.ContactPerson,
                    Email = promotor.Email,
                    Address = promotor.Address,
                    ContactNumber = promotor.ContactNumber,
                    SPRID = promotor.SPRID
                }),
                Builders<ProjectMongoDB>.Update.Set(r => r.PMC, new OrganizationMongoDB
                {
                    Id = pmc.Id.ToString(),
                    Name = pmc.Name,
                    ContactPerson = pmc.ContactPerson,
                    Email = pmc.Email,
                    Address = promotor.Address,
                    ContactNumber = promotor.ContactNumber,
                    SPRID = promotor.SPRID
                }),
                Builders<ProjectMongoDB>.Update.Set(r => r.StartDate, project.StartDate),
                Builders<ProjectMongoDB>.Update.Set(r => r.EndDate, project.EndDate),
                Builders<ProjectMongoDB>.Update.Set(r => r.ContactPerson, project.ContactPerson)
            );

            // Perform the update
            var result = await _projectCollection.UpdateOneAsync(
                filter: r => r.Id == project.Id.ToString(),
                update: updates
            );

            if (result.MatchedCount == 0)
            {
                return false;
            }

            return true;
        }
        public async Task<ProjectMongoDB?> GetProjectDetailsFromCache(Guid projectId)
        {

            // Build filter and projection to exclude large 'Buildings' list
            var filter = Builders<ProjectMongoDB>.Filter.Eq(p => p.Id, projectId.ToString());
            var projection = Builders<ProjectMongoDB>.Projection.Exclude(p => p.Buildings);

            // Perform query
            var project = await _projectCollection
                .Find(filter)
                .Project<ProjectMongoDB>(projection)
            .FirstOrDefaultAsync();

            return project;
        }
        public async Task<ProjectMongoDB?> GetProjectDetailsWithBuildingsFromCache(Guid projectId)
        {

            // Build filter and projection to exclude large 'Buildings' list
            var filter = Builders<ProjectMongoDB>.Filter.Eq(p => p.Id, projectId.ToString());

            // Perform query
            var project = await _projectCollection
                .Find(filter)
            .FirstOrDefaultAsync();

            return project;
        }
        public async Task<List<ProjectMongoDB>> GetProjectDetailsListFromCache(List<Guid> projectIds)
        {
            List<string> stringProjectIds = projectIds.Select(p => p.ToString()).ToList();
            var filter = Builders<ProjectMongoDB>.Filter.In(p => p.Id, stringProjectIds);
            var projection = Builders<ProjectMongoDB>.Projection.Exclude(p => p.Buildings);
            var projects = await _projectCollection
                .Find(filter)
                .Project<ProjectMongoDB>(projection)
            .ToListAsync();
            return projects;
        }
        public async Task<bool> DeleteProjectByIdFromCacheAsync(Guid projectId)
        {
            var filter = Builders<ProjectMongoDB>.Filter.Eq(e => e.Id, projectId.ToString());
            var result = await _projectCollection.DeleteOneAsync(filter);
            return result.DeletedCount > 0;
        }
        public async Task<bool> RemoveProjectsFromCacheAsync(List<Guid> projectIds)
        {
            var stringIds = projectIds.Select(id => id.ToString()).ToList();
            var filter = Builders<ProjectMongoDB>.Filter.In(p => p.Id, stringIds);
            var result = await _projectCollection.DeleteManyAsync(filter);
            return result.DeletedCount > 0;
        }

        #endregion

        #region=================================================================== Project infrastructure Cache Helper ===================================================================

        public async Task AddBuildngInfraToCache(Guid projectId, Building? building, Floor? floor, WorkArea? workArea, Guid? buildingId)
        {
            var stringProjectId = projectId.ToString();

            // Add Building
            if (building != null)
            {
                var buildingMongo = new BuildingMongoDB
                {
                    Id = building.Id.ToString(),
                    BuildingName = building.Name,
                    Description = building.Description,
                    PlannedWork = 0,
                    CompletedWork = 0,
                    Floors = new List<FloorMongoDB>()
                };

                var filter = Builders<ProjectMongoDB>.Filter.Eq(p => p.Id, stringProjectId);
                var update = Builders<ProjectMongoDB>.Update.Push("Buildings", buildingMongo);

                var result = await _projectCollection.UpdateOneAsync(filter, update);

                if (result.MatchedCount == 0)
                {
                    return;
                }
                return;
            }

            // Add Floor
            if (floor != null)
            {
                var floorMongo = new FloorMongoDB
                {
                    Id = floor.Id.ToString(),
                    FloorName = floor.FloorName,
                    PlannedWork = 0,
                    CompletedWork = 0,
                    WorkAreas = new List<WorkAreaMongoDB>()
                };

                var filter = Builders<ProjectMongoDB>.Filter.And(
                    Builders<ProjectMongoDB>.Filter.Eq(p => p.Id, stringProjectId),
                    Builders<ProjectMongoDB>.Filter.Eq("Buildings._id", floor.BuildingId.ToString())
                );

                var update = Builders<ProjectMongoDB>.Update.Push("Buildings.$.Floors", floorMongo);
                var result = await _projectCollection.UpdateOneAsync(filter, update);

                if (result.MatchedCount == 0)
                {
                    return;
                }
                return;
            }

            // Add WorkArea
            if (workArea != null && buildingId != null)
            {
                var workAreaMongo = new WorkAreaMongoDB
                {
                    Id = workArea.Id.ToString(),
                    AreaName = workArea.AreaName,
                    PlannedWork = 0,
                    CompletedWork = 0
                };

                var filter = Builders<ProjectMongoDB>.Filter.Eq(p => p.Id, stringProjectId);

                var arrayFilters = new List<ArrayFilterDefinition>
                {
                    new JsonArrayFilterDefinition<BsonDocument>("{ 'b._id': '" + buildingId + "' }"),
                    new JsonArrayFilterDefinition<BsonDocument>("{ 'f._id': '" + workArea.FloorId + "' }")
                };

                var update = Builders<ProjectMongoDB>.Update.Push("Buildings.$[b].Floors.$[f].WorkAreas", workAreaMongo);
                var updateOptions = new UpdateOptions { ArrayFilters = arrayFilters };

                var result = await _projectCollection.UpdateOneAsync(filter, update, updateOptions);

                if (result.MatchedCount == 0)
                {
                    return;
                }
                return;
            }
        }
        public async Task<bool> UpdateBuildngInfraToCache(Guid projectId, Building? building, Floor? floor, WorkArea? workArea, Guid? buildingId)
        {
            var stringProjectId = projectId.ToString();

            // Update Building
            if (building != null)
            {
                var filter = Builders<ProjectMongoDB>.Filter.And(
                    Builders<ProjectMongoDB>.Filter.Eq(p => p.Id, stringProjectId),
                    Builders<ProjectMongoDB>.Filter.Eq("Buildings._id", building.Id.ToString())
                );

                var update = Builders<ProjectMongoDB>.Update.Combine(
                    Builders<ProjectMongoDB>.Update.Set("Buildings.$.BuildingName", building.Name),
                    Builders<ProjectMongoDB>.Update.Set("Buildings.$.Description", building.Description)
                );

                var result = await _projectCollection.UpdateOneAsync(filter, update);

                if (result.MatchedCount == 0)
                {
                    return false;
                }

                return true;
            }

            // Update Floor
            if (floor != null)
            {
                var arrayFilters = new List<ArrayFilterDefinition>
                {
                    new JsonArrayFilterDefinition<BsonDocument>("{ 'b._id': '" + floor.BuildingId + "' }"),
                    new JsonArrayFilterDefinition<BsonDocument>("{ 'f._id': '" + floor.Id + "' }")
                };

                var update = Builders<ProjectMongoDB>.Update.Set("Buildings.$[b].Floors.$[f].FloorName", floor.FloorName);
                var updateOptions = new UpdateOptions { ArrayFilters = arrayFilters };
                var filter = Builders<ProjectMongoDB>.Filter.Eq(p => p.Id, stringProjectId);

                var result = await _projectCollection.UpdateOneAsync(filter, update, updateOptions);

                if (result.MatchedCount == 0)
                {
                    return false;
                }
                return true;
            }

            // Update WorkArea
            if (workArea != null && buildingId != null)
            {
                var arrayFilters = new List<ArrayFilterDefinition>
                {
                    new JsonArrayFilterDefinition<BsonDocument>("{ 'b._id': '" + buildingId + "' }"),
                    new JsonArrayFilterDefinition<BsonDocument>("{ 'f._id': '" + workArea.FloorId + "' }"),
                    new JsonArrayFilterDefinition<BsonDocument>("{ 'a._id': '" + workArea.Id + "' }")
                };

                var update = Builders<ProjectMongoDB>.Update.Set("Buildings.$[b].Floors.$[f].WorkAreas.$[a].AreaName", workArea.AreaName);
                var updateOptions = new UpdateOptions { ArrayFilters = arrayFilters };
                var filter = Builders<ProjectMongoDB>.Filter.Eq(p => p.Id, stringProjectId);

                var result = await _projectCollection.UpdateOneAsync(filter, update, updateOptions);

                if (result.MatchedCount == 0)
                {
                    return false;
                }
                return true;
            }
            return false;
        }
        public async Task<List<BuildingMongoDB>?> GetBuildingInfraFromCache(Guid projectId)
        {

            // Filter by project ID
            var filter = Builders<ProjectMongoDB>.Filter.Eq(p => p.Id, projectId.ToString());

            // Project only the "Buildings" field from the document
            var buildings = await _projectCollection
                .Find(filter)
                .Project(p => p.Buildings)
                .FirstOrDefaultAsync();

            return buildings;
        }
        public async Task UpdatePlannedAndCompleteWorksInBuildingFromCache(Guid workAreaId, double plannedWork, double completedWork)
        {
            var filter = Builders<ProjectMongoDB>.Filter.Eq("Buildings.Floors.WorkAreas._id", workAreaId.ToString());
            var project = await _projectCollection.Find(filter).FirstOrDefaultAsync();

            string? selectedBuildingId = null;
            string? selectedFloorId = null;
            string? selectedWorkAreaId = null;

            foreach (var building in project.Buildings)
            {
                foreach (var floor in building.Floors)
                {
                    foreach (var area in floor.WorkAreas)
                    {
                        if (area.Id == workAreaId.ToString())
                        {
                            selectedWorkAreaId = area.Id;
                            selectedFloorId = floor.Id;
                            selectedBuildingId = building.Id;
                        }
                    }
                }
            }

            var arrayFilters = new List<ArrayFilterDefinition>
            {
                    new JsonArrayFilterDefinition<BsonDocument>("{ 'b._id': '" + selectedBuildingId + "' }"),
                    new JsonArrayFilterDefinition<BsonDocument>("{ 'f._id': '" + selectedFloorId + "' }"),
                    new JsonArrayFilterDefinition<BsonDocument>("{ 'a._id': '" + selectedWorkAreaId + "' }")
            };
            var updateOptions = new UpdateOptions { ArrayFilters = arrayFilters };
            var update = Builders<ProjectMongoDB>.Update
                .Inc("Buildings.$[b].Floors.$[f].WorkAreas.$[a].PlannedWork", plannedWork)
                .Inc("Buildings.$[b].Floors.$[f].WorkAreas.$[a].CompletedWork", completedWork)
                .Inc("Buildings.$[b].Floors.$[f].PlannedWork", plannedWork)
                .Inc("Buildings.$[b].Floors.$[f].CompletedWork", completedWork)
                .Inc("Buildings.$[b].PlannedWork", plannedWork)
                .Inc("Buildings.$[b].CompletedWork", completedWork)
                .Inc("PlannedWork", plannedWork)
                .Inc("CompletedWork", completedWork);
            var result = await _projectCollection.UpdateOneAsync(filter, update, updateOptions);

        }
        public async Task<WorkAreaInfoMongoDB?> GetBuildingAndFloorByWorkAreaIdFromCache(Guid workAreaId)
        {
            var pipeline = new[]
            {
                new BsonDocument("$unwind", "$Buildings"),
                new BsonDocument("$unwind", "$Buildings.Floors"),
                new BsonDocument("$unwind", "$Buildings.Floors.WorkAreas"),
                new BsonDocument("$match", new BsonDocument("Buildings.Floors.WorkAreas._id", workAreaId.ToString())),
                new BsonDocument("$project", new BsonDocument
                {
                    { "_id", 0 },
                    { "ProjectId", "$_id" },
                    { "ProjectName", "$Name" },
                    { "PlannedWork", "$PlannedWork" },
                    { "CompletedWork", "$CompletedWork" },
                    {
                        "Building", new BsonDocument
                        {
                            { "_id", "$Buildings._id" },
                            { "BuildingName", "$Buildings.BuildingName" },
                            { "Description", "$Buildings.Description" },
                            { "PlannedWork", "$Buildings.PlannedWork" },
                            { "CompletedWork", "$Buildings.CompletedWork" }
                        }
                    },
                    {
                        "Floor", new BsonDocument
                        {
                            { "_id", "$Buildings.Floors._id" },
                            { "FloorName", "$Buildings.Floors.FloorName" },
                            { "PlannedWork", "$Buildings.Floors.PlannedWork" },
                            { "CompletedWork", "$Buildings.Floors.CompletedWork" }
                        }
                    },
                    { "WorkArea", "$Buildings.Floors.WorkAreas" }
                })
            };
            var result = await _projectCollection.Aggregate<WorkAreaInfoMongoDB>(pipeline).FirstOrDefaultAsync();
            if (result == null)
                return null;
            return result;
        }

        #endregion

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

        public async Task<List<WorkItemMongoDB>> GetWorkItemsByWorkAreaIdsFromCache(List<Guid> workAreaIds, List<Guid> serviceIds)
        {
            var stringWorkAreaIds = workAreaIds.Select(wa => wa.ToString()).ToList();

            var filterBuilder = Builders<WorkItemMongoDB>.Filter;
            var filter = filterBuilder.Empty;

            filter &= filterBuilder.In(w => w.WorkAreaId, stringWorkAreaIds);
            if (serviceIds.Any())
            {
                var stringServiceIds = serviceIds.Select(s => s.ToString()).ToList();
                filter &= filterBuilder.In(w => w.ActivityMaster!.ActivityGroupMaster!.Service!.Id, stringServiceIds);
            }

            var workItems = await _taskCollection // replace with your actual collection name
                .Find(filter)
                .ToListAsync();

            return workItems;
        }
        public async Task ManageWorkItemDetailsToCache(List<WorkItemMongoDB> workItems)
        {
            foreach (WorkItemMongoDB workItem in workItems)
            {
                var filter = Builders<WorkItemMongoDB>.Filter.Eq(p => p.Id, workItem.Id.ToString());
                var updates = Builders<WorkItemMongoDB>.Update.Combine(
                   Builders<WorkItemMongoDB>.Update.Set(r => r.WorkAreaId, workItem.WorkAreaId.ToString()),
                   Builders<WorkItemMongoDB>.Update.Set(r => r.ParentTaskId, (workItem.ParentTaskId != null ? workItem.ParentTaskId.ToString() : null)),
                   Builders<WorkItemMongoDB>.Update.Set(r => r.PlannedWork, workItem.PlannedWork),
                   Builders<WorkItemMongoDB>.Update.Set(r => r.TodaysAssigned, workItem.TodaysAssigned),
                   Builders<WorkItemMongoDB>.Update.Set(r => r.CompletedWork, workItem.CompletedWork),
                   Builders<WorkItemMongoDB>.Update.Set(r => r.Description, workItem.Description),
                   Builders<WorkItemMongoDB>.Update.Set(r => r.TaskDate, workItem.TaskDate),
                   Builders<WorkItemMongoDB>.Update.Set(r => r.ExpireAt, DateTime.UtcNow.Date.AddDays(1)),
                   Builders<WorkItemMongoDB>.Update.Set(r => r.ActivityMaster, workItem.ActivityMaster),
                   Builders<WorkItemMongoDB>.Update.Set(r => r.WorkCategoryMaster, workItem.WorkCategoryMaster)
               );
                var options = new UpdateOptions { IsUpsert = true };
                var result = await _taskCollection.UpdateOneAsync(filter, updates, options);
                if (result.UpsertedId != null)
                {
                    var indexKeys = Builders<WorkItemMongoDB>.IndexKeys.Ascending(x => x.ExpireAt);
                    var indexOptions = new CreateIndexOptions
                    {
                        ExpireAfter = TimeSpan.Zero // required for fixed expiration time
                    };
                    var indexModel = new CreateIndexModel<WorkItemMongoDB>(indexKeys, indexOptions);
                    await _taskCollection.Indexes.CreateOneAsync(indexModel);
                }
            }
        }
        public async Task<List<WorkItemMongoDB>> GetWorkItemDetailsByWorkAreaFromCache(Guid workAreaId, List<Guid> serviceIds)
        {
            var filterBuilder = Builders<WorkItemMongoDB>.Filter;
            var filter = filterBuilder.Empty;

            filter &= filterBuilder.Eq(p => p.WorkAreaId, workAreaId.ToString());
            if (serviceIds.Any())
            {
                var stringServiceIds = serviceIds.Select(s => s.ToString()).ToList();
                filter &= filterBuilder.In(w => w.ActivityMaster!.ActivityGroupMaster!.Service!.Id, stringServiceIds);
            }

            var options = new UpdateOptions { IsUpsert = true };
            var workItems = await _taskCollection
                .Find(filter)
                .ToListAsync();
            return workItems;
        }
        public async Task<WorkItemMongoDB> GetWorkItemDetailsByIdFromCache(Guid id)
        {
            var filter = Builders<WorkItemMongoDB>.Filter.Eq(p => p.Id, id.ToString());

            var options = new UpdateOptions { IsUpsert = true };
            var workItem = await _taskCollection
                .Find(filter)
                .FirstOrDefaultAsync();
            return workItem;
        }
        public async Task<bool> UpdatePlannedAndCompleteWorksInWorkItemToCache(Guid id, double plannedWork, double completedWork, double todaysAssigned)
        {
            var filter = Builders<WorkItemMongoDB>.Filter.Eq(p => p.Id, id.ToString());
            var updates = Builders<WorkItemMongoDB>.Update
                .Inc("PlannedWork", plannedWork)
                .Inc("CompletedWork", completedWork)
                .Inc("TodaysAssigned", todaysAssigned);

            var result = await _taskCollection.UpdateOneAsync(filter, updates);
            if (result.ModifiedCount > 0)
            {
                return true;
            }
            return false;
        }
        public async Task<bool> DeleteWorkItemByIdFromCacheAsync(Guid workItemId)
        {
            var filter = Builders<WorkItemMongoDB>.Filter.Eq(e => e.Id, workItemId.ToString());
            var result = await _taskCollection.DeleteOneAsync(filter);
            return result.DeletedCount > 0;
        }

        #endregion
    }
}
