using FirebaseAdmin.Messaging;
using Marco.Pms.DataAccess.Data;
using Marco.Pms.Model.Dtos.Attendance;
using Marco.Pms.Model.Entitlements;
using Marco.Pms.Model.Expenses;
using Marco.Pms.Model.Projects;
using Marco.Pms.Services.Service.ServiceInterfaces;
using MarcoBMS.Services.Service;
using Microsoft.CodeAnalysis;
using Microsoft.EntityFrameworkCore;
using Project = Marco.Pms.Model.Projects.Project;

namespace Marco.Pms.Services.Service
{
    public class FirebaseService : IFirebaseService
    {
        private readonly IDbContextFactory<ApplicationDbContext> _dbContextFactory;
        private readonly IServiceScopeFactory _serviceScopeFactory;

        private static readonly Guid Review = Guid.Parse("6537018f-f4e9-4cb3-a210-6c3b2da999d7");
        private static readonly Guid RejectedByReviewer = Guid.Parse("965eda62-7907-4963-b4a1-657fb0b2724b");
        private static readonly Guid Approve = Guid.Parse("4068007f-c92f-4f37-a907-bc15fe57d4d8");
        private static readonly Guid RejectedByApprover = Guid.Parse("d1ee5eec-24b6-4364-8673-a8f859c60729");
        private static readonly Guid ProcessPending = Guid.Parse("f18c5cfd-7815-4341-8da2-2c2d65778e27");
        private static readonly Guid Processed = Guid.Parse("61578360-3a49-4c34-8604-7b35a3787b95");

        public FirebaseService(IDbContextFactory<ApplicationDbContext> dbContextFactory,
            IServiceScopeFactory serviceScopeFactory)
        {
            _dbContextFactory = dbContextFactory ?? throw new ArgumentNullException(nameof(dbContextFactory));
            _serviceScopeFactory = serviceScopeFactory ?? throw new ArgumentNullException(nameof(serviceScopeFactory));
        }

        // Auth Controller
        public async Task SendLoginOnAnotherDeviceMessageAsync(Guid employeeId, string fcmToken, Guid tenantId)
        {
            await using var _context = await _dbContextFactory.CreateDbContextAsync();

            // List of device registration tokens to send the message to
            var registrationTokens = await _context.FCMTokenMappings
                .Where(ft => ft.EmployeeId == employeeId &&
                    ft.ExpiredAt >= DateTime.UtcNow &&
                    ft.FcmToken != fcmToken &&
                    ft.TenantId == tenantId)
                .Select(ft => ft.FcmToken).ToListAsync();

            var notificationFirebase = new Notification
            {
                Title = "Login Alert",
                Body = $"You has successfully logged in to the application from another device"
            };

            await SendMessageToMultipleDevicesAsync(registrationTokens, notificationFirebase);
        }
        public async Task SendLoginMessageAsync(string name, Guid tenantId)
        {
            await using var _context = await _dbContextFactory.CreateDbContextAsync();

            // List of device registration tokens to send the message to
            var registrationTokens = await _context.FCMTokenMappings
                .Where(ft => ft.ExpiredAt >= DateTime.UtcNow &&
                    ft.TenantId == tenantId)
                .Select(ft => ft.FcmToken).ToListAsync();

            var notificationFirebase = new Notification
            {
                Title = "Login Alert",
                Body = $"{name} has successfully logged in to the application"
            };

            await SendMessageToMultipleDevicesAsync(registrationTokens, notificationFirebase);
        }

        // Employee Controller
        public async Task SendEmployeeSuspendMessageAsync(Guid employeeId, Guid tenantId)
        {
            await using var _context = await _dbContextFactory.CreateDbContextAsync();

            var projectIds = await _context.ProjectAllocations
                .Where(pa => pa.EmployeeId == employeeId && pa.TenantId == tenantId)
                .Select(pa => pa.ProjectId).ToListAsync();

            var teamEmployeeIdsTask = Task.Run(async () =>
            {
                await using var dbContext = await _dbContextFactory.CreateDbContextAsync();

                return await dbContext.ProjectAllocations
                    .Where(pa => projectIds.Contains(pa.ProjectId) && pa.TenantId == tenantId)
                    .Select(pa => pa.EmployeeId).ToListAsync();
            });

            var manageProjectsRoleTask = Task.Run(async () =>
            {
                await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                return await dbContext.RolePermissionMappings
                .Where(rp => rp.FeaturePermissionId == PermissionsMaster.ManageProject)
                .Select(rp => rp.ApplicationRoleId).ToListAsync();
            });

            await Task.WhenAll(teamEmployeeIdsTask, manageProjectsRoleTask);

            var teamEmployeeIds = teamEmployeeIdsTask.Result;
            var manageProjectsRoleIds = manageProjectsRoleTask.Result;

            var employeeIds = await _context.EmployeeRoleMappings
                    .Where(er =>
                        teamEmployeeIds.Contains(er.EmployeeId) || manageProjectsRoleIds.Contains(er.RoleId))
                    .Select(er => er.EmployeeId)
                    .ToListAsync();

            var registrationTokensForData = await _context.FCMTokenMappings
                .Where(ft => employeeIds.Contains(ft.EmployeeId) && ft.ExpiredAt >= DateTime.UtcNow && ft.TenantId == tenantId)
                .Select(ft => ft.FcmToken).ToListAsync();

            string commaSeparatedProjectIds = string.Join(",", projectIds);

            var data = new Dictionary<string, string>()
                    {
                        { "Keyword", "Employee_Suspend" },
                        { "EmployeeId", employeeId.ToString() },
                        { "ProjectIds", commaSeparatedProjectIds }
                    };

            await SendMessageToMultipleDevicesOnlyDataAsync(registrationTokensForData, data);
        }

        // Attendance Controller
        public async Task SendAttendanceMessageAsync(Guid projectId, string name, ATTENDANCE_MARK_TYPE markType, Guid employeeId, Guid tenantId)
        {
            await using var _context = await _dbContextFactory.CreateDbContextAsync();
            var projectTask = Task.Run(async () =>
            {
                await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                return await dbContext.ProjectAllocations
                .Include(pa => pa.Project)
                .Where(pa => pa.ProjectId == projectId && pa.IsActive && pa.Project != null)
                .GroupBy(pa => pa.ProjectId)
                .Select(g => new
                {
                    ProjectName = g.Select(pa => pa.Project!.Name).FirstOrDefault(),
                    EmployeeIds = g.Select(pa => pa.EmployeeId).Distinct().ToList()
                }).FirstOrDefaultAsync();
            });

            var teamAttendanceRoleTask = Task.Run(async () =>
            {
                await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                return await dbContext.RolePermissionMappings
                .Where(rp => rp.FeaturePermissionId == PermissionsMaster.TeamAttendance)
                .Select(rp => rp.ApplicationRoleId).ToListAsync();
            });
            var regularizeAttendanceRoleTask = Task.Run(async () =>
            {
                await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                return await dbContext.RolePermissionMappings
                .Where(rp => rp.FeaturePermissionId == PermissionsMaster.RegularizeAttendance)
                .Select(rp => rp.ApplicationRoleId).ToListAsync();
            });
            var manageProjectsRoleTask = Task.Run(async () =>
            {
                await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                return await dbContext.RolePermissionMappings
                .Where(rp => rp.FeaturePermissionId == PermissionsMaster.ManageProject)
                .Select(rp => rp.ApplicationRoleId).ToListAsync();
            });

            await Task.WhenAll(projectTask, teamAttendanceRoleTask, manageProjectsRoleTask, regularizeAttendanceRoleTask);

            var teamAttendanceRoleIds = teamAttendanceRoleTask.Result;
            var regularizeAttendanceRoleIds = regularizeAttendanceRoleTask.Result;
            var manageProjectsRoleIds = manageProjectsRoleTask.Result;
            var project = projectTask.Result;

            List<Guid> projectAssignedEmployeeIds = project?.EmployeeIds ?? new List<Guid>();

            var employeeIdsTask = Task.Run(async () =>
            {
                await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                return await dbContext.EmployeeRoleMappings
                .Where(er => (projectAssignedEmployeeIds.Contains(er.EmployeeId) || manageProjectsRoleIds.Contains(er.RoleId)) && teamAttendanceRoleIds.Contains(er.RoleId))
                .Select(er => er.EmployeeId)
                .ToListAsync();
            });
            var teamEmployeeIdsTask = Task.Run(async () =>
            {
                await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                return await dbContext.EmployeeRoleMappings
                .Where(er => projectAssignedEmployeeIds.Contains(er.EmployeeId) || manageProjectsRoleIds.Contains(er.RoleId))
                .Select(er => er.EmployeeId)
                .ToListAsync();
            });

            await Task.WhenAll(employeeIdsTask, teamEmployeeIdsTask);

            var employeeIds = employeeIdsTask.Result;
            var teamEmployeeIds = teamEmployeeIdsTask.Result;

            var mesaageNotificationIds = new List<Guid>();

            Notification notificationFirebase;
            switch (markType)
            {
                case ATTENDANCE_MARK_TYPE.CHECK_IN:
                    notificationFirebase = new Notification
                    {
                        Title = "Attendance Update",
                        Body = $" {name} has checked in for project {project?.ProjectName ?? ""}."
                    };
                    mesaageNotificationIds.AddRange(employeeIds);
                    break;
                case ATTENDANCE_MARK_TYPE.CHECK_OUT:
                    notificationFirebase = new Notification
                    {
                        Title = "Attendance Update",
                        Body = $" {name} has checked out for project {project?.ProjectName ?? ""}."
                    };
                    mesaageNotificationIds.AddRange(employeeIds);
                    break;
                case ATTENDANCE_MARK_TYPE.REQUEST_REGULARIZE:
                    notificationFirebase = new Notification
                    {
                        Title = "Regularization Request",
                        Body = $" {name} has submitted a regularization request for project {project?.ProjectName ?? ""}."
                    };
                    mesaageNotificationIds = await _context.EmployeeRoleMappings
                        .Where(er => (projectAssignedEmployeeIds.Contains(er.EmployeeId) || manageProjectsRoleIds.Contains(er.RoleId)) && regularizeAttendanceRoleIds.Contains(er.RoleId))
                        .Select(er => er.EmployeeId)
                        .ToListAsync();
                    break;
                case ATTENDANCE_MARK_TYPE.REGULARIZE:
                    notificationFirebase = new Notification
                    {
                        Title = " Regularization Approved",
                        Body = $" {name}'s regularization request for project {project?.ProjectName ?? ""} has been accepted."
                    };
                    mesaageNotificationIds.Add(employeeId);
                    break;
                case ATTENDANCE_MARK_TYPE.REGULARIZE_REJECT:
                    notificationFirebase = new Notification
                    {
                        Title = "Regularization Denied",
                        Body = $" {name}'s regularization request for project {project?.ProjectName ?? ""} has been rejected."
                    };
                    mesaageNotificationIds.Add(employeeId);
                    break;
                default:
                    notificationFirebase = new Notification
                    {
                        Title = "Attendance Update",
                        Body = $" {name} has update his/her attendance for project {project?.ProjectName ?? ""}."
                    };
                    break;
            }

            // List of device registration tokens to send the message to

            var data = new Dictionary<string, string>()
                    {
                        { "Keyword", "Attendance" },
                        { "ProjectId", projectId.ToString() },
                        { "Action", markType.ToString() }
                    };

            var registrationTokensForNotificationTask = Task.Run(async () =>
            {
                if (mesaageNotificationIds.Any())
                {
                    await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                    var registrationTokensForNotification = await dbContext.FCMTokenMappings
                    .Where(ft => mesaageNotificationIds.Contains(ft.EmployeeId) && ft.ExpiredAt >= DateTime.UtcNow && ft.TenantId == tenantId)
                    .Select(ft => ft.FcmToken).ToListAsync();

                    await SendMessageToMultipleDevicesWithDataAsync(registrationTokensForNotification, notificationFirebase, data);
                }
            });
            var registrationTokensForDataTask = Task.Run(async () =>
            {
                if (teamEmployeeIds.Any())
                {
                    await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                    var registrationTokensForData = await dbContext.FCMTokenMappings
                    .Where(ft => teamEmployeeIds.Contains(ft.EmployeeId) && ft.ExpiredAt >= DateTime.UtcNow && ft.TenantId == tenantId)
                    .Select(ft => ft.FcmToken).ToListAsync();

                    await SendMessageToMultipleDevicesOnlyDataAsync(registrationTokensForData, data);
                }
            });

            await Task.WhenAll(registrationTokensForNotificationTask, registrationTokensForDataTask);
        }

        // Task Controller
        public async Task SendAssignTaskMessageAsync(Guid workItemId, string name, List<Guid> teamMembers, Guid tenantId)
        {
            using var scope = _serviceScopeFactory.CreateScope();
            var _logger = scope.ServiceProvider.GetRequiredService<ILoggingService>();

            try
            {
                await using var _context = await _dbContextFactory.CreateDbContextAsync();

                var workItem = await _context.WorkItems
                    .Include(wi => wi.WorkArea)
                        .ThenInclude(wa => wa!.Floor)
                            .ThenInclude(f => f!.Building)
                    .FirstOrDefaultAsync(wi => wi.Id == workItemId && wi.WorkArea != null && wi.WorkArea.Floor != null && wi.WorkArea.Floor.Building != null);

                if (workItem == null)
                {
                    return;
                }

                var projectId = workItem.WorkArea!.Floor!.Building!.ProjectId;

                var projectTask = Task.Run(async () =>
                  {
                      await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                      return await dbContext.ProjectAllocations
                      .Include(pa => pa.Project)
                      .Where(pa => pa.ProjectId == projectId && pa.IsActive && pa.Project != null)
                      .GroupBy(pa => pa.ProjectId)
                      .Select(g => new
                      {
                          ProjectName = g.Select(pa => pa.Project!.Name).FirstOrDefault(),
                          EmployeeIds = g.Select(pa => pa.EmployeeId).Distinct().ToList()
                      }).FirstOrDefaultAsync();
                  });

                var viewTaskRoleTask = Task.Run(async () =>
                {
                    await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                    return await dbContext.RolePermissionMappings
                    .Where(rp => rp.FeaturePermissionId == PermissionsMaster.ViewTask)
                    .Select(rp => rp.ApplicationRoleId).ToListAsync();
                });

                var manageProjectsRoleTask = Task.Run(async () =>
                {
                    await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                    return await dbContext.RolePermissionMappings
                    .Where(rp => rp.FeaturePermissionId == PermissionsMaster.ManageProject)
                    .Select(rp => rp.ApplicationRoleId).ToListAsync();
                });

                await Task.WhenAll(projectTask, viewTaskRoleTask, manageProjectsRoleTask);

                var viewTaskRoleIds = viewTaskRoleTask.Result;
                var manageProjectsRoleIds = manageProjectsRoleTask.Result;
                var project = projectTask.Result;

                var buildingName = workItem.WorkArea.Floor.Building.Name;
                var FloorName = workItem.WorkArea.Floor.FloorName;
                var AreaName = workItem.WorkArea.AreaName;

                var location = $"{buildingName} > {FloorName} > {AreaName}";

                List<Guid> projectAssignedEmployeeIds = project?.EmployeeIds ?? new List<Guid>();

                var employeeIds = await _context.EmployeeRoleMappings
                    .Where(er => projectAssignedEmployeeIds.Contains(er.EmployeeId) || manageProjectsRoleIds.Contains(er.RoleId))
                    .Select(er => er.EmployeeId)
                    .ToListAsync();

                var data = new Dictionary<string, string>()
                    {
                        { "Keyword", "Assign_Task" },
                        { "ProjectId", projectId.ToString() }
                    };


                // List of device registration tokens to send the message to

                var registrationTokensForNotificationTask = Task.Run(async () =>
                {
                    await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                    var registrationTokensForNotification = await dbContext.FCMTokenMappings
                    .Where(ft => teamMembers.Contains(ft.EmployeeId) && ft.ExpiredAt >= DateTime.UtcNow && ft.TenantId == tenantId)
                    .Select(ft => ft.FcmToken).ToListAsync();
                    var notificationFirebase = new Notification
                    {
                        Title = $"Task Assigned for {project?.ProjectName}",
                        Body = $"A task has been assigned to you by {name} at {location}"
                    };

                    await SendMessageToMultipleDevicesWithDataAsync(registrationTokensForNotification, notificationFirebase, data);
                });
                var registrationTokensForDataTask = Task.Run(async () =>
                {
                    await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                    var registrationTokensForData = await dbContext.FCMTokenMappings
                    .Where(ft => employeeIds.Contains(ft.EmployeeId) && ft.ExpiredAt >= DateTime.UtcNow && ft.TenantId == tenantId)
                    .Select(ft => ft.FcmToken).ToListAsync();

                    await SendMessageToMultipleDevicesOnlyDataAsync(registrationTokensForData, data);
                });

                await Task.WhenAll(registrationTokensForNotificationTask, registrationTokensForDataTask);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Exception occured while get data for sending notification");
            }
        }
        public async Task SendReportTaskMessageAsync(Guid taskAllocationId, string name, Guid tenantId)
        {
            using var scope = _serviceScopeFactory.CreateScope();
            var _logger = scope.ServiceProvider.GetRequiredService<ILoggingService>();

            try
            {
                await using var _context = await _dbContextFactory.CreateDbContextAsync();

                var taskAllocation = await _context.TaskAllocations
                    .Include(t => t.WorkItem)
                        .ThenInclude(wi => wi!.WorkArea)
                            .ThenInclude(wa => wa!.Floor)
                                .ThenInclude(f => f!.Building)
                    .FirstOrDefaultAsync(t => t.Id == taskAllocationId &&
                        t.TenantId == tenantId &&
                        t.WorkItem != null &&
                        t.WorkItem.WorkArea != null &&
                        t.WorkItem.WorkArea.Floor != null &&
                        t.WorkItem.WorkArea.Floor.Building != null);

                if (taskAllocation == null)
                {
                    return;
                }

                var projectId = taskAllocation.WorkItem!.WorkArea!.Floor!.Building!.ProjectId;

                var projectTask = Task.Run(async () =>
                {
                    await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                    return await dbContext.ProjectAllocations
                    .Include(pa => pa.Project)
                    .Where(pa => pa.ProjectId == projectId && pa.IsActive && pa.Project != null)
                    .GroupBy(pa => pa.ProjectId)
                    .Select(g => new
                    {
                        ProjectName = g.Select(pa => pa.Project!.Name).FirstOrDefault(),
                        EmployeeIds = g.Select(pa => pa.EmployeeId).Distinct().ToList()
                    }).FirstOrDefaultAsync();
                });

                var viewTaskRoleTask = Task.Run(async () =>
                {
                    await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                    return await dbContext.RolePermissionMappings
                    .Where(rp => rp.FeaturePermissionId == PermissionsMaster.ViewTask)
                    .Select(rp => rp.ApplicationRoleId).ToListAsync();
                });
                var teamMemberTask = Task.Run(async () =>
                {
                    await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                    return await dbContext.TaskMembers
                    .Where(tm => tm.TaskAllocationId == taskAllocationId && tm.TenantId == tenantId)
                    .Select(tm => tm.EmployeeId).ToListAsync();
                });
                var approveTaskRoleTask = Task.Run(async () =>
                {
                    await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                    return await dbContext.RolePermissionMappings
                    .Where(rp => rp.FeaturePermissionId == PermissionsMaster.ApproveTask)
                    .Select(rp => rp.ApplicationRoleId).ToListAsync();
                });

                var manageProjectsRoleTask = Task.Run(async () =>
                {
                    await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                    return await dbContext.RolePermissionMappings
                    .Where(rp => rp.FeaturePermissionId == PermissionsMaster.ManageProject)
                    .Select(rp => rp.ApplicationRoleId).ToListAsync();
                });

                await Task.WhenAll(projectTask, teamMemberTask, viewTaskRoleTask, manageProjectsRoleTask, approveTaskRoleTask);

                var viewTaskRoleIds = viewTaskRoleTask.Result;
                var teamMembers = teamMemberTask.Result;
                var manageProjectsRoleIds = manageProjectsRoleTask.Result;
                var approveTaskRoleIds = approveTaskRoleTask.Result;
                var project = projectTask.Result;

                var buildingName = taskAllocation.WorkItem.WorkArea.Floor.Building.Name;
                var FloorName = taskAllocation.WorkItem.WorkArea.Floor.FloorName;
                var AreaName = taskAllocation.WorkItem.WorkArea.AreaName;

                var location = $"{buildingName} > {FloorName} > {AreaName}";

                List<Guid> projectAssignedEmployeeIds = project?.EmployeeIds ?? new List<Guid>();

                var dataNotificationTask = Task.Run(async () =>
                {
                    await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                    var employeeIds = await dbContext.EmployeeRoleMappings
                    .Where(er => projectAssignedEmployeeIds.Contains(er.EmployeeId) || manageProjectsRoleIds.Contains(er.RoleId))
                    .Select(er => er.EmployeeId)
                    .ToListAsync();

                    employeeIds.AddRange(teamMembers);

                    return employeeIds;
                });
                var mesaageNotificationTask = Task.Run(async () =>
                {
                    await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                    return await dbContext.EmployeeRoleMappings
                    .Where(er => (projectAssignedEmployeeIds.Contains(er.EmployeeId) || manageProjectsRoleIds.Contains(er.RoleId)) && approveTaskRoleIds.Contains(er.RoleId))
                    .Select(er => er.EmployeeId)
                    .ToListAsync();
                });

                await Task.WhenAll(dataNotificationTask, mesaageNotificationTask);

                var dataNotificationIds = dataNotificationTask.Result;
                var mesaageNotificationIds = mesaageNotificationTask.Result;

                var data = new Dictionary<string, string>()
                    {
                        { "Keyword", "Report_Task" },
                        { "ProjectId", projectId.ToString() },
                        { "TaskAllocationId", taskAllocationId.ToString() }
                    };


                // List of device registration tokens to send the message to

                var registrationTokensForNotificationTask = Task.Run(async () =>
                {
                    await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                    var registrationTokensForNotification = await dbContext.FCMTokenMappings
                    .Where(ft => mesaageNotificationIds.Contains(ft.EmployeeId) && ft.ExpiredAt >= DateTime.UtcNow && ft.TenantId == tenantId)
                    .Select(ft => ft.FcmToken).ToListAsync();
                    var notificationFirebase = new Notification
                    {
                        Title = $"New Task Reported - {project?.ProjectName}",
                        Body = $"{name} reported a task at {location}."
                    };

                    await SendMessageToMultipleDevicesWithDataAsync(registrationTokensForNotification, notificationFirebase, data);
                });
                var registrationTokensForDataTask = Task.Run(async () =>
                {
                    await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                    var registrationTokensForData = await dbContext.FCMTokenMappings
                    .Where(ft => dataNotificationIds.Contains(ft.EmployeeId) && ft.ExpiredAt >= DateTime.UtcNow && ft.TenantId == tenantId)
                    .Select(ft => ft.FcmToken).ToListAsync();

                    await SendMessageToMultipleDevicesOnlyDataAsync(registrationTokensForData, data);
                });

                await Task.WhenAll(registrationTokensForNotificationTask, registrationTokensForDataTask);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Exception occured while get data for sending notification");
            }
        }
        public async Task SendTaskCommentMessageAsync(Guid taskAllocationId, string name, Guid tenantId)
        {
            using var scope = _serviceScopeFactory.CreateScope();
            var _logger = scope.ServiceProvider.GetRequiredService<ILoggingService>();

            try
            {
                await using var _context = await _dbContextFactory.CreateDbContextAsync();

                var taskAllocation = await _context.TaskAllocations
                    .Include(t => t.WorkItem)
                        .ThenInclude(wi => wi!.WorkArea)
                            .ThenInclude(wa => wa!.Floor)
                                .ThenInclude(f => f!.Building)
                    .FirstOrDefaultAsync(t => t.Id == taskAllocationId &&
                        t.TenantId == tenantId &&
                        t.WorkItem != null &&
                        t.WorkItem.WorkArea != null &&
                        t.WorkItem.WorkArea.Floor != null &&
                        t.WorkItem.WorkArea.Floor.Building != null);

                if (taskAllocation == null)
                {
                    return;
                }

                var projectId = taskAllocation.WorkItem!.WorkArea!.Floor!.Building!.ProjectId;

                var projectTask = Task.Run(async () =>
                {
                    await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                    return await dbContext.ProjectAllocations
                    .Include(pa => pa.Project)
                    .Where(pa => pa.ProjectId == projectId && pa.IsActive && pa.Project != null)
                    .GroupBy(pa => pa.ProjectId)
                    .Select(g => new
                    {
                        ProjectName = g.Select(pa => pa.Project!.Name).FirstOrDefault(),
                        EmployeeIds = g.Select(pa => pa.EmployeeId).Distinct().ToList()
                    }).FirstOrDefaultAsync();
                });
                var viewTaskRoleTask = Task.Run(async () =>
                {
                    await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                    return await dbContext.RolePermissionMappings
                    .Where(rp => rp.FeaturePermissionId == PermissionsMaster.ViewTask)
                    .Select(rp => rp.ApplicationRoleId).ToListAsync();
                });

                var manageProjectsRoleTask = Task.Run(async () =>
                {
                    await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                    return await dbContext.RolePermissionMappings
                    .Where(rp => rp.FeaturePermissionId == PermissionsMaster.ManageProject)
                    .Select(rp => rp.ApplicationRoleId).ToListAsync();
                });

                await Task.WhenAll(projectTask, viewTaskRoleTask, manageProjectsRoleTask);

                var viewTaskRoleIds = viewTaskRoleTask.Result;
                var manageProjectsRoleIds = manageProjectsRoleTask.Result;
                var project = projectTask.Result;

                var buildingName = taskAllocation.WorkItem.WorkArea.Floor.Building.Name;
                var FloorName = taskAllocation.WorkItem.WorkArea.Floor.FloorName;
                var AreaName = taskAllocation.WorkItem.WorkArea.AreaName;

                var location = $"{buildingName} > {FloorName} > {AreaName}";

                List<Guid> projectAssignedEmployeeIds = project?.EmployeeIds ?? new List<Guid>();

                var employeeIds = await _context.EmployeeRoleMappings
                    .Where(er => (projectAssignedEmployeeIds.Contains(er.EmployeeId) || manageProjectsRoleIds.Contains(er.RoleId)) && viewTaskRoleIds.Contains(er.RoleId))
                    .Select(er => er.EmployeeId)
                    .ToListAsync();

                employeeIds.Add(taskAllocation.AssignedBy);

                var data = new Dictionary<string, string>()
                    {
                        { "Keyword", "Task_Comment" },
                        { "ProjectId", projectId.ToString() },
                        { "TaskAllocationId", taskAllocationId.ToString() }
                    };


                // List of device registration tokens to send the message to
                var registrationTokensForNotification = await _context.FCMTokenMappings
                    .Where(ft => employeeIds.Contains(ft.EmployeeId) && ft.ExpiredAt >= DateTime.UtcNow && ft.TenantId == tenantId)
                    .Select(ft => ft.FcmToken).ToListAsync();
                var notificationFirebase = new Notification
                {
                    Title = $"New Comment on Task - {project?.ProjectName}",
                    Body = $"{name} added a comment on Task at {location}."
                };

                await SendMessageToMultipleDevicesWithDataAsync(registrationTokensForNotification, notificationFirebase, data);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Exception occured while get data for sending notification");
            }
        }
        public async Task SendApproveTaskMessageAsync(Guid taskAllocationId, string name, Guid tenantId)
        {
            using var scope = _serviceScopeFactory.CreateScope();
            var _logger = scope.ServiceProvider.GetRequiredService<ILoggingService>();

            try
            {
                await using var _context = await _dbContextFactory.CreateDbContextAsync();

                var taskAllocation = await _context.TaskAllocations
                    .Include(t => t.WorkItem)
                        .ThenInclude(wi => wi!.WorkArea)
                            .ThenInclude(wa => wa!.Floor)
                                .ThenInclude(f => f!.Building)
                    .FirstOrDefaultAsync(t => t.Id == taskAllocationId &&
                        t.TenantId == tenantId &&
                        t.WorkItem != null &&
                        t.WorkItem.WorkArea != null &&
                        t.WorkItem.WorkArea.Floor != null &&
                        t.WorkItem.WorkArea.Floor.Building != null);

                if (taskAllocation == null)
                {
                    return;
                }

                var projectId = taskAllocation.WorkItem!.WorkArea!.Floor!.Building!.ProjectId;

                var projectTask = Task.Run(async () =>
                {
                    await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                    return await dbContext.ProjectAllocations
                    .Include(pa => pa.Project)
                    .Where(pa => pa.ProjectId == projectId && pa.IsActive && pa.Project != null)
                    .GroupBy(pa => pa.ProjectId)
                    .Select(g => new
                    {
                        ProjectName = g.Select(pa => pa.Project!.Name).FirstOrDefault(),
                        EmployeeIds = g.Select(pa => pa.EmployeeId).Distinct().ToList()
                    }).FirstOrDefaultAsync();
                });

                var viewTaskRoleTask = Task.Run(async () =>
                {
                    await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                    return await dbContext.RolePermissionMappings
                    .Where(rp => rp.FeaturePermissionId == PermissionsMaster.ViewTask)
                    .Select(rp => rp.ApplicationRoleId).ToListAsync();
                });
                var teamMemberTask = Task.Run(async () =>
                {
                    await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                    return await dbContext.TaskMembers
                    .Where(tm => tm.TaskAllocationId == taskAllocationId && tm.TenantId == tenantId)
                    .Select(tm => tm.EmployeeId).ToListAsync();
                });

                var manageProjectsRoleTask = Task.Run(async () =>
                {
                    await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                    return await dbContext.RolePermissionMappings
                    .Where(rp => rp.FeaturePermissionId == PermissionsMaster.ManageProject)
                    .Select(rp => rp.ApplicationRoleId).ToListAsync();
                });

                await Task.WhenAll(projectTask, teamMemberTask, viewTaskRoleTask, manageProjectsRoleTask);

                var viewTaskRoleIds = viewTaskRoleTask.Result;
                var teamMembers = teamMemberTask.Result;
                var manageProjectsRoleIds = manageProjectsRoleTask.Result;
                var project = projectTask.Result;

                var buildingName = taskAllocation.WorkItem.WorkArea.Floor.Building.Name;
                var FloorName = taskAllocation.WorkItem.WorkArea.Floor.FloorName;
                var AreaName = taskAllocation.WorkItem.WorkArea.AreaName;

                var location = $"{buildingName} > {FloorName} > {AreaName}";

                List<Guid> projectAssignedEmployeeIds = project?.EmployeeIds ?? new List<Guid>();

                var employeeIds = await _context.EmployeeRoleMappings
                .Where(er => projectAssignedEmployeeIds.Contains(er.EmployeeId) || manageProjectsRoleIds.Contains(er.RoleId))
                .Select(er => er.EmployeeId)
                .ToListAsync();

                teamMembers.Add(taskAllocation.AssignedBy);

                var data = new Dictionary<string, string>()
                    {
                        { "Keyword", "Report_Task" },
                        { "ProjectId", projectId.ToString() },
                        { "TaskAllocationId", taskAllocationId.ToString() }
                    };


                // List of device registration tokens to send the message to

                var registrationTokensForNotificationTask = Task.Run(async () =>
                {
                    await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                    var registrationTokensForNotification = await dbContext.FCMTokenMappings
                    .Where(ft => teamMembers.Contains(ft.EmployeeId) && ft.ExpiredAt >= DateTime.UtcNow && ft.TenantId == tenantId)
                    .Select(ft => ft.FcmToken).ToListAsync();
                    var notificationFirebase = new Notification
                    {
                        Title = $"Task Approved - {project?.ProjectName}",
                        Body = $"{name} approved your {taskAllocation.ReportedTask} of {taskAllocation.CompletedTask} tasks at {location}."
                    };

                    await SendMessageToMultipleDevicesWithDataAsync(registrationTokensForNotification, notificationFirebase, data);
                });
                var registrationTokensForDataTask = Task.Run(async () =>
                {
                    await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                    var registrationTokensForData = await dbContext.FCMTokenMappings
                    .Where(ft => employeeIds.Contains(ft.EmployeeId) && ft.ExpiredAt >= DateTime.UtcNow && ft.TenantId == tenantId)
                    .Select(ft => ft.FcmToken).ToListAsync();

                    await SendMessageToMultipleDevicesOnlyDataAsync(registrationTokensForData, data);
                });

                await Task.WhenAll(registrationTokensForNotificationTask, registrationTokensForDataTask);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Exception occured while get data for sending notification");
            }
        }

        // Project Controller
        //Project Infra
        public async Task SendModifyTaskMeaasgeAsync(List<Guid> workItemIds, string name, bool IsExist, Guid tenantId)
        {

            using var scope = _serviceScopeFactory.CreateScope();
            var _logger = scope.ServiceProvider.GetRequiredService<ILoggingService>();

            try
            {
                await using var _context = await _dbContextFactory.CreateDbContextAsync();

                var workItems = await _context.WorkItems
                    .Include(wi => wi.ActivityMaster)
                    .Include(wi => wi.WorkArea)
                        .ThenInclude(wa => wa!.Floor)
                            .ThenInclude(f => f!.Building)
                    .Where(wi => workItemIds.Contains(wi.Id) &&
                        wi.TenantId == tenantId &&
                        wi.ActivityMaster != null &&
                        wi.WorkArea != null &&
                        wi.WorkArea.Floor != null &&
                        wi.WorkArea.Floor.Building != null)
                    .ToListAsync();

                if (!workItems.Any())
                {
                    return;
                }

                var workItem = workItems.FirstOrDefault();

                var projectId = workItem!.WorkArea!.Floor!.Building!.ProjectId;

                var projectTask = Task.Run(async () =>
                {
                    await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                    return await dbContext.ProjectAllocations
                    .Include(pa => pa.Project)
                    .Where(pa => pa.ProjectId == projectId && pa.IsActive && pa.Project != null)
                    .GroupBy(pa => pa.ProjectId)
                    .Select(g => new
                    {
                        ProjectName = g.Select(pa => pa.Project!.Name).FirstOrDefault(),
                        EmployeeIds = g.Select(pa => pa.EmployeeId).Distinct().ToList()
                    }).FirstOrDefaultAsync();
                });

                var viewTaskRoleTask = Task.Run(async () =>
                {
                    await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                    return await dbContext.RolePermissionMappings
                    .Where(rp => rp.FeaturePermissionId == PermissionsMaster.ViewTask)
                    .Select(rp => rp.ApplicationRoleId).ToListAsync();
                });
                var viewProjectInfraRoleTask = Task.Run(async () =>
                {
                    await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                    return await dbContext.RolePermissionMappings
                    .Where(rp => rp.FeaturePermissionId == PermissionsMaster.ViewProjectInfra)
                    .Select(rp => rp.ApplicationRoleId).ToListAsync();
                });

                var manageProjectsRoleTask = Task.Run(async () =>
                {
                    await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                    return await dbContext.RolePermissionMappings
                    .Where(rp => rp.FeaturePermissionId == PermissionsMaster.ManageProject)
                    .Select(rp => rp.ApplicationRoleId).ToListAsync();
                });

                await Task.WhenAll(projectTask, viewTaskRoleTask, manageProjectsRoleTask, viewProjectInfraRoleTask);

                var viewTaskRoleIds = viewTaskRoleTask.Result;
                var manageProjectsRoleIds = manageProjectsRoleTask.Result;
                var viewProjectInfraRoleIds = viewProjectInfraRoleTask.Result;
                var project = projectTask.Result;

                var activityName = workItem.ActivityMaster!.ActivityName;

                var buildingName = workItem.WorkArea.Floor.Building.Name;
                var FloorName = workItem.WorkArea.Floor.FloorName;
                var AreaName = workItem.WorkArea.AreaName;

                var location = $"{buildingName} > {FloorName} > {AreaName}";

                var buildingId = workItem.WorkArea.Floor.Building.Id;
                var floorId = workItem.WorkArea.Floor.Id;
                var areaId = workItem.WorkArea.Id;

                List<Guid> projectAssignedEmployeeIds = project?.EmployeeIds ?? new List<Guid>();

                var employeeIds = await _context.EmployeeRoleMappings
                .Where(er =>
                    (projectAssignedEmployeeIds.Contains(er.EmployeeId) || manageProjectsRoleIds.Contains(er.RoleId)) &&
                    (viewTaskRoleIds.Contains(er.RoleId) || viewProjectInfraRoleIds.Contains(er.RoleId)))
                .Select(er => er.EmployeeId)
                .ToListAsync();

                Notification notificationFirebase;
                if (IsExist)
                {
                    notificationFirebase = new Notification
                    {
                        Title = $"Task Updated - {project?.ProjectName}",
                        Body = $"{name} updated tasks {activityName} at {location}."
                    };
                }
                else
                {
                    notificationFirebase = new Notification
                    {
                        Title = $"New Task Created - {project?.ProjectName}",
                        Body = $"{name} created tasks {activityName} at {location}."
                    };
                }

                var data = new Dictionary<string, string>()
                    {
                        { "Keyword", "Task_Modified" },
                        { "ProjectId", projectId.ToString() },
                        { "BuildingId", buildingId.ToString() },
                        { "FloorId", floorId.ToString() },
                        { "AreaId", areaId.ToString() }
                    };


                // List of device registration tokens to send the message to
                var registrationTokensForNotification = await _context.FCMTokenMappings
                    .Where(ft => employeeIds.Contains(ft.EmployeeId) && ft.ExpiredAt >= DateTime.UtcNow && ft.TenantId == tenantId)
                    .Select(ft => ft.FcmToken).ToListAsync();

                await SendMessageToMultipleDevicesWithDataAsync(registrationTokensForNotification, notificationFirebase, data);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Exception occured while get data for sending notification");
            }
        }
        public async Task SendModifyWorkAreaMeaasgeAsync(Guid workAreaId, string name, bool IsExist, Guid tenantId)
        {

            using var scope = _serviceScopeFactory.CreateScope();
            var _logger = scope.ServiceProvider.GetRequiredService<ILoggingService>();

            try
            {
                await using var _context = await _dbContextFactory.CreateDbContextAsync();

                var workArea = await _context.WorkAreas
                    .Include(wa => wa.Floor)
                        .ThenInclude(f => f!.Building)
                    .FirstOrDefaultAsync(wa => wa.Id == workAreaId &&
                        wa.TenantId == tenantId &&
                        wa.Floor != null &&
                        wa.Floor.Building != null);

                if (workArea == null)
                {
                    return;
                }

                var projectId = workArea.Floor!.Building!.ProjectId;

                var projectTask = Task.Run(async () =>
                {
                    await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                    return await dbContext.ProjectAllocations
                    .Include(pa => pa.Project)
                    .Where(pa => pa.ProjectId == projectId && pa.IsActive && pa.Project != null)
                    .GroupBy(pa => pa.ProjectId)
                    .Select(g => new
                    {
                        ProjectName = g.Select(pa => pa.Project!.Name).FirstOrDefault(),
                        EmployeeIds = g.Select(pa => pa.EmployeeId).Distinct().ToList()
                    }).FirstOrDefaultAsync();
                });

                var viewProjectInfraRoleTask = Task.Run(async () =>
                {
                    await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                    return await dbContext.RolePermissionMappings
                    .Where(rp => rp.FeaturePermissionId == PermissionsMaster.ViewProjectInfra)
                    .Select(rp => rp.ApplicationRoleId).ToListAsync();
                });

                var manageProjectsRoleTask = Task.Run(async () =>
                {
                    await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                    return await dbContext.RolePermissionMappings
                    .Where(rp => rp.FeaturePermissionId == PermissionsMaster.ManageProject)
                    .Select(rp => rp.ApplicationRoleId).ToListAsync();
                });

                await Task.WhenAll(projectTask, manageProjectsRoleTask, viewProjectInfraRoleTask);

                var manageProjectsRoleIds = manageProjectsRoleTask.Result;
                var viewProjectInfraRoleIds = viewProjectInfraRoleTask.Result;
                var project = projectTask.Result;

                var buildingName = workArea.Floor.Building.Name;
                var FloorName = workArea.Floor.FloorName;

                var location = $"{buildingName} > {FloorName}";

                var buildingId = workArea.Floor.Building.Id;
                var floorId = workArea.Floor.Id;
                var areaName = workArea.AreaName;

                List<Guid> projectAssignedEmployeeIds = project?.EmployeeIds ?? new List<Guid>();

                var employeeIds = await _context.EmployeeRoleMappings
                .Where(er =>
                    (projectAssignedEmployeeIds.Contains(er.EmployeeId) || manageProjectsRoleIds.Contains(er.RoleId)) && viewProjectInfraRoleIds.Contains(er.RoleId))
                .Select(er => er.EmployeeId)
                .ToListAsync();

                Notification notificationFirebase;
                if (IsExist)
                {
                    notificationFirebase = new Notification
                    {
                        Title = $"WorkArea Updated - {project?.ProjectName}",
                        Body = $"{name} updated WorkArea \"{areaName}\" at {location}."
                    };
                }
                else
                {
                    notificationFirebase = new Notification
                    {
                        Title = $"New WorkArea Created - {project?.ProjectName}",
                        Body = $"{name} created WorkArea \"{areaName}\" at {location}."
                    };
                }

                var data = new Dictionary<string, string>()
                    {
                        { "Keyword", "WorkArea_Modified" },
                        { "ProjectId", projectId.ToString() },
                        { "BuildingId", buildingId.ToString() },
                        { "FloorId", floorId.ToString() }
                    };


                // List of device registration tokens to send the message to
                var registrationTokensForNotification = await _context.FCMTokenMappings
                    .Where(ft => employeeIds.Contains(ft.EmployeeId) && ft.ExpiredAt >= DateTime.UtcNow && ft.TenantId == tenantId)
                    .Select(ft => ft.FcmToken).ToListAsync();

                await SendMessageToMultipleDevicesWithDataAsync(registrationTokensForNotification, notificationFirebase, data);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Exception occured while get data for sending notification");
            }
        }
        public async Task SendModifyFloorMeaasgeAsync(Guid floorId, string name, bool IsExist, Guid tenantId)
        {

            using var scope = _serviceScopeFactory.CreateScope();
            var _logger = scope.ServiceProvider.GetRequiredService<ILoggingService>();

            try
            {
                await using var _context = await _dbContextFactory.CreateDbContextAsync();

                var floor = await _context.Floor
                    .Include(f => f.Building)
                    .FirstOrDefaultAsync(f => f.Id == floorId &&
                        f.TenantId == tenantId &&
                        f.Building != null);

                if (floor == null)
                {
                    return;
                }

                var projectId = floor.Building!.ProjectId;

                var projectTask = Task.Run(async () =>
                {
                    await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                    return await dbContext.ProjectAllocations
                    .Include(pa => pa.Project)
                    .Where(pa => pa.ProjectId == projectId && pa.IsActive && pa.Project != null)
                    .GroupBy(pa => pa.ProjectId)
                    .Select(g => new
                    {
                        ProjectName = g.Select(pa => pa.Project!.Name).FirstOrDefault(),
                        EmployeeIds = g.Select(pa => pa.EmployeeId).Distinct().ToList()
                    }).FirstOrDefaultAsync();
                });

                var viewProjectInfraRoleTask = Task.Run(async () =>
                {
                    await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                    return await dbContext.RolePermissionMappings
                    .Where(rp => rp.FeaturePermissionId == PermissionsMaster.ViewProjectInfra)
                    .Select(rp => rp.ApplicationRoleId).ToListAsync();
                });

                var manageProjectsRoleTask = Task.Run(async () =>
                {
                    await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                    return await dbContext.RolePermissionMappings
                    .Where(rp => rp.FeaturePermissionId == PermissionsMaster.ManageProject)
                    .Select(rp => rp.ApplicationRoleId).ToListAsync();
                });

                await Task.WhenAll(projectTask, manageProjectsRoleTask, viewProjectInfraRoleTask);

                var manageProjectsRoleIds = manageProjectsRoleTask.Result;
                var viewProjectInfraRoleIds = viewProjectInfraRoleTask.Result;
                var project = projectTask.Result;

                var buildingName = floor.Building.Name;
                var floorName = floor.FloorName;

                var buildingId = floor.Building.Id;

                List<Guid> projectAssignedEmployeeIds = project?.EmployeeIds ?? new List<Guid>();

                var employeeIds = await _context.EmployeeRoleMappings
                .Where(er =>
                    (projectAssignedEmployeeIds.Contains(er.EmployeeId) || manageProjectsRoleIds.Contains(er.RoleId)) && viewProjectInfraRoleIds.Contains(er.RoleId))
                .Select(er => er.EmployeeId)
                .ToListAsync();

                Notification notificationFirebase;
                if (IsExist)
                {
                    notificationFirebase = new Notification
                    {
                        Title = $"Floor Updated - {project?.ProjectName}",
                        Body = $"{name} updated Floor \"{floorName}\" at {buildingName}."
                    };
                }
                else
                {
                    notificationFirebase = new Notification
                    {
                        Title = $"New Floor Created - {project?.ProjectName}",
                        Body = $"{name} created Floor \"{floorName}\" at {buildingName}."
                    };
                }

                var data = new Dictionary<string, string>()
                    {
                        { "Keyword", "Floor_Modified" },
                        { "ProjectId", projectId.ToString() },
                        { "BuildingId", buildingId.ToString() }
                    };


                // List of device registration tokens to send the message to
                var registrationTokensForNotification = await _context.FCMTokenMappings
                    .Where(ft => employeeIds.Contains(ft.EmployeeId) && ft.ExpiredAt >= DateTime.UtcNow && ft.TenantId == tenantId)
                    .Select(ft => ft.FcmToken).ToListAsync();

                await SendMessageToMultipleDevicesWithDataAsync(registrationTokensForNotification, notificationFirebase, data);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Exception occured while get data for sending notification");
            }
        }
        public async Task SendModifyBuildingMeaasgeAsync(Guid buildingId, string name, bool IsExist, Guid tenantId)
        {

            using var scope = _serviceScopeFactory.CreateScope();
            var _logger = scope.ServiceProvider.GetRequiredService<ILoggingService>();

            try
            {
                await using var _context = await _dbContextFactory.CreateDbContextAsync();

                var building = await _context.Buildings
                    .FirstOrDefaultAsync(b => b.Id == buildingId &&
                        b.TenantId == tenantId);

                if (building == null)
                {
                    return;
                }

                var projectId = building.ProjectId;

                var projectTask = Task.Run(async () =>
                {
                    await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                    return await dbContext.ProjectAllocations
                    .Include(pa => pa.Project)
                    .Where(pa => pa.ProjectId == projectId && pa.IsActive && pa.Project != null)
                    .GroupBy(pa => pa.ProjectId)
                    .Select(g => new
                    {
                        ProjectName = g.Select(pa => pa.Project!.Name).FirstOrDefault(),
                        EmployeeIds = g.Select(pa => pa.EmployeeId).Distinct().ToList()
                    }).FirstOrDefaultAsync();
                });

                var viewProjectInfraRoleTask = Task.Run(async () =>
                {
                    await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                    return await dbContext.RolePermissionMappings
                    .Where(rp => rp.FeaturePermissionId == PermissionsMaster.ViewProjectInfra)
                    .Select(rp => rp.ApplicationRoleId).ToListAsync();
                });

                var manageProjectsRoleTask = Task.Run(async () =>
                {
                    await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                    return await dbContext.RolePermissionMappings
                    .Where(rp => rp.FeaturePermissionId == PermissionsMaster.ManageProject)
                    .Select(rp => rp.ApplicationRoleId).ToListAsync();
                });

                await Task.WhenAll(projectTask, manageProjectsRoleTask, viewProjectInfraRoleTask);

                var manageProjectsRoleIds = manageProjectsRoleTask.Result;
                var viewProjectInfraRoleIds = viewProjectInfraRoleTask.Result;
                var project = projectTask.Result;

                List<Guid> projectAssignedEmployeeIds = project?.EmployeeIds ?? new List<Guid>();

                var employeeIds = await _context.EmployeeRoleMappings
                .Where(er =>
                    (projectAssignedEmployeeIds.Contains(er.EmployeeId) || manageProjectsRoleIds.Contains(er.RoleId)) && viewProjectInfraRoleIds.Contains(er.RoleId))
                .Select(er => er.EmployeeId)
                .ToListAsync();

                var buildingName = building.Name;

                Notification notificationFirebase;
                if (IsExist)
                {
                    notificationFirebase = new Notification
                    {
                        Title = $"Building Updated - {project?.ProjectName}",
                        Body = $"{name} updated building \"{buildingName}\" in {project?.ProjectName}."
                    };
                }
                else
                {
                    notificationFirebase = new Notification
                    {
                        Title = $"New Building Created - {project?.ProjectName}",
                        Body = $"\"{{name}} created a new building \"{buildingName}\" in {project?.ProjectName}."
                    };
                }

                var data = new Dictionary<string, string>()
                    {
                        { "Keyword", "Building_Modified" },
                        { "ProjectId", projectId.ToString() }
                    };


                // List of device registration tokens to send the message to
                var registrationTokensForNotification = await _context.FCMTokenMappings
                    .Where(ft => employeeIds.Contains(ft.EmployeeId) && ft.ExpiredAt >= DateTime.UtcNow && ft.TenantId == tenantId)
                    .Select(ft => ft.FcmToken).ToListAsync();

                await SendMessageToMultipleDevicesWithDataAsync(registrationTokensForNotification, notificationFirebase, data);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Exception occured while get data for sending notification");
            }
        }

        public async Task SendDeleteTaskMeaasgeAsync(Guid workItemId, string name, Guid tenantId)
        {

            using var scope = _serviceScopeFactory.CreateScope();
            var _logger = scope.ServiceProvider.GetRequiredService<ILoggingService>();

            try
            {
                await using var _context = await _dbContextFactory.CreateDbContextAsync();

                var workItem = await _context.WorkItems
                    .Include(wi => wi.ActivityMaster)
                    .Include(wi => wi.WorkArea)
                        .ThenInclude(wa => wa!.Floor)
                            .ThenInclude(f => f!.Building)
                    .FirstOrDefaultAsync(wi => workItemId == wi.Id &&
                        wi.TenantId == tenantId &&
                        wi.ActivityMaster != null &&
                        wi.WorkArea != null &&
                        wi.WorkArea.Floor != null &&
                        wi.WorkArea.Floor.Building != null);

                if (workItem != null)
                {
                    return;
                }

                var projectId = workItem!.WorkArea!.Floor!.Building!.ProjectId;

                var projectTask = Task.Run(async () =>
                {
                    await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                    return await dbContext.ProjectAllocations
                    .Include(pa => pa.Project)
                    .Where(pa => pa.ProjectId == projectId && pa.IsActive && pa.Project != null)
                    .GroupBy(pa => pa.ProjectId)
                    .Select(g => new
                    {
                        ProjectName = g.Select(pa => pa.Project!.Name).FirstOrDefault(),
                        EmployeeIds = g.Select(pa => pa.EmployeeId).Distinct().ToList()
                    }).FirstOrDefaultAsync();
                });

                var viewTaskRoleTask = Task.Run(async () =>
                {
                    await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                    return await dbContext.RolePermissionMappings
                    .Where(rp => rp.FeaturePermissionId == PermissionsMaster.ViewTask)
                    .Select(rp => rp.ApplicationRoleId).ToListAsync();
                });
                var viewProjectInfraRoleTask = Task.Run(async () =>
                {
                    await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                    return await dbContext.RolePermissionMappings
                    .Where(rp => rp.FeaturePermissionId == PermissionsMaster.ViewProjectInfra)
                    .Select(rp => rp.ApplicationRoleId).ToListAsync();
                });

                var manageProjectsRoleTask = Task.Run(async () =>
                {
                    await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                    return await dbContext.RolePermissionMappings
                    .Where(rp => rp.FeaturePermissionId == PermissionsMaster.ManageProject)
                    .Select(rp => rp.ApplicationRoleId).ToListAsync();
                });

                await Task.WhenAll(projectTask, viewTaskRoleTask, manageProjectsRoleTask, viewProjectInfraRoleTask);

                var viewTaskRoleIds = viewTaskRoleTask.Result;
                var manageProjectsRoleIds = manageProjectsRoleTask.Result;
                var viewProjectInfraRoleIds = viewProjectInfraRoleTask.Result;
                var project = projectTask.Result;

                var activityName = workItem.ActivityMaster!.ActivityName;

                var buildingName = workItem.WorkArea.Floor.Building.Name;
                var FloorName = workItem.WorkArea.Floor.FloorName;
                var AreaName = workItem.WorkArea.AreaName;

                var location = $"{buildingName} > {FloorName} > {AreaName}";

                var buildingId = workItem.WorkArea.Floor.Building.Id;
                var floorId = workItem.WorkArea.Floor.Id;
                var areaId = workItem.WorkArea.Id;

                List<Guid> projectAssignedEmployeeIds = project?.EmployeeIds ?? new List<Guid>();

                var employeeIds = await _context.EmployeeRoleMappings
                .Where(er =>
                    (projectAssignedEmployeeIds.Contains(er.EmployeeId) || manageProjectsRoleIds.Contains(er.RoleId)) &&
                    (viewTaskRoleIds.Contains(er.RoleId) || viewProjectInfraRoleIds.Contains(er.RoleId)))
                .Select(er => er.EmployeeId)
                .ToListAsync();

                Notification notificationFirebase = new Notification
                {
                    Title = $"Task Deleted - {project?.ProjectName}",
                    Body = $"{name} deleted tasks {activityName} at {location}."
                };


                var data = new Dictionary<string, string>()
                    {
                        { "Keyword", "Task_Modified" },
                        { "ProjectId", projectId.ToString() },
                        { "BuildingId", buildingId.ToString() },
                        { "FloorId", floorId.ToString() },
                        { "AreaId", areaId.ToString() },
                    };


                // List of device registration tokens to send the message to
                var registrationTokensForNotification = await _context.FCMTokenMappings
                    .Where(ft => employeeIds.Contains(ft.EmployeeId) && ft.ExpiredAt >= DateTime.UtcNow && ft.TenantId == tenantId)
                    .Select(ft => ft.FcmToken).ToListAsync();

                await SendMessageToMultipleDevicesWithDataAsync(registrationTokensForNotification, notificationFirebase, data);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Exception occured while get data for sending notification");
            }
        }

        //Project Allocation
        public async Task SendProjectAllocationMessageAsync(List<ProjectAllocation> projectAllocations, string name, Guid tenantId)
        {
            using var scope = _serviceScopeFactory.CreateScope();
            var _logger = scope.ServiceProvider.GetRequiredService<ILoggingService>();

            try
            {
                await using var _context = await _dbContextFactory.CreateDbContextAsync();

                foreach (var projectAllocation in projectAllocations)
                {

                    var projectId = projectAllocation.ProjectId;

                    var projectTask = Task.Run(async () =>
                    {
                        await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                        return await dbContext.ProjectAllocations
                        .Include(pa => pa.Project)
                        .Where(pa => pa.ProjectId == projectId && pa.IsActive && pa.Project != null)
                        .GroupBy(pa => pa.ProjectId)
                        .Select(g => new
                        {
                            ProjectName = g.Select(pa => pa.Project!.Name).FirstOrDefault(),
                            EmployeeIds = g.Select(pa => pa.EmployeeId).Distinct().ToList()
                        }).FirstOrDefaultAsync();
                    });

                    var manageTeamRoleTask = Task.Run(async () =>
                    {
                        await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                        return await dbContext.RolePermissionMappings
                        .Where(rp => rp.FeaturePermissionId == PermissionsMaster.ManageTeam)
                        .Select(rp => rp.ApplicationRoleId).ToListAsync();
                    });

                    var manageProjectsRoleTask = Task.Run(async () =>
                    {
                        await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                        return await dbContext.RolePermissionMappings
                        .Where(rp => rp.FeaturePermissionId == PermissionsMaster.ManageProject)
                        .Select(rp => rp.ApplicationRoleId).ToListAsync();
                    });

                    await Task.WhenAll(projectTask, manageTeamRoleTask, manageProjectsRoleTask);

                    var manageTeamRoleIds = manageTeamRoleTask.Result;
                    var manageProjectsRoleIds = manageProjectsRoleTask.Result;
                    var project = projectTask.Result;

                    List<Guid> projectAssignedEmployeeIds = project?.EmployeeIds ?? new List<Guid>();

                    var employeeIds = await _context.EmployeeRoleMappings
                    .Where(er =>
                        (projectAssignedEmployeeIds.Contains(er.EmployeeId) || manageProjectsRoleIds.Contains(er.RoleId)) && manageTeamRoleIds.Contains(er.RoleId))
                    .Select(er => er.EmployeeId)
                    .ToListAsync();

                    Notification notificationFirebase;
                    if (projectAllocation.IsActive)
                    {
                        notificationFirebase = new Notification
                        {
                            Title = $"Assigned to Project - {project?.ProjectName}",
                            Body = $"You have been assigned to the project \"{project?.ProjectName}\"."
                        };
                    }
                    else
                    {
                        notificationFirebase = new Notification
                        {
                            Title = $"Removed from Project - {project?.ProjectName}",
                            Body = $"{name} has removed you from the project \"{project?.ProjectName}\"."
                        };
                    }

                    var data = new Dictionary<string, string>()
                    {
                        { "Keyword", "Team_Modified" },
                        { "ProjectId", projectId.ToString() }
                    };


                    // List of device registration tokens to send the message to
                    var registrationTokensForNotificationTask = Task.Run(async () =>
                    {
                        await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                        var registrationTokensForNotification = await dbContext.FCMTokenMappings
                        .Where(ft => projectAllocation.EmployeeId == ft.EmployeeId && ft.ExpiredAt >= DateTime.UtcNow && ft.TenantId == tenantId)
                        .Select(ft => ft.FcmToken).ToListAsync();

                        await SendMessageToMultipleDevicesWithDataAsync(registrationTokensForNotification, notificationFirebase, data);
                    });
                    var registrationTokensForDataTask = Task.Run(async () =>
                    {
                        await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                        var registrationTokensForData = await dbContext.FCMTokenMappings
                        .Where(ft => employeeIds.Contains(ft.EmployeeId) && ft.ExpiredAt >= DateTime.UtcNow && ft.TenantId == tenantId)
                        .Select(ft => ft.FcmToken).ToListAsync();

                        await SendMessageToMultipleDevicesOnlyDataAsync(registrationTokensForData, data);
                    });

                    await Task.WhenAll(registrationTokensForNotificationTask, registrationTokensForDataTask);
                }
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Exception occured while get data for sending notification");
            }
        }

        //Project Management
        public async Task SendModifyProjectMessageAsync(Project project, string name, bool IsExist, Guid tenantId)
        {
            using var scope = _serviceScopeFactory.CreateScope();
            var _logger = scope.ServiceProvider.GetRequiredService<ILoggingService>();

            try
            {
                await using var _context = await _dbContextFactory.CreateDbContextAsync();

                var projectTask = Task.Run(async () =>
                {
                    await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                    return await dbContext.ProjectAllocations
                    .Include(pa => pa.Project)
                    .Where(pa => pa.ProjectId == project.Id && pa.IsActive && pa.Project != null)
                    .GroupBy(pa => pa.ProjectId)
                    .Select(g => new
                    {
                        ProjectName = g.Select(pa => pa.Project!.Name).FirstOrDefault(),
                        EmployeeIds = g.Select(pa => pa.EmployeeId).Distinct().ToList()
                    }).FirstOrDefaultAsync();
                });

                var manageProjectsRoleTask = Task.Run(async () =>
                {
                    await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                    return await dbContext.RolePermissionMappings
                    .Where(rp => rp.FeaturePermissionId == PermissionsMaster.ManageProject)
                    .Select(rp => rp.ApplicationRoleId).ToListAsync();
                });

                await Task.WhenAll(projectTask, manageProjectsRoleTask);

                var manageProjectsRoleIds = manageProjectsRoleTask.Result;
                var projectVM = projectTask.Result;

                List<Guid> projectAssignedEmployeeIds = projectVM?.EmployeeIds ?? new List<Guid>();

                var employeeIds = await _context.EmployeeRoleMappings
                .Where(er =>
                    (projectAssignedEmployeeIds.Contains(er.EmployeeId) || manageProjectsRoleIds.Contains(er.RoleId)))
                .Select(er => er.EmployeeId)
                .ToListAsync();

                Notification notificationFirebase;
                if (IsExist)
                {
                    notificationFirebase = new Notification
                    {
                        Title = $"Project Updated - {project.Name}",
                        Body = $"{name} updated the project \"{project.Name}\"."
                    };
                }
                else
                {
                    notificationFirebase = new Notification
                    {
                        Title = $"Project Created - {project.Name}",
                        Body = $"A new project \"{project.Name}\" has been created by {name}."
                    };
                }

                var data = new Dictionary<string, string>()
                    {
                        { "Keyword", "Project_Modefied" },
                        { "ProjectId", project.Id.ToString() }
                    };

                // List of device registration tokens to send the message to
                var registrationTokensForNotification = await _context.FCMTokenMappings
                    .Where(ft => employeeIds.Contains(ft.EmployeeId) && ft.ExpiredAt >= DateTime.UtcNow && ft.TenantId == tenantId)
                    .Select(ft => ft.FcmToken).ToListAsync();

                await SendMessageToMultipleDevicesWithDataAsync(registrationTokensForNotification, notificationFirebase, data);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Exception occured while get data for sending notification");
            }
        }

        //Expenses Controller
        public async Task SendExpenseMessageAsync(Expenses expenses, string name, Guid tenantId)
        {
            using var scope = _serviceScopeFactory.CreateScope();
            var _logger = scope.ServiceProvider.GetRequiredService<ILoggingService>();

            try
            {
                await using var _context = await _dbContextFactory.CreateDbContextAsync();
                var nextStatusIds = await _context.ExpensesStatusMapping.Where(sm => sm.StatusId == expenses.StatusId).Select(sm => sm.NextStatusId).ToListAsync();

                var requriedPermissionIds = await _context.StatusPermissionMapping.Where(sp => nextStatusIds.Contains(sp.StatusId)).Select(sp => sp.PermissionId).ToListAsync();
                requriedPermissionIds = requriedPermissionIds.Distinct().ToList();



                var notificationEmployeeTask = Task.Run(async () =>
                {
                    await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                    var roleIds = await dbContext.RolePermissionMappings
                        .Where(rp => requriedPermissionIds.Contains(rp.FeaturePermissionId))
                        .Select(rp => rp.ApplicationRoleId).ToListAsync();
                    var employeeIds = await dbContext.EmployeeRoleMappings
                       .Where(er =>
                           roleIds.Contains(er.RoleId))
                       .Select(er => er.EmployeeId)
                       .ToListAsync();
                    return employeeIds;
                });
                var dataEmployeeTask = Task.Run(async () =>
                {
                    await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                    var roleIds = await dbContext.RolePermissionMappings
                        .Where(rp => rp.FeaturePermissionId == PermissionsMaster.ExpenseViewAll)
                        .Select(rp => rp.ApplicationRoleId).ToListAsync();
                    var employeeIds = await dbContext.EmployeeRoleMappings
                       .Where(er =>
                           roleIds.Contains(er.RoleId))
                       .Select(er => er.EmployeeId)
                       .ToListAsync();
                    return employeeIds;
                });

                await Task.WhenAll(notificationEmployeeTask, dataEmployeeTask);

                var notificationEmployeeIds = notificationEmployeeTask.Result;
                var dataEmployeeIds = dataEmployeeTask.Result;

                Notification? notificationFirebase = null;
                Notification? notificationCreatedBy = null;
                if (expenses.StatusId == Review)
                {
                    notificationFirebase = new Notification
                    {
                        Title = $"Expense Awaiting Your Review",
                        Body = $"An expense of amount \"{expenses.Amount}\" has been submitted by {name} and is awaiting your review."
                    };
                }
                else if (expenses.StatusId == Approve)
                {
                    notificationFirebase = new Notification
                    {
                        Title = $"Expense Awaiting Your Approval",
                        Body = $"An expense of amount \"{expenses.Amount}\" has been reviewed by {name} and is awaiting your approval."
                    };
                    notificationCreatedBy = new Notification
                    {
                        Title = "Expense Reviewed",
                        Body = $"Your expense of amount \"{expenses.Amount}\" is reviewed by {name}."
                    };
                }
                else if (expenses.StatusId == ProcessPending)
                {
                    notificationFirebase = new Notification
                    {
                        Title = $"Expense Awaiting Your Payment",
                        Body = $"Expense of amount \"{expenses.Amount}\" has been approved by {name}. Please complete the payment."
                    };
                    notificationCreatedBy = new Notification
                    {
                        Title = "Expense Approved",
                        Body = $"Your expense of amount \"{expenses.Amount}\" is approved by {name}."
                    };
                }
                else if (expenses.StatusId == Processed)
                {
                    notificationCreatedBy = new Notification
                    {
                        Title = "Expense Paid",
                        Body = $"Your expense of amount \"{expenses.Amount}\" has been processed and paid by {name}."
                    };
                }
                else if (expenses.StatusId == RejectedByApprover || expenses.StatusId == RejectedByReviewer)
                {
                    notificationCreatedBy = new Notification
                    {
                        Title = "Expense Rejected",
                        Body = $"Your expense of amount \"{expenses.Amount}\" has been rejected by {name}."
                    };
                }

                var data = new Dictionary<string, string>()
                    {
                        { "Keyword", "Expenses_Modified" },
                        { "ExpenseId", expenses.Id.ToString() }
                    };

                var registrationTokensForNotificationTask = Task.Run(async () =>
                {
                    if (notificationFirebase != null)
                    {
                        await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                        var registrationTokensForNotification = await dbContext.FCMTokenMappings
                        .Where(ft => notificationEmployeeIds.Contains(ft.EmployeeId) && ft.ExpiredAt >= DateTime.UtcNow && ft.TenantId == tenantId)
                        .Select(ft => ft.FcmToken).ToListAsync();

                        await SendMessageToMultipleDevicesWithDataAsync(registrationTokensForNotification, notificationFirebase, data);
                    }
                });
                var registrationTokensForDataTask = Task.Run(async () =>
                {

                    await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                    var registrationTokensForNotification = await dbContext.FCMTokenMappings
                    .Where(ft => dataEmployeeIds.Contains(ft.EmployeeId) && ft.ExpiredAt >= DateTime.UtcNow && ft.TenantId == tenantId)
                    .Select(ft => ft.FcmToken).ToListAsync();

                    await SendMessageToMultipleDevicesOnlyDataAsync(registrationTokensForNotification, data);

                });
                var registrationTokensForCreatedByTask = Task.Run(async () =>
                {
                    if (notificationCreatedBy != null)
                    {
                        await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                        var registrationTokensForemployee = await dbContext.FCMTokenMappings
                        .Where(ft => ft.EmployeeId == expenses.CreatedById && ft.ExpiredAt >= DateTime.UtcNow && ft.TenantId == tenantId)
                        .Select(ft => ft.FcmToken).ToListAsync();

                        await SendMessageToMultipleDevicesWithDataAsync(registrationTokensForemployee, notificationCreatedBy, data);
                    }
                });

                await Task.WhenAll(registrationTokensForNotificationTask, registrationTokensForDataTask, registrationTokensForCreatedByTask);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Exception occured while get data for sending notification");
            }
        }

        // Directory Controller
        public async Task SendContactAsync(Guid contactId, List<Guid> bucketIds, Notification notification, Guid tenantId)
        {
            await using var _context = await _dbContextFactory.CreateDbContextAsync();
            using var scope = _serviceScopeFactory.CreateScope();
            var _logger = scope.ServiceProvider.GetRequiredService<ILoggingService>();

            var assignedEmployeeIdsTask = Task.Run(async () =>
            {
                await using var _context = await _dbContextFactory.CreateDbContextAsync();
                return await _context.EmployeeBucketMappings.Where(eb => bucketIds.Contains(eb.BucketId)).Select(eb => eb.EmployeeId).ToListAsync();
            });
            var directoryAdminEmployeeIdsTask = Task.Run(async () =>
            {
                await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                var roleIds = await dbContext.RolePermissionMappings
                    .Where(rp => rp.FeaturePermissionId == PermissionsMaster.DirectoryAdmin)
                    .Select(rp => rp.ApplicationRoleId).ToListAsync();
                var employeeIds = await dbContext.EmployeeRoleMappings
                   .Where(er =>
                       roleIds.Contains(er.RoleId))
                   .Select(er => er.EmployeeId)
                   .ToListAsync();
                return employeeIds;
            });

            await Task.WhenAll(assignedEmployeeIdsTask, directoryAdminEmployeeIdsTask);

            var assignedEmployeeIds = assignedEmployeeIdsTask.Result;
            var directoryAdminEmployeeIds = directoryAdminEmployeeIdsTask.Result;

            assignedEmployeeIds.AddRange(directoryAdminEmployeeIds);

            assignedEmployeeIds = assignedEmployeeIds.Distinct().ToList();

            var data = new Dictionary<string, string>()
            {
                { "Keyword", "Contact_Modified" },
                { "ContactId", contactId.ToString() }
            };

            var registrationTokensForNotification = await _context.FCMTokenMappings
                .Where(ft => assignedEmployeeIds.Contains(ft.EmployeeId) && ft.ExpiredAt >= DateTime.UtcNow && ft.TenantId == tenantId)
                .Select(ft => ft.FcmToken).ToListAsync();

            await SendMessageToMultipleDevicesWithDataAsync(registrationTokensForNotification, notification, data);
        }
        public async Task SendContactNoteAsync(Guid contactId, List<Guid> bucketIds, Notification notification, Guid tenantId)
        {
            await using var _context = await _dbContextFactory.CreateDbContextAsync();
            using var scope = _serviceScopeFactory.CreateScope();
            var _logger = scope.ServiceProvider.GetRequiredService<ILoggingService>();

            var assignedEmployeeIdsTask = Task.Run(async () =>
            {
                await using var _context = await _dbContextFactory.CreateDbContextAsync();
                return await _context.EmployeeBucketMappings.Where(eb => bucketIds.Contains(eb.BucketId)).Select(eb => eb.EmployeeId).ToListAsync();
            });
            var directoryAdminEmployeeIdsTask = Task.Run(async () =>
            {
                await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                var roleIds = await dbContext.RolePermissionMappings
                    .Where(rp => rp.FeaturePermissionId == PermissionsMaster.DirectoryAdmin)
                    .Select(rp => rp.ApplicationRoleId).ToListAsync();
                var employeeIds = await dbContext.EmployeeRoleMappings
                   .Where(er =>
                       roleIds.Contains(er.RoleId))
                   .Select(er => er.EmployeeId)
                   .ToListAsync();
                return employeeIds;
            });

            await Task.WhenAll(assignedEmployeeIdsTask, directoryAdminEmployeeIdsTask);

            var assignedEmployeeIds = assignedEmployeeIdsTask.Result;
            var directoryAdminEmployeeIds = directoryAdminEmployeeIdsTask.Result;

            assignedEmployeeIds.AddRange(directoryAdminEmployeeIds);

            assignedEmployeeIds = assignedEmployeeIds.Distinct().ToList();

            var data = new Dictionary<string, string>()
            {
                { "Keyword", "Contact_Note_Modified" },
                { "ContactId", contactId.ToString() }
            };

            var registrationTokensForNotification = await _context.FCMTokenMappings
                .Where(ft => assignedEmployeeIds.Contains(ft.EmployeeId) && ft.ExpiredAt >= DateTime.UtcNow && ft.TenantId == tenantId)
                .Select(ft => ft.FcmToken).ToListAsync();

            await SendMessageToMultipleDevicesWithDataAsync(registrationTokensForNotification, notification, data);
        }
        public async Task SendBucketAsync(Guid bucketId, Notification notification, Guid tenantId)
        {
            await using var _context = await _dbContextFactory.CreateDbContextAsync();
            using var scope = _serviceScopeFactory.CreateScope();
            var _logger = scope.ServiceProvider.GetRequiredService<ILoggingService>();

            var assignedEmployeeIdsTask = Task.Run(async () =>
            {
                await using var _context = await _dbContextFactory.CreateDbContextAsync();
                return await _context.EmployeeBucketMappings.Where(eb => bucketId == eb.BucketId).Select(eb => eb.EmployeeId).ToListAsync();
            });
            var directoryAdminEmployeeIdsTask = Task.Run(async () =>
            {
                await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                var roleIds = await dbContext.RolePermissionMappings
                    .Where(rp => rp.FeaturePermissionId == PermissionsMaster.DirectoryAdmin)
                    .Select(rp => rp.ApplicationRoleId).ToListAsync();
                var employeeIds = await dbContext.EmployeeRoleMappings
                   .Where(er =>
                       roleIds.Contains(er.RoleId))
                   .Select(er => er.EmployeeId)
                   .ToListAsync();
                return employeeIds;
            });

            await Task.WhenAll(assignedEmployeeIdsTask, directoryAdminEmployeeIdsTask);

            var assignedEmployeeIds = assignedEmployeeIdsTask.Result;
            var directoryAdminEmployeeIds = directoryAdminEmployeeIdsTask.Result;

            assignedEmployeeIds.AddRange(directoryAdminEmployeeIds);

            assignedEmployeeIds = assignedEmployeeIds.Distinct().ToList();

            var data = new Dictionary<string, string>()
            {
                { "Keyword", "Bucket_Modified" },
                {"BucketId", bucketId.ToString() }
            };

            var registrationTokensForNotification = await _context.FCMTokenMappings
                .Where(ft => assignedEmployeeIds.Contains(ft.EmployeeId) && ft.ExpiredAt >= DateTime.UtcNow && ft.TenantId == tenantId)
                .Select(ft => ft.FcmToken).ToListAsync();

            await SendMessageToMultipleDevicesWithDataAsync(registrationTokensForNotification, notification, data);
        }
        public async Task SendAssignBucketAsync(List<Guid> employeeIds, Notification notification, Guid tenantId)
        {
            await using var _context = await _dbContextFactory.CreateDbContextAsync();
            using var scope = _serviceScopeFactory.CreateScope();
            var _logger = scope.ServiceProvider.GetRequiredService<ILoggingService>();

            var data = new Dictionary<string, string>()
            {
                { "Keyword", "Bucket_Assigned" }
            };

            var registrationTokensForNotification = await _context.FCMTokenMappings
                .Where(ft => employeeIds.Contains(ft.EmployeeId) && ft.ExpiredAt >= DateTime.UtcNow && ft.TenantId == tenantId)
                .Select(ft => ft.FcmToken).ToListAsync();

            await SendMessageToMultipleDevicesWithDataAsync(registrationTokensForNotification, notification, data);
        }

        //Document Controller
        public async Task SendEmployeeDocumentMessageAsync(Guid documentId, Guid employeeId, Notification notification, Guid tenantId)
        {
            await using var _context = await _dbContextFactory.CreateDbContextAsync();
            using var scope = _serviceScopeFactory.CreateScope();

            var roleIds = await _context.RolePermissionMappings
                .Where(rp => rp.FeaturePermissionId == PermissionsMaster.ViewDocument)
                .Select(rp => rp.ApplicationRoleId).ToListAsync();
            var employeeIds = await _context.EmployeeRoleMappings
               .Where(er =>
                   roleIds.Contains(er.RoleId))
               .Select(er => er.EmployeeId)
               .ToListAsync();

            employeeIds.Add(employeeId);

            var data = new Dictionary<string, string>()
            {
                { "Keyword", "Employee_Document_Modified" },
                { "EmployeeId", employeeId.ToString() },
                { "DocumentId", documentId.ToString() }
            };

            var registrationTokensForNotification = await _context.FCMTokenMappings
                .Where(ft => employeeIds.Contains(ft.EmployeeId) && ft.ExpiredAt >= DateTime.UtcNow && ft.TenantId == tenantId)
                .Select(ft => ft.FcmToken).ToListAsync();

            await SendMessageToMultipleDevicesWithDataAsync(registrationTokensForNotification, notification, data);
        }
        public async Task SendProjectDocumentMessageAsync(Guid documentId, Guid projectId, Notification notification, Guid tenantId)
        {
            await using var _context = await _dbContextFactory.CreateDbContextAsync();
            using var scope = _serviceScopeFactory.CreateScope();

            var assignedEmployeeIdsTask = Task.Run(async () =>
            {
                await using var _context = await _dbContextFactory.CreateDbContextAsync();
                return await _context.ProjectAllocations.Where(pa => projectId == pa.ProjectId).Select(pa => pa.EmployeeId).ToListAsync();
            });
            var manageProjectEmployeeIdsTask = Task.Run(async () =>
            {
                await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                var roleIds = await dbContext.RolePermissionMappings
                    .Where(rp => rp.FeaturePermissionId == PermissionsMaster.ManageProject)
                    .Select(rp => rp.ApplicationRoleId).ToListAsync();
                var employeeIds = await dbContext.EmployeeRoleMappings
                   .Where(er =>
                       roleIds.Contains(er.RoleId))
                   .Select(er => er.EmployeeId)
                   .ToListAsync();
                return employeeIds;
            });
            var viewDocumentEmployeeIdsTask = Task.Run(async () =>
            {
                await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                var roleIds = await dbContext.RolePermissionMappings
                    .Where(rp => rp.FeaturePermissionId == PermissionsMaster.ViewDocument)
                    .Select(rp => rp.ApplicationRoleId).ToListAsync();
                var employeeIds = await dbContext.EmployeeRoleMappings
                   .Where(er =>
                       roleIds.Contains(er.RoleId))
                   .Select(er => er.EmployeeId)
                   .ToListAsync();
                return employeeIds;
            });
            var viewDocumentForProjectEmployeeIdsTask = Task.Run(async () =>
            {
                await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
                var employeeIds = await dbContext.ProjectLevelPermissionMappings
                   .Where(pl => pl.ProjectId == projectId && pl.PermissionId == PermissionsMaster.ViewDocument)
                   .Select(pl => pl.EmployeeId)
                   .ToListAsync();
                return employeeIds;
            });

            await Task.WhenAll(assignedEmployeeIdsTask, manageProjectEmployeeIdsTask, viewDocumentEmployeeIdsTask, viewDocumentForProjectEmployeeIdsTask);

            var assignedEmployeeIds = assignedEmployeeIdsTask.Result;
            var manageProjectEmployeeIds = manageProjectEmployeeIdsTask.Result;
            var viewDocumentEmployeeIds = viewDocumentEmployeeIdsTask.Result;
            var viewDocumentForProjectEmployeeIds = viewDocumentForProjectEmployeeIdsTask.Result;

            assignedEmployeeIds.AddRange(manageProjectEmployeeIds);
            assignedEmployeeIds.AddRange(viewDocumentForProjectEmployeeIds);

            var finalEmployeeIds = assignedEmployeeIds.Intersect(viewDocumentEmployeeIds).ToList();

            var data = new Dictionary<string, string>()
            {
                { "Keyword", "Project_Document_Modified" },
                { "ProjectId", projectId.ToString() },
                { "DocumentId", documentId.ToString() }
            };

            var registrationTokensForNotification = await _context.FCMTokenMappings
                .Where(ft => finalEmployeeIds.Contains(ft.EmployeeId) && ft.ExpiredAt >= DateTime.UtcNow && ft.TenantId == tenantId)
                .Select(ft => ft.FcmToken).ToListAsync();

            await SendMessageToMultipleDevicesWithDataAsync(registrationTokensForNotification, notification, data);
        }

        #region =================================================================== Helper Functions ===================================================================

        public async Task SendMessageToMultipleDevicesWithDataAsync(List<string> registrationTokens, Notification notificationFirebase, Dictionary<string, string> data)
        {
            using var scope = _serviceScopeFactory.CreateScope();
            var _logger = scope.ServiceProvider.GetRequiredService<ILoggingService>();
            try
            {
                registrationTokens = registrationTokens.Distinct().ToList();
                var message = new MulticastMessage()
                {
                    Tokens = registrationTokens,
                    Data = data,
                    Notification = notificationFirebase
                };
                // Send the multicast message
                var response = await FirebaseMessaging.DefaultInstance.SendEachForMulticastAsync(message);

                _logger.LogInfo("{SuccessCount} messages were sent successfully.", response.SuccessCount);

                if (response.FailureCount > 0)
                {
                    var failedTokens = new List<string>();
                    for (int i = 0; i < response.Responses.Count; i++)
                    {
                        if (!response.Responses[i].IsSuccess)
                        {
                            failedTokens.Add(registrationTokens[i]);
                        }
                    }
                    _logger.LogInfo("List of tokens that caused failures: " + string.Join(", ", failedTokens));
                }
            }
            catch (FirebaseMessagingException ex)
            {
                // Log the specific Firebase error.
                _logger.LogError(ex, "Error sending push notification");

                // Check for specific error codes that indicate an invalid or unregistered token.
                if (ex.MessagingErrorCode == MessagingErrorCode.Unregistered ||
                    ex.MessagingErrorCode == MessagingErrorCode.InvalidArgument)
                {
                    _logger.LogWarning("FCM token is invalid and should be deleted from the database");

                    // TODO: Implement the logic here to remove the invalid token from your database.
                    // Example: await YourTokenService.DeleteTokenAsync(loginDto.DeviceToken);
                }
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Exception Occured while sending notification to firebase");
            }

        }
        public async Task SendMessageToMultipleDevicesOnlyDataAsync(List<string> registrationTokens, Dictionary<string, string> data)
        {
            using var scope = _serviceScopeFactory.CreateScope();
            var _logger = scope.ServiceProvider.GetRequiredService<ILoggingService>();
            try
            {
                registrationTokens = registrationTokens.Distinct().ToList();
                var message = new MulticastMessage()
                {
                    Tokens = registrationTokens,
                    Data = data
                };
                // Send the multicast message
                var response = await FirebaseMessaging.DefaultInstance.SendEachForMulticastAsync(message);

                _logger.LogInfo("{SuccessCount} messages were sent successfully.", response.SuccessCount);

                if (response.FailureCount > 0)
                {
                    var failedTokens = new List<string>();
                    for (int i = 0; i < response.Responses.Count; i++)
                    {
                        if (!response.Responses[i].IsSuccess)
                        {
                            failedTokens.Add(registrationTokens[i]);
                        }
                    }
                    _logger.LogInfo("List of tokens that caused failures: " + string.Join(", ", failedTokens));
                }
            }
            catch (FirebaseMessagingException ex)
            {
                // Log the specific Firebase error.
                _logger.LogError(ex, "Error sending push notification");

                // Check for specific error codes that indicate an invalid or unregistered token.
                if (ex.MessagingErrorCode == MessagingErrorCode.Unregistered ||
                    ex.MessagingErrorCode == MessagingErrorCode.InvalidArgument)
                {
                    _logger.LogWarning("FCM token is invalid and should be deleted from the database");

                    // TODO: Implement the logic here to remove the invalid token from your database.
                    // Example: await YourTokenService.DeleteTokenAsync(loginDto.DeviceToken);
                }
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Exception Occured while sending notification to firebase");
            }

        }
        public async Task SendMessageToMultipleDevicesAsync(List<string> registrationTokens, Notification notificationFirebase)
        {
            using var scope = _serviceScopeFactory.CreateScope();
            var _logger = scope.ServiceProvider.GetRequiredService<ILoggingService>();
            try
            {
                registrationTokens = registrationTokens.Distinct().ToList();
                var message = new MulticastMessage()
                {
                    Tokens = registrationTokens,
                    Notification = notificationFirebase
                };
                // Send the multicast message
                var response = await FirebaseMessaging.DefaultInstance.SendEachForMulticastAsync(message);
                _logger.LogInfo("{SuccessCount} messages were sent successfully.", response.SuccessCount);

                if (response.FailureCount > 0)
                {
                    var failedTokens = new List<string>();
                    for (int i = 0; i < response.Responses.Count; i++)
                    {
                        if (!response.Responses[i].IsSuccess)
                        {
                            failedTokens.Add(registrationTokens[i]);
                        }
                    }
                    _logger.LogInfo("List of tokens that caused failures: " + string.Join(", ", failedTokens));
                }
            }
            catch (FirebaseMessagingException ex)
            {
                // Log the specific Firebase error.
                _logger.LogError(ex, "Error sending push notification");

                // Check for specific error codes that indicate an invalid or unregistered token.
                if (ex.MessagingErrorCode == MessagingErrorCode.Unregistered ||
                    ex.MessagingErrorCode == MessagingErrorCode.InvalidArgument)
                {
                    _logger.LogWarning("FCM token is invalid and should be deleted from the database");

                    // TODO: Implement the logic here to remove the invalid token from your database.
                    // Example: await YourTokenService.DeleteTokenAsync(loginDto.DeviceToken);
                }
            }

        }

        #endregion
    }
}
