using AutoMapper;
using FirebaseAdmin.Messaging;
using Marco.Pms.DataAccess.Data;
using Marco.Pms.Helpers.Utility;
using Marco.Pms.Model.Directory;
using Marco.Pms.Model.Dtos.Directory;
using Marco.Pms.Model.Employees;
using Marco.Pms.Model.Entitlements;
using Marco.Pms.Model.Mapper;
using Marco.Pms.Model.MongoDBModels.Utility;
using Marco.Pms.Model.Utilities;
using Marco.Pms.Model.ViewModels.Activities;
using Marco.Pms.Model.ViewModels.Directory;
using Marco.Pms.Model.ViewModels.Master;
using Marco.Pms.Model.ViewModels.Projects;
using Marco.Pms.Services.Service.ServiceInterfaces;
using MarcoBMS.Services.Helpers;
using MarcoBMS.Services.Service;
using Microsoft.CodeAnalysis;
using Microsoft.EntityFrameworkCore;
using System.Text.Json;

namespace Marco.Pms.Services.Service
{
    public class DirectoryService : IDirectoryService
    {
        private readonly IDbContextFactory<ApplicationDbContext> _dbContextFactory;
        private readonly ApplicationDbContext _context;
        private readonly IServiceScopeFactory _serviceScopeFactory;
        private readonly ILoggingService _logger;
        private readonly UserHelper _userHelper;
        private readonly IMapper _mapper;
        private readonly UtilityMongoDBHelper _updateLogsHelper;


        private static readonly string contactCollection = "ContactModificationLog";
        private static readonly string contactPhoneCollection = "ContactPhoneModificationLog";
        private static readonly string contactEmailCollection = "ContactEmailModificationLog";
        private static readonly string bucketCollection = "BucketModificationLog";
        private static readonly string contactNoteCollection = "ContactNoteModificationLog";


        public DirectoryService(
            IDbContextFactory<ApplicationDbContext> dbContextFactory,
            ApplicationDbContext context,
            ILoggingService logger,
            IServiceScopeFactory serviceScopeFactory,
            UserHelper userHelper,
            IMapper mapper)
        {
            _dbContextFactory = dbContextFactory ?? throw new ArgumentNullException(nameof(dbContextFactory));
            _context = context ?? throw new ArgumentNullException(nameof(context));
            _logger = logger ?? throw new ArgumentNullException(nameof(logger));
            _serviceScopeFactory = serviceScopeFactory ?? throw new ArgumentNullException(nameof(serviceScopeFactory));
            _userHelper = userHelper ?? throw new ArgumentNullException(nameof(userHelper));
            _mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
            using var scope = serviceScopeFactory.CreateScope();
            _updateLogsHelper = scope.ServiceProvider.GetRequiredService<UtilityMongoDBHelper>();
        }
        #region =================================================================== Contact APIs ===================================================================

        #region =================================================================== Contact Get APIs ===================================================================

        /// <summary>
        /// Retrieves a paginated list of contacts based on permissions, search criteria, and filters.
        /// </summary>
        /// <param name="search">A search term to filter contacts by name, organization, email, phone, or tag.</param>
        /// <param name="filter">A JSON string representing ContactFilterDto for advanced filtering.</param>
        /// <param name="projectId">Optional project ID to filter contacts assigned to a specific project.</param>
        /// <param name="active">Boolean to filter for active or inactive contacts.</param>
        /// <param name="pageSize">The number of records per page.</param>
        /// <param name="pageNumber">The current page number.</param>
        /// <param name="tenantId">The ID of the tenant to which the contacts belong.</param>
        /// <param name="loggedInEmployee">The employee making the request, used for permission checks.</param>
        /// <returns>An ApiResponse containing the paginated list of contacts or an error.</returns>
        public async Task<ApiResponse<object>> GetListOfContactsAsync(string? search, string? filter, Guid? projectId, bool active, int pageSize, int pageNumber, Guid tenantId, Employee loggedInEmployee)
        {
            Guid loggedInEmployeeId = loggedInEmployee.Id;
            _logger.LogInfo(
                "Attempting to fetch contact list for Tenant {TenantId} by Employee {EmployeeId}. Search: '{Search}', Filter: '{Filter}'",
                tenantId, loggedInEmployeeId, search ?? "", filter ?? "");

            try
            {
                // Use a single DbContext for the entire operation to ensure consistency.
                await using var dbContext = await _dbContextFactory.CreateDbContextAsync();

                // Step 1: Perform initial permission checks in parallel.
                var (hasAdminPermission, hasManagerPermission, hasUserPermission) = await CheckPermissionsAsync(loggedInEmployeeId);

                // Step 2: Build the core IQueryable<Contact> with all filtering logic applied on the server.
                // This is the most critical optimization.
                var contactQuery = dbContext.Contacts.Where(c => c.TenantId == tenantId && c.IsActive == active);

                // --- Permission-based Filtering ---
                if (!hasAdminPermission)
                {
                    if (hasManagerPermission || hasUserPermission)
                    {
                        // User can see contacts in buckets they created or are assigned to.
                        var employeeBucketIds = await dbContext.EmployeeBucketMappings
                            .Where(eb => eb.EmployeeId == loggedInEmployeeId)
                            .Select(eb => eb.BucketId)
                            .ToListAsync();

                        var accessibleBucketIds = await dbContext.Buckets
                            .Where(b => b.TenantId == tenantId && (b.CreatedByID == loggedInEmployeeId || employeeBucketIds.Contains(b.Id)))
                            .Select(b => b.Id)
                            .ToListAsync();

                        contactQuery = contactQuery.Where(c => dbContext.ContactBucketMappings.Any(cbm =>
                            cbm.ContactId == c.Id && accessibleBucketIds.Contains(cbm.BucketId)));
                    }
                    else
                    {
                        _logger.LogWarning("Access DENIED for employee {EmployeeId} attempting to get contact list due to lack of permissions.", loggedInEmployeeId);
                        return ApiResponse<object>.SuccessResponse("Access Denied", "You do not have permission to view any contacts.", 403);
                    }
                }

                // --- Advanced Filtering from 'filter' parameter ---
                ContactFilterDto? contactFilter = TryDeserializeContactFilter(filter);
                if (contactFilter != null)
                {
                    if (contactFilter.BucketIds?.Any() ?? false)
                    {
                        // Note: Permission filtering is already applied. Here we further restrict by the user's filter choice.
                        contactQuery = contactQuery.Where(c => dbContext.ContactBucketMappings.Any(cbm =>
                            cbm.ContactId == c.Id && contactFilter.BucketIds.Contains(cbm.BucketId)));
                    }
                    if (contactFilter.CategoryIds?.Any() ?? false)
                    {
                        contactQuery = contactQuery.Where(c => c.ContactCategoryId.HasValue && contactFilter.CategoryIds.Contains(c.ContactCategoryId.Value));
                    }
                }

                // --- Standard Filtering ---
                if (projectId != null)
                {
                    contactQuery = contactQuery.Where(c => dbContext.ContactProjectMappings.Any(cpm => cpm.ContactId == c.Id && cpm.ProjectId == projectId));
                }

                // --- Search Term Filtering ---
                if (!string.IsNullOrWhiteSpace(search))
                {
                    var searchTermLower = search.ToLower();
                    contactQuery = contactQuery.Where(c =>
                        (c.Name != null && c.Name.ToLower().Contains(searchTermLower)) ||
                        (c.Organization != null && c.Organization.ToLower().Contains(searchTermLower)) ||
                        dbContext.ContactsEmails.Any(e => e.ContactId == c.Id && e.EmailAddress.ToLower().Contains(searchTermLower)) ||
                        dbContext.ContactsPhones.Any(p => p.ContactId == c.Id && p.PhoneNumber.ToLower().Contains(searchTermLower)) ||
                        dbContext.ContactTagMappings.Any(ctm => ctm.ContactId == c.Id && ctm.ContactTag != null && ctm.ContactTag.Name.ToLower().Contains(searchTermLower))
                    );
                }

                // Step 3: Get the total count for pagination before applying Skip/Take.
                var totalCount = await contactQuery.CountAsync();
                if (totalCount == 0)
                {
                    return ApiResponse<object>.SuccessResponse(new { TotalPages = 0, CurrentPage = pageNumber, PageSize = pageSize, Data = new List<ContactVM>() }, "No contacts found matching the criteria.", 200);
                }

                // Step 4: Apply ordering and pagination to get the primary Contact entities for the current page.
                var contacts = await contactQuery
                    .OrderBy(c => c.Name)
                    .Skip((pageNumber - 1) * pageSize)
                    .Take(pageSize)
                    .Include(c => c.ContactCategory) // Include direct navigation properties if they exist
                    .ToListAsync();

                var finalContactIds = contacts.Select(c => c.Id).ToList();

                // Step 5: Batch-fetch all related data for only the contacts on the current page.
                // This is highly efficient and avoids the N+1 problem.
                var phonesTask = Task.Run(async () =>
                {
                    await using var taskDbContext = await _dbContextFactory.CreateDbContextAsync();
                    return await taskDbContext.ContactsPhones
                        .Where(p => finalContactIds.Contains(p.ContactId) && p.TenantId == tenantId)
                        .ToListAsync();
                });

                var emailsTask = Task.Run(async () =>
                {
                    await using var taskDbContext = await _dbContextFactory.CreateDbContextAsync();
                    return await taskDbContext.ContactsEmails
                        .Where(e => finalContactIds.Contains(e.ContactId) && e.TenantId == tenantId)
                        .ToListAsync();
                });

                var tagsTask = Task.Run(async () =>
                {
                    await using var taskDbContext = await _dbContextFactory.CreateDbContextAsync();
                    return await taskDbContext.ContactTagMappings
                        .Include(t => t.ContactTag)
                        .Where(t => finalContactIds.Contains(t.ContactId))
                        .ToListAsync();
                });

                var projectsTask = Task.Run(async () =>
                {
                    await using var taskDbContext = await _dbContextFactory.CreateDbContextAsync();
                    return await taskDbContext.ContactProjectMappings
                        .Where(p => finalContactIds.Contains(p.ContactId))
                        .ToListAsync();
                });

                var bucketsTask = Task.Run(async () =>
                {
                    await using var taskDbContext = await _dbContextFactory.CreateDbContextAsync();
                    return await taskDbContext.ContactBucketMappings
                        .Where(b => finalContactIds.Contains(b.ContactId))
                        .ToListAsync();
                });

                // Now, await all the independent, thread-safe tasks.
                await Task.WhenAll(phonesTask, emailsTask, tagsTask, projectsTask, bucketsTask);

                // Use Lookups for efficient in-memory mapping.
                var phonesLookup = (phonesTask.Result).ToLookup(p => p.ContactId);
                var emailsLookup = (emailsTask.Result).ToLookup(e => e.ContactId);
                var tagsLookup = (tagsTask.Result).ToLookup(t => t.ContactId);
                var projectsLookup = (projectsTask.Result).ToLookup(p => p.ContactId);
                var bucketsLookup = (bucketsTask.Result).ToLookup(b => b.ContactId);

                // Step 6: Map entities to ViewModels, populating with the pre-fetched related data.
                var list = contacts.Select(c =>
                {
                    var contactVM = _mapper.Map<ContactVM>(c);
                    contactVM.ContactPhones = _mapper.Map<List<ContactPhoneVM>>(phonesLookup[c.Id]);
                    contactVM.ContactEmails = _mapper.Map<List<ContactEmailVM>>(emailsLookup[c.Id]);
                    contactVM.Tags = _mapper.Map<List<ContactTagVM>>(tagsLookup[c.Id].Select(ctm => ctm.ContactTag));
                    contactVM.ProjectIds = projectsLookup[c.Id].Select(p => p.ProjectId).ToList();
                    contactVM.BucketIds = bucketsLookup[c.Id].Select(b => b.BucketId).ToList();
                    return contactVM;
                }).ToList();

                var totalPages = (int)Math.Ceiling((double)totalCount / pageSize);

                // Step 7: Construct and return the final response.
                var response = new
                {
                    TotalPages = totalPages,
                    CurrentPage = pageNumber,
                    TotalRecords = totalCount,
                    PageSize = pageSize,
                    Data = list
                };

                _logger.LogInfo("{Count} contacts were fetched successfully for Tenant {TenantId} by Employee {EmployeeId}", list.Count, tenantId, loggedInEmployeeId);
                return ApiResponse<object>.SuccessResponse(response, $"{list.Count} contacts fetched successfully.", 200);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "An unexpected error occurred while fetching contact list for Tenant {TenantId} by Employee {EmployeeId}", tenantId, loggedInEmployeeId);
                return ApiResponse<object>.ErrorResponse("An internal error occurred.", ExceptionMapper(ex), 500);
            }
        }
        public async Task<ApiResponse<object>> GetListOfContactsOld(string? search, bool active, ContactFilterDto? filterDto, Guid? projectId)
        {
            Guid tenantId = _userHelper.GetTenantId();
            var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
            var assignedRoleIds = await _context.EmployeeRoleMappings.Where(r => r.EmployeeId == LoggedInEmployee.Id).Select(r => r.RoleId).ToListAsync();
            var permissionIds = await _context.RolePermissionMappings.Where(rp => assignedRoleIds.Contains(rp.ApplicationRoleId)).Select(rp => rp.FeaturePermissionId).Distinct().ToListAsync();
            List<EmployeeBucketMapping>? employeeBuckets = await _context.EmployeeBucketMappings.Where(eb => eb.EmployeeId == LoggedInEmployee.Id).ToListAsync();
            List<Guid> bucketIds = employeeBuckets.Select(c => c.BucketId).ToList();
            if (permissionIds.Contains(PermissionsMaster.DirectoryAdmin))
            {
                var buckets = await _context.Buckets.Where(b => b.TenantId == tenantId).ToListAsync();
                bucketIds = buckets.Select(b => b.Id).ToList();
            }
            else if (permissionIds.Contains(PermissionsMaster.DirectoryAdmin) || permissionIds.Contains(PermissionsMaster.DirectoryUser))
            {
                var buckets = await _context.Buckets.Where(b => b.CreatedByID == LoggedInEmployee.Id).ToListAsync();
                var createdBucketIds = buckets.Select(b => b.Id).ToList();
                bucketIds.AddRange(createdBucketIds);
                bucketIds = bucketIds.Distinct().ToList();
            }
            else
            {
                _logger.LogWarning("Employee {EmployeeId} attemped to access a contacts, but do not have permission", LoggedInEmployee.Id);
                return ApiResponse<object>.ErrorResponse("You don't have permission", "You don't have permission", 403);
            }

            List<Guid> filterbucketIds = bucketIds;
            if (filterDto != null && filterDto.BucketIds != null && filterDto.BucketIds.Count > 0)
            {
                filterbucketIds = filterDto.BucketIds;
            }
            List<ContactBucketMapping>? contactBuckets = await _context.ContactBucketMappings.Where(cb => bucketIds.Contains(cb.BucketId)).ToListAsync();
            List<Guid> contactIds = contactBuckets.Where(b => filterbucketIds.Contains(b.BucketId)).Select(cb => cb.ContactId).ToList();
            List<Contact> contacts = new List<Contact>();

            var contactProjects = await _context.ContactProjectMappings.Where(p => contactIds.Contains(p.ContactId)).ToListAsync();

            if (projectId != null && projectId != Guid.Empty)
            {
                contactProjects = contactProjects.Where(p => p.ProjectId == projectId).ToList();
                contactIds = contactProjects.Select(p => p.ContactId).Distinct().ToList();
            }

            if (filterDto != null && filterDto.CategoryIds != null && filterDto.CategoryIds.Count > 0)
            {
                var categoryIds = filterDto.CategoryIds;
                contacts = await _context.Contacts.Include(c => c.ContactCategory).Where(c => contactIds.Contains(c.Id) && categoryIds.Contains(c.ContactCategoryId ?? Guid.Empty) && c.TenantId == tenantId && c.IsActive == active).ToListAsync();
            }
            else
            {
                contacts = await _context.Contacts.Include(c => c.ContactCategory).Where(c => contactIds.Contains(c.Id) && c.TenantId == tenantId && c.IsActive == active).ToListAsync();
            }

            var phoneNo = await _context.ContactsPhones.Where(p => contactIds.Contains(p.ContactId)).ToListAsync();
            var Emails = await _context.ContactsEmails.Where(E => contactIds.Contains(E.ContactId)).ToListAsync();
            var Tags = await _context.ContactTagMappings.Where(t => contactIds.Contains(t.ContactId)).ToListAsync();

            List<Guid> TagIds = Tags.Select(t => t.ContactTagId).ToList();

            var TagList = await _context.ContactTagMasters.Where(t => TagIds.Contains(t.Id)).ToListAsync();

            if (search != null && search != string.Empty)
            {
                List<Guid> filteredContactIds = new List<Guid>();
                phoneNo = phoneNo.Where(p => Compare(p.PhoneNumber, search)).ToList();
                filteredContactIds = phoneNo.Select(p => p.ContactId).ToList();

                Emails = Emails.Where(e => Compare(e.EmailAddress, search)).ToList();
                filteredContactIds.AddRange(Emails.Select(e => e.ContactId).ToList());
                filteredContactIds = filteredContactIds.Distinct().ToList();

                contacts = contacts.Where(c => Compare(c.Name, search) || Compare(c.Organization, search) || filteredContactIds.Contains(c.Id)).ToList();
            }

            List<ContactVM> list = new List<ContactVM>();

            foreach (var contact in contacts)
            {

                ContactVM contactVM = new ContactVM();
                List<ContactEmailVM> contactEmailVms = new List<ContactEmailVM>();
                List<ContactPhoneVM> contactPhoneVms = new List<ContactPhoneVM>();

                List<ContactTagVM> conatctTagVms = new List<ContactTagVM>();
                var phones = phoneNo.Where(p => p.ContactId == contact.Id).ToList();
                var emails = Emails.Where(e => e.ContactId == contact.Id).ToList();
                var tagMappingss = Tags.Where(t => t.ContactId == contact.Id).ToList();
                var projectMapping = contactProjects.Where(p => p.ContactId == contact.Id).ToList();
                var bucketMapping = contactBuckets.Where(b => b.ContactId == contact.Id).ToList();


                if (emails != null && emails.Count > 0)
                {
                    foreach (var email in emails)
                    {
                        ContactEmailVM emailVM = new ContactEmailVM();
                        emailVM = email.ToContactEmailVMFromContactEmail();
                        contactEmailVms.Add(emailVM);
                    }
                }

                if (phones != null && phones.Count > 0)
                {
                    foreach (var phone in phones)
                    {
                        ContactPhoneVM phoneVM = new ContactPhoneVM();
                        phoneVM = phone.ToContactPhoneVMFromContactPhone();
                        contactPhoneVms.Add(phoneVM);
                    }

                }
                if (tagMappingss != null && tagMappingss.Count > 0)
                {
                    foreach (var tagMapping in tagMappingss)
                    {
                        ContactTagVM tagVM = new ContactTagVM(); ;
                        var tag = TagList.Find(t => t.Id == tagMapping.ContactTagId);

                        tagVM = tag != null ? tag.ToContactTagVMFromContactTagMaster() : new ContactTagVM();
                        conatctTagVms.Add(tagVM);


                    }
                }
                contactVM = contact.ToContactVMFromContact();

                if (projectMapping != null && projectMapping.Count > 0)
                {
                    contactVM.ProjectIds = projectMapping.Select(p => p.ProjectId).ToList();
                }
                if (bucketMapping != null && bucketMapping.Count > 0)
                {
                    contactVM.BucketIds = bucketMapping.Select(p => p.BucketId).ToList();
                }

                contactVM.ContactEmails = contactEmailVms;
                contactVM.ContactPhones = contactPhoneVms;
                contactVM.Tags = conatctTagVms;

                list.Add(contactVM);
            }
            _logger.LogInfo("{count} contacts are fetched by Employee with ID {LoggedInEmployeeId}", list.Count, LoggedInEmployee.Id);
            return ApiResponse<object>.SuccessResponse(list, System.String.Format("{0} contacts fetched successfully", list.Count), 200);

        }
        public async Task<ApiResponse<object>> GetContactsListByBucketIdAsync(Guid bucketId, Guid tenantId, Employee loggedInEmployee)
        {
            // Validate incoming bucket ID
            if (bucketId == Guid.Empty)
            {
                _logger.LogInfo("Employee ID {EmployeeId} sent an empty Bucket ID.", loggedInEmployee.Id);
                return ApiResponse<object>.ErrorResponse("Bucket ID is empty", "Bucket ID is empty", 400);
            }

            // Check permissions of logged-in employee
            var (hasAdminPermission, hasManagerPermission, hasUserPermission) = await CheckPermissionsAsync(loggedInEmployee.Id);
            if (!hasAdminPermission && !hasManagerPermission && !hasUserPermission)
            {
                _logger.LogWarning("Access DENIED for employee {EmployeeId} attempting to get contacts list for tenant {TenantId} due to insufficient permissions.",
                    loggedInEmployee.Id, tenantId);
                return ApiResponse<object>.ErrorResponse("Access Denied", "You do not have permission to perform this action.", 403);
            }

            // Use dbContextFactory to create new DbContext instances for parallel calls
            using var context = _dbContextFactory.CreateDbContext();

            // Confirm that the bucket exists and belongs to tenant
            var bucket = await context.Buckets.FirstOrDefaultAsync(b => b.Id == bucketId && b.TenantId == tenantId);
            if (bucket == null)
            {
                _logger.LogInfo("Employee ID {EmployeeId} attempted to access non-existent bucket ID {BucketId}.", loggedInEmployee.Id, bucketId);
                return ApiResponse<object>.ErrorResponse("Bucket not found", "Bucket not found", 404);
            }

            // Load employee-bucket mappings for this bucket to check access
            var employeeBuckets = await context.EmployeeBucketMappings.Where(em => em.BucketId == bucketId).ToListAsync();

            EmployeeBucketMapping? employeeBucket = hasAdminPermission
                                                    ? employeeBuckets.FirstOrDefault()
                                                    : employeeBuckets.FirstOrDefault(eb => eb.EmployeeId == loggedInEmployee.Id);

            if (employeeBucket == null)
            {
                _logger.LogInfo("Employee ID {EmployeeId} does not have access to bucket ID {BucketId}.", loggedInEmployee.Id, bucketId);
                return ApiResponse<object>.ErrorResponse("You do not have access to this bucket.", "Unauthorized access to bucket.", 403);
            }

            // Fetch all contact-bucket mappings for this bucket
            var contactBucketMappings = await context.ContactBucketMappings.Where(cb => cb.BucketId == bucketId).ToListAsync();

            if (contactBucketMappings.Count == 0)
            {
                _logger.LogInfo("No contacts found in bucket ID {BucketId} for employee {EmployeeId}.", bucketId, loggedInEmployee.Id);
                return ApiResponse<object>.SuccessResponse(new List<ContactVM>(), "No contacts available in this bucket.", 200);
            }

            var contactIds = contactBucketMappings.Select(cb => cb.ContactId).Distinct().ToList();

            // Parallel fetching of related data via independent contexts to improve performance
            var contactTask = Task.Run(async () =>
            {
                await using var _dbContext = await _dbContextFactory.CreateDbContextAsync();

                return await _dbContext.Contacts.Include(c => c.ContactCategory)
                 .Where(c => contactIds.Contains(c.Id) && c.IsActive).ToListAsync();
            });
            var phoneTask = Task.Run(async () =>
            {
                await using var _dbContext = await _dbContextFactory.CreateDbContextAsync();

                return await _dbContext.ContactsPhones.Where(p => contactIds.Contains(p.ContactId)).ToListAsync();
            });
            var emailTask = Task.Run(async () =>
            {
                await using var _dbContext = await _dbContextFactory.CreateDbContextAsync();

                return await _dbContext.ContactsEmails.Where(e => contactIds.Contains(e.ContactId)).ToListAsync();
            });
            var tagTask = Task.Run(async () =>
            {
                await using var _dbContext = await _dbContextFactory.CreateDbContextAsync();

                return await _dbContext.ContactTagMappings.Where(ct => contactIds.Contains(ct.ContactId)).ToListAsync();
            });
            var contactProjectTask = Task.Run(async () =>
            {
                await using var _dbContext = await _dbContextFactory.CreateDbContextAsync();

                return await _dbContext.ContactProjectMappings.Where(cp => contactIds.Contains(cp.ContactId)).ToListAsync();
            });
            var contactBucketTask = Task.Run(async () =>
            {
                await using var _dbContext = await _dbContextFactory.CreateDbContextAsync();

                return await _dbContext.ContactBucketMappings.Where(cb => contactIds.Contains(cb.ContactId)).ToListAsync();
            });

            await Task.WhenAll(contactTask);

            var contacts = contactTask.Result;
            var phones = phoneTask.Result;
            var emails = emailTask.Result;
            var tags = tagTask.Result;
            var contactProjects = contactProjectTask.Result;
            var contactBuckets = contactBucketTask.Result;

            // Load tag metadata if tags found
            List<ContactTagMaster> tagMasters = new List<ContactTagMaster>();
            if (tags.Count > 0)
            {
                var tagIds = tags.Select(t => t.ContactTagId).Distinct().ToList();
                tagMasters = await context.ContactTagMasters.Where(tm => tagIds.Contains(tm.Id)).ToListAsync();
            }

            var contactVMs = new List<ContactVM>();

            // Build contact view models from data
            foreach (var contact in contacts)
            {
                // Transform phones
                var phoneVMs = phones.Where(p => p.ContactId == contact.Id).Select(p => _mapper.Map<ContactPhoneVM>(p)).ToList();

                // Transform emails
                var emailVMs = emails.Where(e => e.ContactId == contact.Id).Select(e => _mapper.Map<ContactEmailVM>(e)).ToList();

                // Transform tags
                var contactTagMappings = tags.Where(t => t.ContactId == contact.Id);
                var tagVMs = new List<ContactTagVM>();
                foreach (var ct in contactTagMappings)
                {
                    var tagMaster = tagMasters.Find(tm => tm.Id == ct.ContactTagId);
                    if (tagMaster != null)
                    {
                        tagVMs.Add(_mapper.Map<ContactTagVM>(tagMaster));
                    }
                }

                // Get project and bucket mappings for the contact
                var projectIds = contactProjects.Where(cp => cp.ContactId == contact.Id).Select(cp => cp.ProjectId).ToList();
                var bucketIds = contactBuckets.Where(cb => cb.ContactId == contact.Id).Select(cb => cb.BucketId).ToList();

                // Create the contact VM
                var contactVM = _mapper.Map<ContactVM>(contact);
                contactVM.ContactPhones = phoneVMs;
                contactVM.ContactEmails = emailVMs;
                contactVM.Tags = tagVMs;
                contactVM.ProjectIds = projectIds;
                contactVM.BucketIds = bucketIds;

                contactVMs.Add(contactVM);
            }

            _logger.LogInfo("{Count} contacts from Bucket {BucketId} fetched successfully by Employee {EmployeeId}.", contactVMs.Count, bucketId, loggedInEmployee.Id);

            return ApiResponse<object>.SuccessResponse(contactVMs, $"{contactVMs.Count} contacts fetched successfully.", 200);
        }
        public async Task<ApiResponse<object>> GetContactProfileAsync(Guid id, Guid tenantId, Employee loggedInEmployee)
        {
            Guid loggedInEmployeeId = loggedInEmployee.Id;
            if (id == Guid.Empty)
            {
                _logger.LogInfo("Employee ID {EmployeeId} sent an empty contact id", loggedInEmployeeId);
                return ApiResponse<object>.ErrorResponse("Contact ID is empty", "Contact ID is empty", 400);
            }
            try
            {
                // Use a single DbContext for the entire operation to ensure consistency.
                await using var dbContext = await _dbContextFactory.CreateDbContextAsync();

                // Step 1: Perform initial permission checks in parallel.
                var (hasAdminPermission, hasManagerPermission, hasUserPermission) = await CheckPermissionsAsync(loggedInEmployeeId);

                if (!hasAdminPermission && !hasManagerPermission && !hasUserPermission)
                {
                    _logger.LogWarning("Access DENIED for employee {EmployeeId} attempting to get contact profile due to lack of permissions.", loggedInEmployeeId);
                    return ApiResponse<object>.ErrorResponse("Access Denied", "You do not have permission to view contact.", 403);
                }

                Contact? contact = await dbContext.Contacts
                    .AsNoTracking() // Use AsNoTracking for read-only operations to improve performance.
                    .Include(c => c.ContactCategory)
                    .Include(c => c.CreatedBy)
                        .ThenInclude(e => e!.JobRole)
                    .Include(c => c.UpdatedBy)
                        .ThenInclude(e => e!.JobRole)
                    .FirstOrDefaultAsync(c => c.Id == id && c.IsActive && c.TenantId == tenantId);
                if (contact == null)
                {
                    _logger.LogWarning("Employee with ID {LoggedInEmployeeId} tries to update contact with ID {ContactId} is not found in database", loggedInEmployeeId);
                    return ApiResponse<object>.ErrorResponse("Contact not found", "Contact not found", 404);
                }
                ContactProfileVM contactVM = _mapper.Map<ContactProfileVM>(contact);

                var phonesTask = Task.Run(async () =>
                {
                    await using var taskDbContext = await _dbContextFactory.CreateDbContextAsync();
                    return await taskDbContext.ContactsPhones
                        .AsNoTracking()
                        .Where(p => p.ContactId == contact.Id && p.TenantId == tenantId)
                        .Select(p => _mapper.Map<ContactPhoneVM>(p))
                        .ToListAsync();
                });

                var emailsTask = Task.Run(async () =>
                {
                    await using var taskDbContext = await _dbContextFactory.CreateDbContextAsync();
                    return await taskDbContext.ContactsEmails
                        .AsNoTracking()
                        .Where(e => e.ContactId == contact.Id && e.TenantId == tenantId)
                        .Select(e => _mapper.Map<ContactEmailVM>(e))
                        .ToListAsync();
                });

                var contactProjectsTask = Task.Run(async () =>
                {
                    await using var taskDbContext = await _dbContextFactory.CreateDbContextAsync();
                    return await taskDbContext.ContactProjectMappings
                        .AsNoTracking()
                        .Include(cp => cp.Project)
                        .Where(cp => cp.ContactId == contact.Id && cp.Project != null && cp.Project.TenantId == tenantId)
                        .Select(cp => new BasicProjectVM
                        {
                            Id = cp.Project!.Id,
                            Name = cp.Project.Name
                        })
                        .ToListAsync();
                });

                var contactBucketsTask = Task.Run(async () =>
                {
                    await using var taskDbContext = await _dbContextFactory.CreateDbContextAsync();
                    var bucketQuery = taskDbContext.ContactBucketMappings
                    .AsNoTracking()
                    .Include(cb => cb.Bucket)
                        .ThenInclude(b => b!.CreatedBy)
                            .ThenInclude(e => e!.JobRole)
                    .Where(cb => cb.ContactId == contact.Id && cb.Bucket != null && cb.Bucket.TenantId == tenantId);

                    if (hasAdminPermission)
                    {
                        return await bucketQuery
                         .Select(cb => _mapper.Map<BucketVM>(cb.Bucket))
                         .ToListAsync();
                    }
                    List<Guid> employeeBucketIds = await taskDbContext.EmployeeBucketMappings
                    .AsNoTracking()
                    .Where(eb => eb.EmployeeId == loggedInEmployeeId)
                    .Select(eb => eb.BucketId)
                    .ToListAsync();

                    return await bucketQuery
                    .Where(cb => employeeBucketIds.Contains(cb.BucketId))
                    .Select(cb => _mapper.Map<BucketVM>(cb.Bucket))
                    .ToListAsync();

                });

                var contactTagsTask = Task.Run(async () =>
                {
                    await using var taskDbContext = await _dbContextFactory.CreateDbContextAsync();
                    return await taskDbContext.ContactTagMappings
                        .AsNoTracking()
                        .Include(ct => ct.ContactTag)
                        .Where(ct => ct.ContactId == contact.Id && ct.ContactTag != null && ct.ContactTag.TenantId == tenantId)
                        .Select(ct => _mapper.Map<ContactTagVM>(ct.ContactTag))
                        .ToListAsync();
                });

                var contactNotesTask = Task.Run(async () =>
                {
                    await using var taskDbContext = await _dbContextFactory.CreateDbContextAsync();
                    return await taskDbContext.ContactNotes
                        .AsNoTracking()
                        .Include(cn => cn.Createdby)
                            .ThenInclude(e => e!.JobRole)
                        .Include(cn => cn.UpdatedBy)
                            .ThenInclude(e => e!.JobRole)
                        .Include(cn => cn.Contact)
                        .Where(cn => cn.ContactId == contact.Id && cn.Createdby != null && cn.Createdby.TenantId == tenantId)
                        .Select(cn => _mapper.Map<ContactNoteVM>(cn))
                        .ToListAsync();
                });

                await Task.WhenAll(phonesTask, emailsTask, contactProjectsTask, contactBucketsTask, contactTagsTask, contactNotesTask);
                contactVM.ContactPhones = phonesTask.Result;
                contactVM.ContactEmails = emailsTask.Result;
                contactVM.Tags = contactTagsTask.Result;
                contactVM.Buckets = contactBucketsTask.Result;
                contactVM.Projects = contactProjectsTask.Result;
                contactVM.Notes = contactNotesTask.Result;
                _logger.LogInfo("Employee ID {EmployeeId} fetched profile of contact {ContactId}", loggedInEmployeeId, contact.Id);
                return ApiResponse<object>.SuccessResponse(contactVM, "Contact profile fetched successfully");
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "An unexpected error occurred while fetching contact profile for Tenant {TenantId} by Employee {EmployeeId}", tenantId, loggedInEmployeeId);
                return ApiResponse<object>.ErrorResponse("An internal error occurred.", ExceptionMapper(ex), 500);
            }
        }

        /// <summary>
        /// Asynchronously retrieves a distinct list of organization names for a given tenant.
        /// </summary>
        /// <param name="tenantId">The unique identifier of the tenant.</param>
        /// <param name="loggedInEmployee">The employee making the request, used for permission checks.</param>
        /// <returns>
        /// An ApiResponse containing the list of organization names on success,
        /// or an error response with appropriate status codes (403 for Forbidden, 500 for internal errors).
        /// </returns>
        public async Task<ApiResponse<object>> GetOrganizationListAsync(Guid tenantId, Employee loggedInEmployee)
        {
            // --- Parameter Validation ---
            // Fail fast if essential parameters are not provided.
            ArgumentNullException.ThrowIfNull(loggedInEmployee);

            var employeeId = loggedInEmployee.Id;

            try
            {
                // --- 1. Permission Check ---
                // Verify that the employee has at least one of the required permissions to view this data.
                // This prevents unauthorized data access early in the process.
                var (hasAdminPermission, hasManagerPermission, hasUserPermission) = await CheckPermissionsAsync(employeeId);

                if (!hasAdminPermission && !hasManagerPermission && !hasUserPermission)
                {
                    // Log the specific denial reason for security auditing.
                    _logger.LogWarning("Access DENIED for employee {EmployeeId} attempting to get organization list for tenant {TenantId} due to lack of permissions.", employeeId, tenantId);
                    // Return a strongly-typed error response.
                    return ApiResponse<object>.ErrorResponse("Access Denied", "You do not have permission to perform this action.", 403);
                }

                // --- 2. Database Query ---
                // Build and execute the database query efficiently.
                _logger.LogDebug("Fetching organization list for Tenant {TenantId} by Employee {EmployeeId}", tenantId, employeeId);

                var organizationList = await _context.Contacts
                    // Filter contacts by the specified tenant to ensure data isolation and Filter out contacts that do not have an organization name to ensure data quality.
                    .Where(c => c.TenantId == tenantId && !string.IsNullOrEmpty(c.Organization))

                    // Project only the 'Organization' column. This is a major performance optimization
                    // as it avoids loading entire 'Contact' entities into memory.
                    .Select(c => c.Organization)
                    // Let the database perform the distinct operation, which is highly efficient.
                    .Distinct()
                    // Execute the query asynchronously and materialize the results into a list.
                    .ToListAsync();

                // --- 3. Success Response ---
                // Log the successful operation with key details.
                _logger.LogInfo("Successfully fetched {OrganizationCount} distinct organizations for Tenant {TenantId} for employee {EmployeeId}", organizationList.Count, tenantId, employeeId);

                // Return a strongly-typed success response with the data and a descriptive message.
                return ApiResponse<object>.SuccessResponse(
                    organizationList,
                    $"{organizationList.Count} unique organization(s) found.",
                    200
                );
            }
            catch (Exception ex)
            {
                // --- 4. Exception Handling ---
                // Log the full exception details for effective debugging, including context.
                _logger.LogError(ex, "An unexpected error occurred while fetching organization list for Tenant {TenantId} by Employee {EmployeeId}", tenantId, employeeId);

                // Return a generic, strongly-typed error response to the client to avoid leaking implementation details.
                return ApiResponse<object>.ErrorResponse("An internal server error occurred.", ExceptionMapper(ex), 500);
            }
        }
        public async Task<ApiResponse<object>> GetDesignationListAsync(Guid tenantId, Employee loggedInEmployee)
        {
            // --- Parameter Validation ---
            // Fail fast if essential parameters are not provided.
            ArgumentNullException.ThrowIfNull(loggedInEmployee);

            var employeeId = loggedInEmployee.Id;

            try
            {
                // --- 1. Permission Check ---
                // Verify that the employee has at least one of the required permissions to view this data.
                // This prevents unauthorized data access early in the process.
                var (hasAdminPermission, hasManagerPermission, hasUserPermission) = await CheckPermissionsAsync(employeeId);

                if (!hasAdminPermission && !hasManagerPermission && !hasUserPermission)
                {
                    // Log the specific denial reason for security auditing.
                    _logger.LogWarning("Access DENIED for employee {EmployeeId} attempting to get designations list for tenant {TenantId} due to lack of permissions.", employeeId, tenantId);
                    // Return a strongly-typed error response.
                    return ApiResponse<object>.ErrorResponse("Access Denied", "You do not have permission to perform this action.", 403);
                }

                // --- 2. Database Query ---
                // Build and execute the database query efficiently.
                _logger.LogDebug("Fetching designations list for Tenant {TenantId} by Employee {EmployeeId}", tenantId, employeeId);

                var designationsList = await _context.Contacts
                    // Filter contacts by the specified tenant to ensure data isolation and Filter out contacts that do not have an designations name to ensure data quality.
                    .Where(c => c.TenantId == tenantId && !string.IsNullOrEmpty(c.Designation))

                    // Project only the 'Designation' column. This is a major performance optimization
                    // as it avoids loading entire 'Contact' entities into memory.
                    .Select(c => c.Designation)
                    // Let the database perform the distinct operation, which is highly efficient.
                    .Distinct()
                    // Execute the query asynchronously and materialize the results into a list.
                    .ToListAsync();

                // --- 3. Success Response ---
                // Log the successful operation with key details.
                _logger.LogInfo("Successfully fetched {DesignationsCount} distinct designations for Tenant {TenantId} for employee {EmployeeId}", designationsList.Count, tenantId, employeeId);

                // Return a strongly-typed success response with the data and a descriptive message.
                return ApiResponse<object>.SuccessResponse(
                    designationsList,
                    $"{designationsList.Count} unique designation(s) found.",
                    200
                );
            }
            catch (Exception ex)
            {
                // --- 4. Exception Handling ---
                // Log the full exception details for effective debugging, including context.
                _logger.LogError(ex, "An unexpected error occurred while fetching designation list for Tenant {TenantId} by Employee {EmployeeId}", tenantId, employeeId);

                // Return a generic, strongly-typed error response to the client to avoid leaking implementation details.
                return ApiResponse<object>.ErrorResponse("An internal server error occurred.", ExceptionMapper(ex), 500);
            }
        }

        /// <summary>
        /// Fetches filter options (Buckets and Contact Categories) based on user permissions
        /// for a given tenant.
        /// </summary>
        /// <param name="tenantId">The tenant ID.</param>
        /// <param name="loggedInEmployee">The employee making the request.</param>
        /// <returns>ApiResponse with Buckets and Contact Categories.</returns>
        public async Task<ApiResponse<object>> GetContactFilterObjectAsync(Guid tenantId, Employee loggedInEmployee)
        {
            try
            {
                _logger.LogInfo("Started fetching contact filters for TenantId: {TenantId}, EmployeeId: {EmployeeId}",
                    tenantId, loggedInEmployee.Id);

                // Step 1: Determine accessible bucket IDs based on permissions
                var (hasAdminPermission, hasManagerPermission, hasUserPermission) = await CheckPermissionsAsync(loggedInEmployee.Id);
                List<Guid>? accessibleBucketIds = null;

                if (hasAdminPermission)
                {
                    // Admin → Has access to all buckets in the tenant
                    accessibleBucketIds = await _context.Buckets
                        .Where(b => b.TenantId == tenantId)
                        .Select(b => b.Id)
                        .ToListAsync();

                    _logger.LogDebug("Admin access granted. Fetched {Count} tenant buckets.", accessibleBucketIds.Count);
                }
                else if (hasManagerPermission || hasUserPermission)
                {
                    // Manager/User → Only mapped buckets
                    accessibleBucketIds = await _context.EmployeeBucketMappings
                        .Where(eb => eb.EmployeeId == loggedInEmployee.Id)
                        .Select(eb => eb.BucketId)
                        .ToListAsync();

                    _logger.LogDebug("Manager/User access granted. Fetched {Count} mapped buckets for EmployeeId: {EmployeeId}",
                        accessibleBucketIds.Count, loggedInEmployee.Id);
                }
                else
                {
                    _logger.LogWarning("No permissions found for EmployeeId: {EmployeeId}. Returning empty bucket list.", loggedInEmployee.Id);
                }

                // Step 2: Fetch available buckets (accessible OR personally created by the employee)
                var buckets = await _context.Buckets
                    .Where(b =>
                        (accessibleBucketIds != null && accessibleBucketIds.Contains(b.Id) && b.TenantId == tenantId)
                        || b.CreatedByID == loggedInEmployee.Id)
                    .Select(b => new
                    {
                        Id = b.Id,
                        Name = b.Name
                    })
                    .OrderBy(b => b.Name)
                    .Distinct() // Ensures no duplicates (LINQ to Entities distinct works with anonymous types)
                    .ToListAsync();

                accessibleBucketIds = buckets.Select(b => b.Id).ToList();

                _logger.LogInfo("Fetched {Count} buckets for EmployeeId: {EmployeeId}", buckets.Count, loggedInEmployee.Id);

                // Step 3: Fetch contact categories mapped to the retrieved buckets
                var contactCategories = await _context.ContactBucketMappings
                    .AsNoTracking() // Optimized since we are reading only
                    .Include(cb => cb.Contact)
                        .ThenInclude(c => c!.ContactCategory)
                    .Where(cb =>
                        accessibleBucketIds.Contains(cb.BucketId) &&
                        cb.Contact != null &&
                        cb.Contact!.ContactCategory != null)
                    .Select(cb => new
                    {
                        Id = cb.Contact!.ContactCategory!.Id,
                        Name = cb.Contact.ContactCategory.Name
                    })
                    .OrderBy(cc => cc.Name)
                    .Distinct()
                    .ToListAsync();

                _logger.LogInfo("{Count} contact categories fetched for EmployeeId: {EmployeeId}", contactCategories.Count, loggedInEmployee.Id);

                // Step 4: Prepare response payload
                var response = new
                {
                    Buckets = buckets,
                    ContactCategories = contactCategories
                };

                _logger.LogInfo("Successfully returning filters for TenantId: {TenantId}, EmployeeId: {EmployeeId}", tenantId, loggedInEmployee.Id);
                return ApiResponse<object>.SuccessResponse(response, "Filters for contact fetched successfully", 200);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error while fetching contact filters for TenantId: {TenantId}, EmployeeId: {EmployeeId}", tenantId, loggedInEmployee.Id);
                return ApiResponse<object>.ErrorResponse("An error occurred while fetching filters", 500);
            }
        }

        #endregion

        #region =================================================================== Contact Post APIs ===================================================================

        /// <summary>
        /// Creates a new contact along with its associated details such as phone numbers, emails, tags, and project/bucket mappings.
        /// This operation is performed within a single database transaction to ensure data integrity.
        /// </summary>
        /// <param name="createContact">The DTO containing the details for the new contact.</param>
        /// <param name="tenantId">The ID of the tenant to which the contact belongs.</param>
        /// <param name="loggedInEmployee">The employee performing the action.</param>
        /// <returns>An ApiResponse containing the newly created contact's view model or an error.</returns>
        public async Task<ApiResponse<object>> CreateContactAsync(CreateContactDto createContact, Guid tenantId, Employee loggedInEmployee)
        {
            using var scope = _serviceScopeFactory.CreateScope();
            Guid loggedInEmployeeId = loggedInEmployee.Id;

            if (string.IsNullOrWhiteSpace(createContact.Name) ||
                string.IsNullOrWhiteSpace(createContact.Organization) ||
                !(createContact.BucketIds?.Any() ?? false))
            {
                _logger.LogWarning("Validation failed for CreateContactAsync. Payload missing required fields. Triggered by Employee: {LoggedInEmployeeId}", loggedInEmployeeId);
                return ApiResponse<object>.ErrorResponse("Payload is missing required fields: Name, Organization, and at least one BucketId are required.", "Invalid Payload", 400);
            }

            using var transaction = await _context.Database.BeginTransactionAsync();
            _logger.LogInfo("Starting transaction to create contact for Tenant {TenantId} by Employee {EmployeeId}", tenantId, loggedInEmployeeId);

            try
            {
                var contact = _mapper.Map<Contact>(createContact);
                if (string.IsNullOrWhiteSpace(createContact.Description))
                {
                    contact.Description = string.Empty;
                }
                if (string.IsNullOrWhiteSpace(createContact.Designation))
                {
                    contact.Designation = string.Empty;
                }
                contact.CreatedAt = DateTime.UtcNow;
                contact.CreatedById = loggedInEmployeeId;
                contact.TenantId = tenantId;
                _context.Contacts.Add(contact);

                // --- Process Phones ---
                var existingPhoneList = await _context.ContactsPhones
                    .Where(p => p.TenantId == tenantId && p.IsPrimary)
                    .Select(p => p.PhoneNumber)
                    .ToListAsync();
                var existingPhones = new HashSet<string>(existingPhoneList);
                var phoneVMs = ProcessContactPhones(createContact, contact, existingPhones);

                // --- Process Emails ---
                var existingEmailList = await _context.ContactsEmails
                    .Where(e => e.TenantId == tenantId && e.IsPrimary)
                    .Select(e => e.EmailAddress)
                    .ToListAsync();
                var existingEmails = new HashSet<string>(existingEmailList, StringComparer.OrdinalIgnoreCase);
                var emailVMs = ProcessContactEmails(createContact, contact, existingEmails);

                // --- Process Tags ---
                var tenantTags = await _context.ContactTagMasters
                    .Where(t => t.TenantId == tenantId)
                    .ToDictionaryAsync(t => t.Name.ToLowerInvariant(), t => t);
                var tagVMs = ProcessTags(createContact, contact, tenantTags);

                // --- Process Mappings ---
                var contactBucketMappings = await ProcessBucketMappingsAsync(createContact, contact, tenantId);
                var projectMappings = await ProcessProjectMappingsAsync(createContact, contact, tenantId);

                // --- Final Save and Commit ---
                try
                {
                    var changesCount = await _context.SaveChangesAsync();
                    await transaction.CommitAsync();

                    _logger.LogInfo(
                        "Successfully created Contact {ContactId} with {Count} related records for Tenant {TenantId} by Employee {EmployeeId}. Transaction committed.",
                        contact.Id, changesCount - 1, tenantId, loggedInEmployeeId);
                }
                catch (DbUpdateException dbEx)
                {
                    await transaction.RollbackAsync();
                    _logger.LogError(dbEx, "Database exception during contact creation for Tenant {TenantId}. Transaction rolled back.", tenantId);
                    return ApiResponse<object>.ErrorResponse("An internal database error occurred.", ExceptionMapper(dbEx), 500);
                }

                // --- Construct and Return Response ---
                var contactVM = _mapper.Map<ContactVM>(contact);
                contactVM.ContactPhones = phoneVMs;
                contactVM.ContactEmails = emailVMs;
                contactVM.Tags = tagVMs;
                contactVM.BucketIds = contactBucketMappings.Select(cb => cb.BucketId).ToList();
                contactVM.ProjectIds = projectMappings.Select(cp => cp.ProjectId).ToList();

                var _firebase = scope.ServiceProvider.GetRequiredService<IFirebaseService>();
                _ = Task.Run(async () =>
                {
                    // --- Push Notification Section ---
                    // This section attempts to send a test push notification to the user's device.
                    // It's designed to fail gracefully and handle invalid Firebase Cloud Messaging (FCM) tokens.
                    var name = $"{loggedInEmployee.FirstName} {loggedInEmployee.LastName}";

                    var notification = new Notification
                    {
                        Title = "New Contact Created",
                        Body = $"New Contact \"{contact.Name}\" is created by {name} in your bucket"
                    };

                    await _firebase.SendContactAsync(contact.Id, contactVM.BucketIds, notification, tenantId);

                });

                return ApiResponse<object>.SuccessResponse(contactVM, "Contact created successfully.", 201);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "An unexpected exception occurred during contact creation for Tenant {TenantId}. Transaction rolled back.", tenantId);
                return ApiResponse<object>.ErrorResponse("An unexpected internal error occurred.", ExceptionMapper(ex), 500);
            }
        }

        #endregion

        #region =================================================================== Contact Put APIs ===================================================================

        public async Task<ApiResponse<object>> UpdateContactAsync(Guid id, UpdateContactDto updateContact, Guid tenantId, Employee loggedInEmployee)
        {
            using var scope = _serviceScopeFactory.CreateScope();
            if (updateContact == null)
            {
                _logger.LogWarning("Employee {EmployeeId} sent empty payload for updating contact.", loggedInEmployee.Id);
                return ApiResponse<object>.ErrorResponse("Empty payload", "User sent empty payload", 400);
            }

            // Ensure payload ID matches path ID
            if (updateContact.Id != id)
            {
                _logger.LogWarning("Employee {EmployeeId} sent mismatched contact IDs. Path: {PathId}, Payload: {PayloadId}",
                    loggedInEmployee.Id, id, updateContact.Id);
                return ApiResponse<object>.ErrorResponse("Invalid data", "Payload ID does not match path parameter", 400);
            }

            using var context = _dbContextFactory.CreateDbContext();

            // Retrieve the contact with tracking disabled for initial existence and permission check
            var contact = await context.Contacts
                .AsNoTracking()
                .FirstOrDefaultAsync(c => c.Id == id && c.IsActive && c.TenantId == tenantId);

            if (contact == null)
            {
                _logger.LogWarning("Employee {EmployeeId} attempted to update non-existing contact {ContactId}",
                    loggedInEmployee.Id, id);
                return ApiResponse<object>.ErrorResponse("Contact not found", "Contact not found", 404);
            }

            // Validate permissions
            var (hasAdminPermission, hasManagerPermission, hasUserPermission) = await CheckPermissionsAsync(loggedInEmployee.Id);

            // Determine accessible bucket IDs for this employee
            List<Guid> bucketIds;
            if (hasAdminPermission)
            {
                bucketIds = await context.Buckets
                    .Where(b => b.TenantId == tenantId)
                    .Select(b => b.Id)
                    .ToListAsync();
            }
            else if (hasManagerPermission || hasUserPermission)
            {
                var employeeBucketIds = await context.EmployeeBucketMappings
                    .Where(eb => eb.EmployeeId == loggedInEmployee.Id)
                    .Select(eb => eb.BucketId)
                    .ToListAsync();

                var createdBucketIds = await context.Buckets
                    .Where(b => b.CreatedByID == loggedInEmployee.Id)
                    .Select(b => b.Id)
                    .ToListAsync();

                bucketIds = employeeBucketIds.Concat(createdBucketIds).Distinct().ToList();
            }
            else
            {
                _logger.LogWarning("Employee {EmployeeId} does not have permission to update contact {ContactId}",
                    loggedInEmployee.Id, id);
                return ApiResponse<object>.ErrorResponse("Unauthorized", "You do not have permission", 403);
            }

            // Fetch contact bucket mappings accessible by the user
            var contactBuckets = await context.ContactBucketMappings
                .Where(cb => cb.ContactId == contact.Id && bucketIds.Contains(cb.BucketId))
                .ToListAsync();

            // Refresh bucket IDs to only those relevant to this contact & permissions
            var accessibleBucketIds = contactBuckets.Select(cb => cb.BucketId).Distinct().ToHashSet();

            // Update the main contact object from DTO
            var updatedContact = _mapper.Map<Contact>(updateContact);
            if (string.IsNullOrWhiteSpace(updateContact.Designation))
            {
                updatedContact.Designation = string.Empty;
            }
            if (string.IsNullOrWhiteSpace(updateContact.Description))
            {
                updatedContact.Description = string.Empty;
            }
            updatedContact.TenantId = tenantId;
            updatedContact.CreatedAt = contact.CreatedAt;
            updatedContact.CreatedById = contact.CreatedById;
            updatedContact.UpdatedById = loggedInEmployee.Id;
            updatedContact.UpdatedAt = DateTime.UtcNow;

            // Attach updated contact (tracked entity)
            context.Contacts.Update(updatedContact);

            // Prepare parallel tasks for retrieving related collections in independent DbContext instances
            var phonesTask = Task.Run(async () =>
            {
                using var ctx = _dbContextFactory.CreateDbContext();
                return await ctx.ContactsPhones.AsNoTracking().Where(p => p.ContactId == contact.Id).ToListAsync();
            });

            var emailsTask = Task.Run(async () =>
            {
                using var ctx = _dbContextFactory.CreateDbContext();
                return await ctx.ContactsEmails.AsNoTracking().Where(e => e.ContactId == contact.Id).ToListAsync();
            });

            var tagsTask = Task.Run(async () =>
            {
                using var ctx = _dbContextFactory.CreateDbContext();
                return await ctx.ContactTagMappings.AsNoTracking().Where(t => t.ContactId == contact.Id).ToListAsync();
            });

            var projectsTask = Task.Run(async () =>
            {
                using var ctx = _dbContextFactory.CreateDbContext();
                return await ctx.ContactProjectMappings.AsNoTracking().Where(p => p.ContactId == contact.Id).ToListAsync();
            });

            // Await all tasks to complete
            await Task.WhenAll(phonesTask, emailsTask, tagsTask, projectsTask);

            var phones = phonesTask.Result;
            var emails = emailsTask.Result;
            var contactTags = tagsTask.Result;
            var contactProjects = projectsTask.Result;

            var phoneIds = phones.Select(p => p.Id).ToHashSet();
            var emailIds = emails.Select(e => e.Id).ToHashSet();
            var tagIds = contactTags.Select(t => t.ContactTagId).Distinct().ToHashSet();
            var projectIds = contactProjects.Select(p => p.ProjectId).Distinct().ToHashSet();

            // Fetch all tags for this tenant for name checks
            var allTags = await context.ContactTagMasters.Where(t => t.TenantId == tenantId).ToListAsync();
            var tagNameLookup = allTags.ToDictionary(t => t.Name.ToLowerInvariant(), t => t);

            var contactObject = _updateLogsHelper.EntityToBsonDocument(contact);

            var contactUpdateLog = new UpdateLogsObject
            {
                EntityId = contact.Id.ToString(),
                UpdatedById = loggedInEmployee.Id.ToString(),
                OldObject = contactObject,
                UpdatedAt = DateTime.UtcNow
            };

            List<UpdateLogsObject> phoneUpdateLogs = new List<UpdateLogsObject>();
            List<UpdateLogsObject> emailUpdateLogs = new List<UpdateLogsObject>();

            // ---------------------- Update Phones -----------------------
            if (updateContact.ContactPhones != null)
            {
                var updatedPhoneIds = updateContact.ContactPhones.Select(p => p.Id).Where(id => id != null && id != Guid.Empty).ToHashSet();

                foreach (var phoneDto in updateContact.ContactPhones)
                {
                    var phoneEntity = _mapper.Map<ContactPhone>(phoneDto);
                    phoneEntity.TenantId = tenantId;
                    phoneEntity.ContactId = contact.Id;
                    var existingPhone = phones.FirstOrDefault(p => p.Id == phoneEntity.Id);
                    if (phoneDto.Id != null && phoneDto.Id != Guid.Empty && existingPhone != null)
                    {
                        var phoneObject = _updateLogsHelper.EntityToBsonDocument(existingPhone);
                        phoneUpdateLogs.Add(new UpdateLogsObject
                        {
                            EntityId = existingPhone.Id.ToString(),
                            UpdatedById = loggedInEmployee.Id.ToString(),
                            OldObject = phoneObject,
                            UpdatedAt = DateTime.UtcNow
                        });
                        context.ContactsPhones.Update(phoneEntity);
                    }
                    else
                    {
                        context.ContactsPhones.Add(phoneEntity);
                    }
                }

                // Remove phones not updated in payload
                foreach (var phone in phones)
                {
                    if (!updatedPhoneIds.Contains(phone.Id))
                    {
                        var phoneObject = _updateLogsHelper.EntityToBsonDocument(phone);
                        phoneUpdateLogs.Add(new UpdateLogsObject
                        {
                            EntityId = phone.Id.ToString(),
                            UpdatedById = loggedInEmployee.Id.ToString(),
                            OldObject = phoneObject,
                            UpdatedAt = DateTime.UtcNow
                        });
                        context.ContactsPhones.Remove(phone);
                    }
                }
            }
            else if (phones.Any())
            {
                context.ContactsPhones.RemoveRange(phones);
            }

            // ---------------------- Update Emails -----------------------
            if (updateContact.ContactEmails != null)
            {
                var updatedEmailIds = updateContact.ContactEmails.Select(e => e.Id).Where(id => id != null && id != Guid.Empty).ToHashSet();

                foreach (var emailDto in updateContact.ContactEmails)
                {
                    var emailEntity = _mapper.Map<ContactEmail>(emailDto);
                    emailEntity.TenantId = tenantId;
                    emailEntity.ContactId = contact.Id;

                    var existingEmail = emails.FirstOrDefault(e => e.Id == emailEntity.Id);

                    if (emailDto.Id != null && emailDto.Id != Guid.Empty && existingEmail != null)
                    {
                        var emailObject = _updateLogsHelper.EntityToBsonDocument(existingEmail);
                        emailUpdateLogs.Add(new UpdateLogsObject
                        {
                            EntityId = existingEmail.Id.ToString(),
                            UpdatedById = loggedInEmployee.Id.ToString(),
                            OldObject = emailObject,
                            UpdatedAt = DateTime.UtcNow
                        });

                        context.ContactsEmails.Update(emailEntity);
                    }
                    else
                    {
                        context.ContactsEmails.Add(emailEntity);
                    }
                }

                // Remove emails not updated in payload
                foreach (var email in emails)
                {
                    if (!updatedEmailIds.Contains(email.Id))
                    {
                        var emailObject = _updateLogsHelper.EntityToBsonDocument(email);
                        phoneUpdateLogs.Add(new UpdateLogsObject
                        {
                            EntityId = email.Id.ToString(),
                            UpdatedById = loggedInEmployee.Id.ToString(),
                            OldObject = emailObject,
                            UpdatedAt = DateTime.UtcNow
                        });
                        context.ContactsEmails.Remove(email);
                    }
                }
            }
            else if (emails.Any())
            {
                context.ContactsEmails.RemoveRange(emails);
            }

            // ---------------------- Update Buckets -----------------------
            if (updateContact.BucketIds != null)
            {
                var incomingBucketIds = updateContact.BucketIds.ToHashSet();

                // Add newly added bucket mappings if accessible by user
                foreach (var bucketId in incomingBucketIds)
                {
                    if (!accessibleBucketIds.Contains(bucketId) && bucketIds.Contains(bucketId))
                    {
                        context.ContactBucketMappings.Add(new ContactBucketMapping
                        {
                            BucketId = bucketId,
                            ContactId = contact.Id
                        });
                    }
                }

                // Remove bucket mappings removed in payload
                foreach (var contactBucket in contactBuckets)
                {
                    if (!incomingBucketIds.Contains(contactBucket.BucketId))
                    {

                        context.ContactBucketMappings.Remove(contactBucket);
                    }
                }
            }
            else if (contactBuckets.Any())
            {
                context.ContactBucketMappings.RemoveRange(contactBuckets);
            }

            // ---------------------- Update Projects -----------------------
            if (updateContact.ProjectIds != null)
            {
                var incomingProjectIds = updateContact.ProjectIds.ToHashSet();

                foreach (var projectId in incomingProjectIds)
                {
                    if (!projectIds.Contains(projectId))
                    {
                        context.ContactProjectMappings.Add(new ContactProjectMapping
                        {
                            ProjectId = projectId,
                            ContactId = contact.Id,
                            TenantId = tenantId
                        });
                    }
                }

                foreach (var projectMapping in contactProjects)
                {
                    if (!incomingProjectIds.Contains(projectMapping.ProjectId))
                    {
                        context.ContactProjectMappings.Remove(projectMapping);
                    }
                }
            }
            else if (contactProjects.Any())
            {
                context.ContactProjectMappings.RemoveRange(contactProjects);
            }

            // ---------------------- Update Tags -----------------------
            if (updateContact.Tags != null)
            {
                var updatedTagIds = updateContact.Tags.Select(t => t.Id).Where(id => id != null && id != Guid.Empty).ToHashSet();

                foreach (var tagDto in updateContact.Tags)
                {
                    var lowerName = tagDto.Name.Trim().ToLowerInvariant();

                    bool existsByName = !string.IsNullOrWhiteSpace(lowerName) && tagNameLookup.ContainsKey(lowerName);
                    bool idNotExistsInMapping = !updatedTagIds.Contains(tagDto.Id ?? Guid.Empty);

                    if (existsByName && idNotExistsInMapping)
                    {
                        // Use existing tag by name
                        var existingTag = tagNameLookup[lowerName];
                        context.ContactTagMappings.Add(new ContactTagMapping
                        {
                            ContactId = contact.Id,
                            ContactTagId = tagDto.Id ?? existingTag.Id
                        });
                    }
                    else if (tagDto.Id == null || tagDto.Id == Guid.Empty)
                    {
                        // Create new tag master and mapping
                        var newTagMaster = new ContactTagMaster
                        {
                            Name = tagDto.Name,
                            Description = tagDto.Name,
                            TenantId = tenantId
                        };
                        context.ContactTagMasters.Add(newTagMaster);

                        // Mapping will use newTagMaster.Id once saved (EF will fix after SaveChanges)
                        context.ContactTagMappings.Add(new ContactTagMapping
                        {
                            ContactId = contact.Id,
                            ContactTagId = newTagMaster.Id
                        });
                    }
                }

                // Remove tag mappings no longer present
                foreach (var contactTagMapping in contactTags)
                {
                    if (!updatedTagIds.Contains(contactTagMapping.ContactTagId))
                    {
                        context.ContactTagMappings.Remove(contactTagMapping);
                    }
                }
            }
            else if (contactTags.Any())
            {
                context.ContactTagMappings.RemoveRange(contactTags);
            }

            // ---------------------- Add Update Log -----------------------
            context.DirectoryUpdateLogs.Add(new DirectoryUpdateLog
            {
                RefereanceId = contact.Id,
                UpdatedById = loggedInEmployee.Id,
                UpdateAt = DateTime.UtcNow
            });

            // Save all changes once here
            try
            {
                await context.SaveChangesAsync();
            }
            catch (DbUpdateConcurrencyException ex)
            {
                _logger.LogError(ex, "Concurrency conflict while employee {EmployeeId} was updating contact {ContactId}", loggedInEmployee.Id, contact.Id);
                return ApiResponse<object>.ErrorResponse("Concurrency conflict", "This contact was updated by another user. Please reload and try again.", 409);
            }

            // Reload updated contact and related data for response, using a fresh context
            var responseTask = Task.Run(async () =>
            {
                using var responseContext = _dbContextFactory.CreateDbContext();

                var reloadedContact = await responseContext.Contacts
                    .Include(c => c.ContactCategory)
                    .FirstOrDefaultAsync(c => c.Id == id && c.IsActive && c.TenantId == tenantId) ?? new Contact();

                var responsePhones = await responseContext.ContactsPhones.AsNoTracking().Where(p => p.ContactId == reloadedContact.Id).ToListAsync();
                var responseEmails = await responseContext.ContactsEmails.AsNoTracking().Where(e => e.ContactId == reloadedContact.Id).ToListAsync();
                var responseContactTags = await responseContext.ContactTagMappings.AsNoTracking().Where(t => t.ContactId == reloadedContact.Id).ToListAsync();
                var responseContactBuckets = await responseContext.ContactBucketMappings.AsNoTracking().Where(cb => cb.ContactId == reloadedContact.Id).ToListAsync();
                var responseContactProjects = await responseContext.ContactProjectMappings.AsNoTracking().Where(cp => cp.ContactId == reloadedContact.Id).ToListAsync();

                var tagIdsForResponse = responseContactTags.Select(t => t.ContactTagId).Distinct().ToList();
                var tagsForResponse = await responseContext.ContactTagMasters.Where(t => tagIdsForResponse.Contains(t.Id)).ToListAsync();

                // Map entities to view models
                var response = reloadedContact.ToContactVMFromContact();

                response.ContactPhones = responsePhones.Select(p => p.ToContactPhoneVMFromContactPhone()).ToList();
                response.ContactEmails = responseEmails.Select(e => e.ToContactEmailVMFromContactEmail()).ToList();
                response.Tags = responseContactTags.Select(ctm =>
                {
                    var tag = tagsForResponse.Find(t => t.Id == ctm.ContactTagId);
                    return tag != null ? tag.ToContactTagVMFromContactTagMaster() : new ContactTagVM();
                }).ToList();

                response.BucketIds = responseContactBuckets.Select(cb => cb.BucketId).ToList();
                response.ProjectIds = responseContactProjects.Select(cp => cp.ProjectId).ToList();
                return response;
            });

            var contactUpdateLogTask = Task.Run(async () =>
            {
                await _updateLogsHelper.PushToUpdateLogsAsync(contactUpdateLog, contactCollection);
            });
            var phoneUpdateLogTask = Task.Run(async () =>
            {
                await _updateLogsHelper.PushListToUpdateLogsAsync(phoneUpdateLogs, contactPhoneCollection);
            });
            var emailUpdateLogTask = Task.Run(async () =>
            {
                await _updateLogsHelper.PushListToUpdateLogsAsync(emailUpdateLogs, contactEmailCollection);
            });


            await Task.WhenAll(responseTask, contactUpdateLogTask, phoneUpdateLogTask, emailUpdateLogTask);

            var contactVM = responseTask.Result;

            _logger.LogInfo("Contact {ContactId} successfully updated by employee {EmployeeId}.", contact.Id, loggedInEmployee.Id);
            var _firebase = scope.ServiceProvider.GetRequiredService<IFirebaseService>();

            _ = Task.Run(async () =>
            {
                // --- Push Notification Section ---
                // This section attempts to send a test push notification to the user's device.
                // It's designed to fail gracefully and handle invalid Firebase Cloud Messaging (FCM) tokens.

                if (contactVM.BucketIds?.Any() ?? false)
                {
                    var name = $"{loggedInEmployee.FirstName} {loggedInEmployee.LastName}";

                    var notification = new Notification
                    {
                        Title = $"Contact Updated - \"{contact.Name}\"",
                        Body = $"Contact \"{contact.Name}\" is updated by {name} in your bucket"
                    };

                    await _firebase.SendContactAsync(updateContact.Id, contactVM.BucketIds, notification, tenantId);
                }

            });

            return ApiResponse<object>.SuccessResponse(contactVM, "Contact Updated Successfully", 200);
        }

        #endregion

        #region =================================================================== Contact Delete APIs ===================================================================

        public async Task<ApiResponse<object>> DeleteContactAsync(Guid id, bool active, Guid tenantId, Employee loggedInEmployee)
        {
            // Validate the contact id
            if (id == Guid.Empty)
            {
                _logger.LogWarning("Employee ID {EmployeeId} attempted to delete with an empty contact ID.", loggedInEmployee.Id);
                return ApiResponse<object>.ErrorResponse("Contact ID is empty", "Contact ID is empty", 400);
            }

            var bucketIds = await _context.ContactBucketMappings.Where(cb => cb.ContactId == id).Select(cb => cb.BucketId).ToListAsync();
            var hasContactAccess = await _context.EmployeeBucketMappings.AnyAsync(eb => bucketIds.Contains(eb.BucketId) && eb.EmployeeId == loggedInEmployee.Id);
            if (hasContactAccess)
            {
                _logger.LogWarning("Employee {EmployeeId} does not have permission to delete contact {ContactId}",
                     loggedInEmployee.Id, id);
                return ApiResponse<object>.ErrorResponse("Unauthorized", "You do not have permission", 403);
            }

            // Try to find the contact for the given tenant
            Contact? contact = await _context.Contacts.FirstOrDefaultAsync(c => c.Id == id && c.TenantId == tenantId);

            if (contact == null)
            {
                _logger.LogWarning("Employee ID {EmployeeId} attempted to delete contact ID {ContactId}, but it was not found.", loggedInEmployee.Id, id);
                return ApiResponse<object>.ErrorResponse("Contact not found", "Contact not found", 404);
            }

            var contactObject = _updateLogsHelper.EntityToBsonDocument(contact);

            // Update the contact's active status (soft delete or activate)
            contact.IsActive = active;

            // Log the update in the directory update logs
            _context.DirectoryUpdateLogs.Add(new DirectoryUpdateLog
            {
                RefereanceId = contact.Id,
                UpdatedById = loggedInEmployee.Id,
                UpdateAt = DateTime.UtcNow
            });
            // Save changes to the database
            await _context.SaveChangesAsync();

            await _updateLogsHelper.PushToUpdateLogsAsync(new UpdateLogsObject
            {
                EntityId = contact.Id.ToString(),
                UpdatedById = loggedInEmployee.Id.ToString(),
                OldObject = contactObject,
                UpdatedAt = DateTime.UtcNow
            }, contactCollection);

            _logger.LogInfo("Contact ID {ContactId} has been {(DeletedOrActivated)} by Employee ID {EmployeeId}.", id, active ? "activated" : "deleted", loggedInEmployee.Id);

            using var scope = _serviceScopeFactory.CreateScope();
            var _firebase = scope.ServiceProvider.GetRequiredService<IFirebaseService>();

            _ = Task.Run(async () =>
            {
                // --- Push Notification Section ---
                // This section attempts to send a test push notification to the user's device.
                // It's designed to fail gracefully and handle invalid Firebase Cloud Messaging (FCM) tokens.

                if (bucketIds.Any())
                {
                    var name = $"{loggedInEmployee.FirstName} {loggedInEmployee.LastName}";

                    Notification notification;
                    if (active)
                    {
                        notification = new Notification
                        {
                            Title = $"Contact restored - \"{contact.Name}\"",
                            Body = $"Contact \"{contact.Name}\" is restored by {name} in your bucket"
                        };
                    }
                    else
                    {
                        notification = new Notification
                        {
                            Title = $"Contact Deleted - \"{contact.Name}\"",
                            Body = $"Contact \"{contact.Name}\" is deleted by {name} in your bucket"
                        };
                    }

                    await _firebase.SendContactAsync(contact.Id, bucketIds, notification, tenantId);
                }

            });

            return ApiResponse<object>.SuccessResponse(new { }, active ? "Contact is activated successfully" : "Contact is deleted successfully", 200);
        }

        #endregion

        #endregion

        #region =================================================================== Contact Notes APIs ===================================================================

        public async Task<ApiResponse<object>> GetListOFAllNotesAsync(Guid? projectId, string? searchString, string? filter, int pageSize, int pageNumber, Guid tenantId, Employee loggedInEmployee)
        {
            _logger.LogInfo("Initiating GetListOFAllNotesAsync. TenantId: {TenantId}, ProjectId: {ProjectId}, PageSize: {PageSize}, PageNumber: {PageNumber}, EmployeeId: {EmployeeId}",
                tenantId, projectId ?? Guid.Empty, pageSize, pageNumber, loggedInEmployee.Id);

            // Ensure user context is present
            if (loggedInEmployee.Id == Guid.Empty || tenantId == Guid.Empty)
            {
                _logger.LogWarning("Unauthorized: LoggedInEmployee is null.");
                return ApiResponse<object>.ErrorResponse("Unauthorized", "Employee not found.", 403);
            }

            try
            {
                // Use a context instance per method call for safety in parallel scenarios
                await using var context = _dbContextFactory.CreateDbContext();

                // Permission checks (parallel as they're independent)
                var (hasAdminPermission, hasManagerPermission, hasUserPermission) = await CheckPermissionsAsync(loggedInEmployee.Id);

                // Access control
                if (!hasAdminPermission && !hasManagerPermission && !hasUserPermission)
                {
                    _logger.LogWarning("Access Denied. EmployeeId: {EmployeeId}, TenantId: {TenantId}", loggedInEmployee.Id, tenantId);
                    return ApiResponse<object>.ErrorResponse("Access Denied", "You don't have access to view notes.", 403);
                }

                // Build base query
                IQueryable<ContactNote> notesQuery = context.ContactNotes
                    .Include(cn => cn.UpdatedBy)
                        .ThenInclude(e => e!.JobRole)
                    .Include(cn => cn.Createdby)
                        .ThenInclude(e => e!.JobRole)
                    .Include(cn => cn.Contact)
                    .Where(cn => cn.TenantId == tenantId);

                // Fetch associated contact IDs for project (if filtering by project)
                List<Guid>? projectContactIds = null;
                if (projectId.HasValue)
                {
                    projectContactIds = await context.ContactProjectMappings
                        .Where(pc => pc.ProjectId == projectId.Value)
                        .Select(pc => pc.ContactId)
                        .ToListAsync();
                }

                if (!hasAdminPermission) // Manager/User filtering
                {
                    _logger.LogInfo("Non-admin user. Applying bucket-based filtering. EmployeeId: {EmployeeId}", loggedInEmployee.Id);
                    // Get assigned bucket IDs
                    var assignedBucketIds = await context.EmployeeBucketMappings
                        .Where(eb => eb.EmployeeId == loggedInEmployee.Id)
                        .Select(eb => eb.BucketId)
                        .ToListAsync();

                    if (!assignedBucketIds.Any())
                    {
                        _logger.LogInfo("No assigned buckets for user: {EmployeeId}", loggedInEmployee.Id);
                        return ApiResponse<object>.SuccessResponse(new
                        {
                            CurrentPage = pageNumber,
                            TotalPages = 0,
                            Data = new List<ContactNoteVM>()
                        }, "No notes found based on assigned buckets.", 200);
                    }

                    // Contacts based on assigned buckets, further filtered by project (if provided)
                    var contactBucketQuery = context.ContactBucketMappings
                        .Where(cb => assignedBucketIds.Contains(cb.BucketId));

                    if (projectContactIds != null)
                    {
                        contactBucketQuery = contactBucketQuery.Where(cb => projectContactIds.Contains(cb.ContactId));
                    }

                    var contactIds = await contactBucketQuery.Select(cb => cb.ContactId).Distinct().ToListAsync();

                    if (!contactIds.Any())
                    {
                        _logger.LogInfo("No contacts found for assigned buckets for user: {EmployeeId}", loggedInEmployee.Id);
                        return ApiResponse<object>.SuccessResponse(new
                        {
                            CurrentPage = pageNumber,
                            TotalPages = 0,
                            Data = new List<ContactNoteVM>()
                        }, "No notes found for associated contacts.", 200);
                    }

                    notesQuery = notesQuery.Where(cn => contactIds.Contains(cn.ContactId));
                }
                else
                {
                    // Admin: If project specified, filter notes further
                    if (projectContactIds != null)
                        notesQuery = notesQuery.Where(cn => projectContactIds.Contains(cn.ContactId));
                }

                // --- Advanced Filtering from 'filter' parameter ---
                ContactNoteFilter? contactNoteFilter = TryDeserializeContactNoteFilter(filter);
                if (contactNoteFilter != null)
                {
                    if (contactNoteFilter.CreatedByIds?.Any() ?? false)
                    {
                        notesQuery = notesQuery.Where(cn => contactNoteFilter.CreatedByIds.Contains(cn.CreatedById));
                    }
                    if (contactNoteFilter.Organizations?.Any() ?? false)
                    {
                        notesQuery = notesQuery.Where(cn => cn.Contact != null && contactNoteFilter.Organizations.Contains(cn.Contact.Organization));
                    }
                }

                // --- Search Term Filtering ---
                if (!string.IsNullOrWhiteSpace(searchString))
                {
                    var searchTermLower = searchString.ToLower();
                    notesQuery = notesQuery.Where(c =>
                        (c.Contact != null && c.Contact.Name.ToLower().Contains(searchTermLower)) ||
                        (c.Contact != null && c.Contact.Organization != null && c.Contact.Organization.ToLower().Contains(searchTermLower)) ||
                        c.Note.ToLower().Contains(searchTermLower)
                    );
                }

                // Pagination safeguard
                pageSize = pageSize < 1 ? 25 : pageSize;
                pageNumber = pageNumber < 1 ? 1 : pageNumber;

                // Accurate pagination metadata
                int totalRecords = await notesQuery.CountAsync();
                int totalPages = (int)Math.Ceiling(totalRecords / (double)pageSize);

                // Fetch paginated, ordered results
                List<ContactNote> notes = await notesQuery
                    .OrderByDescending(cn => cn.UpdatedAt ?? cn.CreatedAt)
                    .Skip((pageNumber - 1) * pageSize)
                    .Take(pageSize)
                    .ToListAsync();

                _logger.LogInfo("Notes fetched: {Count}, Page: {PageNumber}/{TotalPages}, EmployeeId: {EmployeeId}, TenantId: {TenantId}",
                    notes.Count, pageNumber, totalPages, loggedInEmployee.Id, tenantId);

                // In-memory mapping to ViewModel
                var noteVms = _mapper.Map<List<ContactNoteVM>>(notes);

                var response = new
                {
                    CurrentPage = pageNumber,
                    PageSize = pageSize,
                    TotalPages = totalPages,
                    TotalRecords = totalRecords,
                    Data = noteVms
                };

                _logger.LogInfo("Notes mapped to ViewModel for TenantId: {TenantId}, EmployeeId: {EmployeeId}", tenantId, loggedInEmployee.Id);
                return ApiResponse<object>.SuccessResponse(response, $"{noteVms.Count} notes fetched successfully.", 200);

            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Exception occurred in GetListOFAllNotesAsync. TenantId: {TenantId}, EmployeeId: {EmployeeId}", tenantId, loggedInEmployee.Id);
                return ApiResponse<object>.ErrorResponse("Internal Server Error", "An error occurred while fetching notes. Please try again later.", 500);
            }
        }

        /// <summary>
        /// Fetches all notes associated with a given contact, subject to permission checks and contact-bucket mappings.
        /// </summary>
        /// <param name="id">The contact ID.</param>
        /// <param name="tenantId">The tenant ID of the current user.</param>
        /// <param name="active">Whether to filter for active notes only.</param>
        /// <param name="loggedInEmployee">The currently logged in employee object.</param>
        /// <returns>Returns a list of contact notes wrapped in ApiResponse.</returns>
        public async Task<ApiResponse<object>> GetNoteListByContactIdAsync(Guid id, bool active, Guid tenantId, Employee loggedInEmployee)
        {
            // Step 1: Permission Validation
            var (hasAdminPermission, hasManagerPermission, hasUserPermission) = await CheckPermissionsAsync(loggedInEmployee.Id);
            if (!hasAdminPermission && !hasManagerPermission && !hasUserPermission)
            {
                _logger.LogWarning(
                    "Access Denied. EmployeeId: {EmployeeId}, TenantId: {TenantId}. No permissions granted.",
                    loggedInEmployee.Id, tenantId);

                return ApiResponse<object>.ErrorResponse(
                    "Access Denied",
                    "You don't have access to view notes.",
                    StatusCodes.Status403Forbidden);
            }

            // Step 2: Validate Contact Exists
            Contact? contact = await _context.Contacts
                .AsNoTracking() // optimization: no tracking needed
                .FirstOrDefaultAsync(c => c.Id == id && c.IsActive && c.TenantId == tenantId);

            if (contact == null)
            {
                _logger.LogWarning(
                    "Employee {EmployeeId} attempted to fetch notes for Contact {ContactId}, but the contact was not found. TenantId: {TenantId}",
                    loggedInEmployee.Id, id, tenantId);

                return ApiResponse<object>.ErrorResponse(
                    "Contact not found",
                    "Contact not found",
                    StatusCodes.Status404NotFound);
            }

            // Step 3: Bucket-level Security Checks (Non-admin users)
            if (!hasAdminPermission)
            {
                var employeeBucketIds = await _context.EmployeeBucketMappings
                    .AsNoTracking()
                    .Where(em => em.EmployeeId == loggedInEmployee.Id)
                    .Select(em => em.BucketId)
                    .ToListAsync();

                bool hasContactAccess = await _context.ContactBucketMappings
                    .AsNoTracking()
                    .AnyAsync(cb => employeeBucketIds.Contains(cb.BucketId) && cb.ContactId == contact.Id);

                if (!hasContactAccess)
                {
                    _logger.LogWarning(
                        "Access Denied. EmployeeId: {EmployeeId}, TenantId: {TenantId}. No bucket access for ContactId {ContactId}",
                        loggedInEmployee.Id, tenantId, contact.Id);

                    return ApiResponse<object>.ErrorResponse(
                        "Access Denied",
                        "You don't have access to view notes.",
                        StatusCodes.Status403Forbidden);
                }
            }

            // Step 4: Fetch Notes
            var notesQuery = _context.ContactNotes
                .Include(n => n.Createdby) // Eager load creator
                    .ThenInclude(e => e!.JobRole)
                .Include(n => n.UpdatedBy) // Eager load updater
                    .ThenInclude(e => e!.JobRole)
                .Where(n => n.ContactId == contact.Id && n.TenantId == tenantId);

            if (active)
                notesQuery = notesQuery.Where(n => n.IsActive);

            List<ContactNote> notes = await notesQuery
                .AsNoTracking() // reduce EF overhead
                .ToListAsync();

            // Step 5: Fetch Update Logs in one DB call
            var noteIds = notes.Select(n => n.Id).ToList();
            List<DirectoryUpdateLog> updateLogs = new();

            if (noteIds.Count > 0) // only fetch logs if needed
            {
                updateLogs = await _context.DirectoryUpdateLogs
                    .Include(l => l.Employee)
                        .ThenInclude(e => e!.JobRole)
                    .AsNoTracking()
                    .Where(l => noteIds.Contains(l.RefereanceId))
                    .ToListAsync();
            }

            // Step 6: Map Entities to ViewModels
            List<ContactNoteVM> noteVMs = _mapper.Map<List<ContactNoteVM>>(notes);

            // Step 7: Final Log + Response
            _logger.LogInfo(
                "Employee {EmployeeId} successfully fetched {Count} notes for Contact {ContactId} in Tenant {TenantId}",
                loggedInEmployee.Id, noteVMs.Count, id, tenantId);

            return ApiResponse<object>.SuccessResponse(
                noteVMs,
                $"{noteVMs.Count} contact-notes record(s) fetched successfully",
                StatusCodes.Status200OK);
        }

        /// <summary>
        /// Fetches filter objects (CreatedBy employees and Organizations) for Contact Notes
        /// accessible by the logged-in employee, based on permissions.
        /// </summary>
        /// <param name="tenantId">The tenant ID.</param>
        /// <param name="loggedInEmployee">The employee requesting filters.</param>
        /// <returns>ApiResponse containing CreatedBy and Organizations filter options.</returns>
        public async Task<ApiResponse<object>> GetContactNotesFilterObjectAsync(Guid tenantId, Employee loggedInEmployee)
        {
            try
            {
                _logger.LogInfo("Started fetching Contact Notes filters for TenantId: {TenantId}, EmployeeId: {EmployeeId}",
                    tenantId, loggedInEmployee.Id);

                // Step 1: Fetch accessible bucket IDs based on permissions
                var (hasAdminPermission, hasManagerPermission, hasUserPermission) = await CheckPermissionsAsync(loggedInEmployee.Id);
                List<Guid>? accessibleBucketIds = null;

                if (hasAdminPermission)
                {
                    // Admin → Access to all buckets in the tenant
                    accessibleBucketIds = await _context.Buckets
                        .Where(b => b.TenantId == tenantId)
                        .Select(b => b.Id)
                        .ToListAsync();

                    _logger.LogDebug("Admin access granted. Found {Count} buckets.", accessibleBucketIds.Count);
                }
                else if (hasManagerPermission || hasUserPermission)
                {
                    // Manager/User → Access to mapped buckets only
                    accessibleBucketIds = await _context.EmployeeBucketMappings
                        .Where(eb => eb.EmployeeId == loggedInEmployee.Id)
                        .Select(eb => eb.BucketId)
                        .ToListAsync();

                    _logger.LogDebug("Manager/User access granted. Found {Count} mapped buckets for EmployeeId: {EmployeeId}.",
                        accessibleBucketIds.Count, loggedInEmployee.Id);
                }
                else
                {
                    _logger.LogWarning("No permissions found for EmployeeId: {EmployeeId}. Returning empty filters.", loggedInEmployee.Id);
                }

                // Step 2: Fetch Contact IDs from ContactBucketMappings
                var contactIds = await _context.ContactBucketMappings
                    .Where(cb => accessibleBucketIds != null && accessibleBucketIds.Contains(cb.BucketId))
                    .Select(cb => cb.ContactId)
                    .Distinct() // ensures no duplicate contact IDs
                    .ToListAsync();

                _logger.LogInfo("Fetched {Count} contact Ids from accessible buckets.", contactIds.Count);

                // Step 3: Fetch Contacts related to Contact Notes for those contactIds
                var contacts = await _context.ContactNotes
                    .AsNoTracking() // no need to track since it’s for read-only filters
                    .Include(cn => cn.Contact)
                        .ThenInclude(c => c!.CreatedBy)
                    .Where(cn =>
                        contactIds.Contains(cn.ContactId) &&
                        cn.Contact != null &&
                        cn.Contact.CreatedBy != null &&
                        cn.TenantId == tenantId)
                    .Select(cn => cn.Contact!)
                    .Distinct() // avoid duplicate contacts from multiple notes
                    .ToListAsync();

                _logger.LogInfo("Fetched {Count} unique contacts with notes.", contacts.Count);

                // Step 4: Build organization filters
                var organizations = contacts
                    .Where(c => !string.IsNullOrEmpty(c.Organization)) // filter out null/empty orgs
                    .Select(c => new
                    {
                        Id = c.Organization,   // Using organization string as unique identifier
                        Name = c.Organization
                    })
                    .Distinct()
                    .OrderBy(o => o.Name)
                    .ToList();

                _logger.LogInfo("Extracted {Count} unique organizations from contacts.", organizations.Count);

                // Step 5: Build CreatedBy filters (employees who created the contacts)
                var createdBy = contacts
                    .Select(c => new
                    {
                        Id = c.CreatedBy!.Id,
                        Name = $"{c.CreatedBy.FirstName} {c.CreatedBy.LastName}".Trim()
                    })
                    .Distinct()
                    .OrderBy(e => e.Name)
                    .ToList();

                _logger.LogInfo("Extracted {Count} unique CreatedBy employees from contacts.", createdBy.Count);

                // Step 6: Build response
                var response = new
                {
                    CreatedBy = createdBy,
                    Organizations = organizations
                };

                _logger.LogInfo("Successfully returning Contact Notes filters for TenantId: {TenantId}, EmployeeId: {EmployeeId}",
                    tenantId, loggedInEmployee.Id);

                return ApiResponse<object>.SuccessResponse(response, "Filters for contact notes fetched successfully", 200);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex,
                    "Error occurred while fetching Contact Notes filters for TenantId: {TenantId}, EmployeeId: {EmployeeId}",
                    tenantId, loggedInEmployee.Id);

                return ApiResponse<object>.ErrorResponse("An error occurred while fetching filters", 500);
            }
        }


        /// <summary>
        /// Creates a note for a given contact under the specified tenant.
        /// Ensures that the contact exists and belongs to the tenant before adding the note.
        /// </summary>
        /// <param name="noteDto">The DTO containing the note details.</param>
        /// <param name="tenantId">The tenant identifier to which the contact belongs.</param>
        /// <param name="loggedInEmployee">The logged-in employee attempting the action.</param>
        /// <returns>ApiResponse containing the created note details or error information.</returns>
        public async Task<ApiResponse<object>> CreateContactNoteAsync(CreateContactNoteDto noteDto, Guid tenantId, Employee loggedInEmployee)
        {
            // Validate request payload
            if (noteDto == null)
            {
                _logger.LogWarning(
                    "Employee {EmployeeId} attempted to create a note with an empty payload.",
                    loggedInEmployee.Id);

                return ApiResponse<object>.ErrorResponse("Empty payload.", "Request body cannot be null.", 400);
            }

            try
            {
                var (hasAdminPermission, hasManagerPermission, hasUserPermission) = await CheckPermissionsAsync(loggedInEmployee.Id);

                var bucketIds = await _context.ContactBucketMappings.AsNoTracking().Where(cb => cb.ContactId == noteDto.ContactId).Select(cb => cb.BucketId).ToListAsync();
                var hasContactAccess = await _context.EmployeeBucketMappings.AsNoTracking().AnyAsync(eb => bucketIds.Contains(eb.BucketId) && eb.EmployeeId == loggedInEmployee.Id);
                if (!hasAdminPermission && !hasContactAccess)
                {
                    _logger.LogWarning("Employee {EmployeeId} does not have permission to delete contact {ContactId}",
                         loggedInEmployee.Id, noteDto.ContactId);
                    return ApiResponse<object>.ErrorResponse("Unauthorized", "You do not have permission", 403);
                }
                // Check if the contact exists and is active for this tenant
                Contact? contact = await _context.Contacts
                    .AsNoTracking() // optimization for read-only query
                    .FirstOrDefaultAsync(c => c.Id == noteDto.ContactId && c.IsActive && c.TenantId == tenantId);

                if (contact == null)
                {
                    _logger.LogWarning(
                        "Employee {EmployeeId} attempted to add a note to Contact {ContactId}, but it was not found for tenant {TenantId}.",
                        loggedInEmployee.Id, noteDto.ContactId, tenantId);

                    return ApiResponse<object>.ErrorResponse("Contact not found.", "The specified contact does not exist.", 404);
                }

                // Map DTO -> Entity using AutoMapper
                ContactNote note = _mapper.Map<ContactNote>(noteDto);
                note.CreatedById = loggedInEmployee.Id;
                note.TenantId = tenantId;

                // Save new note
                await _context.ContactNotes.AddAsync(note);
                await _context.SaveChangesAsync();

                // Map Entity -> ViewModel
                ContactNoteVM noteVM = _mapper.Map<ContactNoteVM>(note);

                _logger.LogInfo(
                    "Employee {EmployeeId} successfully added a note (NoteId: {NoteId}) to Contact {ContactId} for Tenant {TenantId}.",
                    loggedInEmployee.Id, note.Id, contact.Id, tenantId);

                using var scope = _serviceScopeFactory.CreateScope();
                var _firebase = scope.ServiceProvider.GetRequiredService<IFirebaseService>();

                _ = Task.Run(async () =>
                {
                    // --- Push Notification Section ---
                    // This section attempts to send a test push notification to the user's device.
                    // It's designed to fail gracefully and handle invalid Firebase Cloud Messaging (FCM) tokens.

                    var name = $"{loggedInEmployee.FirstName} {loggedInEmployee.LastName}";

                    var notification = new Notification
                    {
                        Title = $"New note added at Contact - \"{contact.Name}\"",
                        Body = $"New note added at Contact \"{contact.Name}\" by {name} in your bucket"
                    };

                    await _firebase.SendContactNoteAsync(contact.Id, bucketIds, notification, tenantId);

                });

                return ApiResponse<object>.SuccessResponse(noteVM, "Note added successfully.", 200);
            }
            catch (Exception ex)
            {
                // Log unexpected errors to troubleshoot
                _logger.LogError(
                    ex,
                    "Unexpected error occurred while Employee {EmployeeId} attempted to add a note for Contact {ContactId} in Tenant {TenantId}.",
                    loggedInEmployee.Id, noteDto.ContactId, tenantId);

                return ApiResponse<object>.ErrorResponse("An unexpected error occurred.", ex.Message, 500);
            }
        }

        /// <summary>
        /// Updates an existing contact note and logs changes 
        /// both in relational DB (SQL) and update logs (possibly MongoDB).
        /// </summary>
        /// <param name="id">The note ID that needs to be updated.</param>
        /// <param name="noteDto">DTO with updated note data.</param>
        /// <returns>Standardized ApiResponse with updated note or error details.</returns>
        public async Task<ApiResponse<object>> UpdateContactNoteAsync(Guid id, UpdateContactNoteDto noteDto, Guid tenantId, Employee loggedInEmployee)
        {
            // Validation: check null payload or mismatched ID
            if (noteDto == null || id != noteDto.Id)
            {
                _logger.LogWarning("Employee {EmployeeId} sent invalid or null payload. RouteId: {RouteId}, PayloadId: {PayloadId}",
                    loggedInEmployee.Id, id, noteDto?.Id ?? Guid.Empty);

                return ApiResponse<object>.ErrorResponse("Invalid or empty payload", "Invalid or empty payload", 400);
            }
            var (hasAdminPermission, hasManagerPermission, hasUserPermission) = await CheckPermissionsAsync(loggedInEmployee.Id);

            var bucketIds = await _context.ContactBucketMappings.Where(cb => cb.ContactId == noteDto.ContactId).Select(cb => cb.BucketId).ToListAsync();
            var hasContactAccess = await _context.EmployeeBucketMappings.AnyAsync(eb => bucketIds.Contains(eb.BucketId) && eb.EmployeeId == loggedInEmployee.Id);
            if (!hasAdminPermission && hasContactAccess)
            {
                _logger.LogWarning("Employee {EmployeeId} does not have permission to delete contact {ContactId}",
                     loggedInEmployee.Id, noteDto.ContactId);
                return ApiResponse<object>.ErrorResponse("Unauthorized", "You do not have permission", 403);
            }

            // Check if the contact belongs to this tenant
            Contact? contact = await _context.Contacts
                .AsNoTracking()
                .FirstOrDefaultAsync(c => c.Id == noteDto.ContactId && c.TenantId == tenantId);

            if (contact == null)
            {
                _logger.LogWarning("Employee {EmployeeId} attempted to update note {NoteId} for Contact {ContactId}, but the contact was not found in Tenant {TenantId}.",
                    loggedInEmployee.Id, noteDto.Id, noteDto.ContactId, tenantId);

                return ApiResponse<object>.ErrorResponse("Contact not found", "Contact not found", 404);
            }

            // Fetch the contact note to be updated
            ContactNote? contactNote = await _context.ContactNotes
                .Include(cn => cn.Createdby)
                    .ThenInclude(e => e!.JobRole)
                .Include(cn => cn.Contact)
                .FirstOrDefaultAsync(n =>
                    n.Id == noteDto.Id &&
                    n.ContactId == contact.Id &&
                    n.IsActive);

            if (contactNote == null)
            {
                _logger.LogWarning("Employee {EmployeeId} attempted to update Note {NoteId} for Contact {ContactId}, but the note was not found or inactive.",
                    loggedInEmployee.Id, noteDto.Id, noteDto.ContactId);

                return ApiResponse<object>.ErrorResponse("Note not found", "Note not found", 404);
            }

            // Capture old state for change-log before updating
            var oldObject = _updateLogsHelper.EntityToBsonDocument(contactNote);

            // Apply updates
            contactNote.Note = noteDto.Note;
            contactNote.UpdatedById = loggedInEmployee.Id;
            contactNote.UpdatedAt = DateTime.UtcNow;

            // Save change log into relational logs
            _context.DirectoryUpdateLogs.Add(new DirectoryUpdateLog
            {
                RefereanceId = id,
                UpdatedById = loggedInEmployee.Id,
                UpdateAt = DateTime.UtcNow
            });

            // Wrap in try-catch for robustness
            try
            {
                await _context.SaveChangesAsync();

                // Map to ViewModel for output
                ContactNoteVM noteVM = _mapper.Map<ContactNoteVM>(contactNote);
                noteVM.UpdatedAt = contactNote.UpdatedAt;
                noteVM.UpdatedBy = _mapper.Map<BasicEmployeeVM>(loggedInEmployee);

                // Push audit log asynchronously (Mongo / NoSQL Logs)
                await _updateLogsHelper.PushToUpdateLogsAsync(new UpdateLogsObject
                {
                    EntityId = contactNote.Id.ToString(),
                    UpdatedById = loggedInEmployee.Id.ToString(),
                    OldObject = oldObject,
                    UpdatedAt = DateTime.UtcNow
                }, contactNoteCollection);

                // Success log
                _logger.LogInfo("Employee {EmployeeId} successfully updated Note {NoteId} for Contact {ContactId} at {UpdatedAt}",
                    loggedInEmployee.Id, noteVM.Id, contact.Id, noteVM.UpdatedAt);

                using var scope = _serviceScopeFactory.CreateScope();
                var _firebase = scope.ServiceProvider.GetRequiredService<IFirebaseService>();

                _ = Task.Run(async () =>
                {
                    // --- Push Notification Section ---
                    // This section attempts to send a test push notification to the user's device.
                    // It's designed to fail gracefully and handle invalid Firebase Cloud Messaging (FCM) tokens.

                    var name = $"{loggedInEmployee.FirstName} {loggedInEmployee.LastName}";

                    var notification = new Notification
                    {
                        Title = $"Note updated at Contact - \"{contact.Name}\"",
                        Body = $"Note updated at Contact \"{contact.Name}\" by {name} in your bucket"
                    };

                    await _firebase.SendContactNoteAsync(contact.Id, bucketIds, notification, tenantId);

                });

                return ApiResponse<object>.SuccessResponse(noteVM, "Note updated successfully", 200);
            }
            catch (DbUpdateException ex)
            {
                _logger.LogError(ex, "Database Exception occurred while updating Note {NoteId} for Contact {ContactId} by Employee {EmployeeId}",
                    noteDto.Id, noteDto.ContactId, loggedInEmployee.Id);

                return ApiResponse<object>.ErrorResponse("Failed to update note", "An unexpected error occurred while saving note.", 500);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Exception occurred while updating Note {NoteId} for Contact {ContactId} by Employee {EmployeeId}",
                    noteDto.Id, noteDto.ContactId, loggedInEmployee.Id);

                return ApiResponse<object>.ErrorResponse("Failed to update note", "An unexpected error occurred while saving note.", 500);
            }
        }

        /// <summary>
        /// Soft deletes (or restores) a contact note by updating its active status.
        /// Also pushes an update log entry in SQL and Mongo (audit trail).
        /// </summary>
        /// <param name="id">ID of the contact note to delete/restore.</param>
        /// <param name="active">Flag to set note as active or inactive.</param>
        /// <param name="tenantId">Tenant identifier of the logged-in user.</param>
        /// <param name="loggedInEmployee">The employee performing this action.</param>
        /// <returns>ApiResponse with success or error details.</returns>
        public async Task<ApiResponse<object>> DeleteContactNoteAsync(Guid id, bool active, Guid tenantId, Employee loggedInEmployee)
        {
            // Lookup note within the tenant
            ContactNote? note = await _context.ContactNotes
                .FirstOrDefaultAsync(n => n.Id == id && n.TenantId == tenantId);

            if (note == null)
            {
                // Log missing resource
                _logger.LogWarning("Employee {EmployeeId} attempted to delete Note {NoteId}, but it was not found in Tenant {TenantId}",
                    loggedInEmployee.Id, id, tenantId);

                return ApiResponse<object>.ErrorResponse("Note not found", "Note not found", 404);
            }

            var (hasAdminPermission, hasManagerPermission, hasUserPermission) = await CheckPermissionsAsync(loggedInEmployee.Id);

            var bucketIds = await _context.ContactBucketMappings.AsNoTracking().Where(cb => cb.ContactId == note.ContactId).Select(cb => cb.BucketId).ToListAsync();
            var hasContactAccess = await _context.EmployeeBucketMappings.AsNoTracking().AnyAsync(eb => bucketIds.Contains(eb.BucketId) && eb.EmployeeId == loggedInEmployee.Id);
            if (!hasAdminPermission && !hasContactAccess)
            {
                _logger.LogWarning("Employee {EmployeeId} does not have permission to delete contact {ContactId}",
                     loggedInEmployee.Id, note.ContactId);
                return ApiResponse<object>.ErrorResponse("Unauthorized", "You do not have permission", 403);
            }

            // Check if the contact belongs to this tenant
            Contact? contact = await _context.Contacts
                .AsNoTracking()
                .FirstOrDefaultAsync(c => c.Id == note.ContactId && c.TenantId == tenantId);

            if (contact == null)
            {
                _logger.LogWarning("Employee {EmployeeId} attempted to update note {NoteId} for Contact {ContactId}, but the contact was not found in Tenant {TenantId}.",
                    loggedInEmployee.Id, note.Id, note.ContactId, tenantId);

                return ApiResponse<object>.ErrorResponse("Contact not found", "Contact not found", 404);
            }

            // Capture old state for audit logging
            var oldObject = _updateLogsHelper.EntityToBsonDocument(note);

            // Update note metadata
            var currentTime = DateTime.UtcNow;
            note.IsActive = active; // soft delete (false) or restore (true)
            note.UpdatedById = loggedInEmployee.Id;
            note.UpdatedAt = currentTime;

            // Add relational update log entry
            _context.DirectoryUpdateLogs.Add(new DirectoryUpdateLog
            {
                RefereanceId = id,
                UpdatedById = loggedInEmployee.Id,
                UpdateAt = currentTime
            });

            try
            {
                // Save SQL changes
                await _context.SaveChangesAsync();

                // Push audit log (Mongo / NoSQL)
                await _updateLogsHelper.PushToUpdateLogsAsync(new UpdateLogsObject
                {
                    EntityId = note.Id.ToString(),
                    UpdatedById = loggedInEmployee.Id.ToString(),
                    OldObject = oldObject,
                    UpdatedAt = currentTime
                }, contactNoteCollection);

                //  Log success — distinguish delete vs restore
                if (!active)
                {
                    _logger.LogInfo("Employee {EmployeeId} soft deleted Note {NoteId} at {Timestamp}",
                        loggedInEmployee.Id, id, currentTime);
                }
                else
                {
                    _logger.LogInfo("Employee {EmployeeId} restored Note {NoteId} at {Timestamp}",
                        loggedInEmployee.Id, id, currentTime);
                }

                using var scope = _serviceScopeFactory.CreateScope();
                var _firebase = scope.ServiceProvider.GetRequiredService<IFirebaseService>();

                _ = Task.Run(async () =>
                {
                    // --- Push Notification Section ---
                    // This section attempts to send a test push notification to the user's device.
                    // It's designed to fail gracefully and handle invalid Firebase Cloud Messaging (FCM) tokens.

                    var name = $"{loggedInEmployee.FirstName} {loggedInEmployee.LastName}";

                    Notification notification;
                    if (active)
                    {
                        notification = new Notification
                        {
                            Title = $"Note restored at Contact - \"{contact.Name}\"",
                            Body = $"Note restored at Contact \"{contact.Name}\" by {name} in your bucket"
                        };
                    }
                    else
                    {
                        notification = new Notification
                        {
                            Title = $"Note deleted at Contact - \"{contact.Name}\"",
                            Body = $"Note deleted at Contact \"{contact.Name}\" by {name} in your bucket"
                        };
                    }
                    await _firebase.SendContactNoteAsync(contact.Id, bucketIds, notification, tenantId);

                });

                return ApiResponse<object>.SuccessResponse(new { },
                    active ? "Note restored successfully" : "Note deleted successfully",
                    200);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error while updating Note {NoteId} (delete/restore) in Tenant {TenantId} by Employee {EmployeeId}",
                    id, tenantId, loggedInEmployee.Id);

                return ApiResponse<object>.ErrorResponse("Failed to delete note",
                    "An unexpected error occurred while deleting/restoring the note.",
                    500);
            }
        }

        #endregion

        #region =================================================================== Bucket APIs ===================================================================
        public async Task<ApiResponse<object>> GetBucketListAsync(Guid tenantId, Employee loggedInEmployee)
        {
            _logger.LogInfo("Started fetching bucket list for Employee {EmployeeId} in Tenant {TenantId}", loggedInEmployee.Id, tenantId);

            // Check permissions early
            var (hasAdminPermission, hasManagerPermission, hasUserPermission) = await CheckPermissionsAsync(loggedInEmployee.Id);

            if (!hasAdminPermission && !hasManagerPermission && !hasUserPermission)
            {
                _logger.LogWarning("Employee {EmployeeId} attempted to access buckets without permission", loggedInEmployee.Id);
                return ApiResponse<object>.ErrorResponse("You don't have permission", "You don't have permission", 403);
            }

            List<Bucket> bucketList;
            List<Guid> bucketIds;

            if (hasAdminPermission)
            {
                // Admin gets all buckets for the tenant
                bucketList = await _context.Buckets
                    .Include(b => b.CreatedBy)
                        .ThenInclude(e => e!.JobRole)
                    .Where(b => b.TenantId == tenantId)
                    .ToListAsync();

                bucketIds = bucketList.Select(b => b.Id).ToList();
            }
            else
            {
                // Manager or user: fetch employee bucket mappings and buckets accordingly

                var employeeBuckets = await _context.EmployeeBucketMappings
                    .Where(b => b.EmployeeId == loggedInEmployee.Id)
                    .ToListAsync();

                bucketIds = employeeBuckets.Select(b => b.BucketId).ToList();

                bucketList = await _context.Buckets
                    .Include(b => b.CreatedBy)
                        .ThenInclude(e => e!.JobRole)
                    .Where(b => bucketIds.Contains(b.Id) || b.CreatedByID == loggedInEmployee.Id)
                    .ToListAsync();

                bucketIds = bucketList.Select(b => b.Id).ToList();
            }

            if (!bucketList.Any())
            {
                _logger.LogInfo("No buckets found for Employee {EmployeeId}", loggedInEmployee.Id);
                return ApiResponse<object>.SuccessResponse(new List<AssignBucketVM>(), "No buckets found", 200);
            }

            // Fetch related data in parallel using Task.Run with separate contexts to avoid concurrency issues
            var employeeBucketMappingsTask = Task.Run(async () =>
            {
                using var context = _dbContextFactory.CreateDbContext();
                return await context.EmployeeBucketMappings
                    .Where(b => bucketIds.Contains(b.BucketId))
                    .ToListAsync();
            });

            var contactBucketMappingsTask = Task.Run(async () =>
            {
                using var context = _dbContextFactory.CreateDbContext();
                return await context.ContactBucketMappings
                    .Where(cb => bucketIds.Contains(cb.BucketId))
                    .ToListAsync();
            });

            await Task.WhenAll(employeeBucketMappingsTask, contactBucketMappingsTask);

            var employeeBucketMappings = employeeBucketMappingsTask.Result;
            var contactBucketMappings = contactBucketMappingsTask.Result;

            var bucketVMs = new List<AssignBucketVM>();

            // Prepare view models for each bucket
            foreach (var bucket in bucketList)
            {
                var mappedEmployees = employeeBucketMappings
                    .Where(eb => eb.BucketId == bucket.Id)
                    .Select(eb => eb.EmployeeId)
                    .ToList();

                if (bucket.CreatedBy != null)
                {
                    mappedEmployees.Add(bucket.CreatedBy.Id);
                }

                var contactCount = contactBucketMappings.Count(cb => cb.BucketId == bucket.Id);

                var bucketVM = bucket.ToAssignBucketVMFromBucket();
                bucketVM.EmployeeIds = mappedEmployees.Distinct().ToList();
                bucketVM.NumberOfContacts = contactCount;

                bucketVMs.Add(bucketVM);
            }

            _logger.LogInfo("Fetched {BucketCount} buckets for Employee {EmployeeId} successfully", bucketVMs.Count, loggedInEmployee.Id);


            return ApiResponse<object>.SuccessResponse(bucketVMs, $"{bucketVMs.Count} buckets fetched successfully", 200);
        }
        public async Task<ApiResponse<object>> CreateBucketAsync(CreateBucketDto bucketDto, Guid tenantId, Employee loggedInEmployee)
        {
            if (bucketDto == null)
            {
                _logger.LogWarning("Employee with ID {LoggedInEmployeeId} sent empty payload", loggedInEmployee.Id);
                return ApiResponse<object>.ErrorResponse("User sent empty Payload", "User sent empty Payload", 400);
            }

            try
            {
                // Check permissions for the logged-in employee in parallel
                var permissionTask = CheckPermissionsAsync(loggedInEmployee.Id);

                // Check if a bucket with the same name already exists (case insensitive)
                var existingBucketTask = _context.Buckets.FirstOrDefaultAsync(b => b.Name.ToLower() == bucketDto.Name.ToLower());

                await Task.WhenAll(permissionTask, existingBucketTask);

                var (hasAdminPermission, hasManagerPermission, hasUserPermission) = permissionTask.Result;
                var existingBucket = existingBucketTask.Result;

                // If the user does not have any of the required permissions, deny access
                if (!hasAdminPermission && !hasManagerPermission && !hasUserPermission)
                {
                    _logger.LogWarning("Employee {EmployeeId} attempted to create bucket without permission", loggedInEmployee.Id);
                    return ApiResponse<object>.ErrorResponse("You don't have permission", "You don't have permission", 403);
                }

                // If bucket with the same name exists, return conflict response
                if (existingBucket != null)
                {
                    _logger.LogWarning("Employee {EmployeeId} attempted to create an existing bucket with name '{BucketName}'", loggedInEmployee.Id, bucketDto.Name);
                    return ApiResponse<object>.ErrorResponse("Bucket already exists", "Bucket already exists", 409);
                }

                // Create new bucket entity
                var newBucket = new Bucket
                {
                    Name = bucketDto.Name,
                    Description = bucketDto.Description,
                    CreatedAt = DateTime.UtcNow,
                    CreatedByID = loggedInEmployee.Id,
                    TenantId = tenantId
                };

                // Add bucket to context
                _context.Buckets.Add(newBucket);

                // Create mapping between employee and bucket
                var employeeBucketMapping = new EmployeeBucketMapping
                {
                    EmployeeId = loggedInEmployee.Id,
                    BucketId = newBucket.Id
                };

                // Add employee-bucket mapping to context
                _context.EmployeeBucketMappings.Add(employeeBucketMapping);

                // Save changes to DB
                await _context.SaveChangesAsync();

                // Load the newly created bucket including creator info for response
                var createdBucket = await _context.Buckets
                    .Include(b => b.CreatedBy)
                        .ThenInclude(e => e!.JobRole)
                    .FirstOrDefaultAsync(b => b.Id == newBucket.Id);

                var bucketVM = _mapper.Map<BucketVM>(createdBucket);

                _logger.LogInfo("Employee {EmployeeId} successfully created bucket {BucketId}", loggedInEmployee.Id, newBucket.Id);

                using var scope = _serviceScopeFactory.CreateScope();
                var _firebase = scope.ServiceProvider.GetRequiredService<IFirebaseService>();

                _ = Task.Run(async () =>
                {
                    // --- Push Notification Section ---
                    // This section attempts to send a test push notification to the user's device.
                    // It's designed to fail gracefully and handle invalid Firebase Cloud Messaging (FCM) tokens.

                    var name = $"{loggedInEmployee.FirstName} {loggedInEmployee.LastName}";

                    var notification = new Notification
                    {
                        Title = "New Bucket created",
                        Body = $"New Bucket created \"{newBucket.Name}\" by {name}"
                    };

                    await _firebase.SendBucketAsync(newBucket.Id, notification, tenantId);

                });

                return ApiResponse<object>.SuccessResponse(bucketVM, "Bucket created successfully", 200);
            }
            catch (Exception ex)
            {
                // Log unexpected exceptions
                _logger.LogError(ex, "Error occurred while employee {EmployeeId} was creating a bucket", loggedInEmployee.Id);
                return ApiResponse<object>.ErrorResponse("Internal server error", "Internal server error", 500);
            }
        }
        public async Task<ApiResponse<object>> UpdateBucketAsync(Guid id, UpdateBucketDto bucketDto, Guid tenantId, Employee loggedInEmployee)
        {
            if (bucketDto == null || id != bucketDto.Id)
            {
                _logger.LogWarning("Employee with ID {LoggedInEmployeeId} sent invalid or empty payload for bucket update.", loggedInEmployee.Id);
                return ApiResponse<object>.ErrorResponse("Invalid or empty payload", "Invalid or empty payload", 400);
            }

            try
            {
                // Check user permissions in parallel
                var permissionTask = CheckPermissionsAsync(loggedInEmployee.Id);

                // Use IDbContextFactory to create separate contexts for parallel DB calls
                using var employeeBucketContext = _dbContextFactory.CreateDbContext();
                using var bucketContext = _dbContextFactory.CreateDbContext();
                using var contactBucketContext = _dbContextFactory.CreateDbContext();

                // Load employee buckets where BucketId == id in parallel
                var employeeBucketsTask = employeeBucketContext.EmployeeBucketMappings
                    .Where(eb => eb.EmployeeId == loggedInEmployee.Id)
                    .ToListAsync();

                // Load the bucket with CreatedBy for given id and tenantId
                var bucketTask = bucketContext.Buckets
                    .Include(b => b.CreatedBy)
                        .ThenInclude(e => e!.JobRole)
                    .AsNoTracking()
                    .FirstOrDefaultAsync(b => b.Id == bucketDto.Id && b.TenantId == tenantId);

                // Await all parallel tasks
                await Task.WhenAll(permissionTask, employeeBucketsTask, bucketTask);

                var (hasAdminPermission, hasManagerPermission, hasUserPermission) = permissionTask.Result;
                var employeeBuckets = employeeBucketsTask.Result;
                var bucket = bucketTask.Result;

                // Validate bucket exists
                if (bucket == null)
                {
                    _logger.LogWarning("Employee ID {LoggedInEmployeeId} attempted to update bucket {BucketId}, but it was not found in database.", loggedInEmployee.Id, bucketDto.Id);
                    return ApiResponse<object>.ErrorResponse("Bucket not found", "Bucket not found", 404);
                }

                // Determine if employee has access to update the bucket
                var bucketIdsForEmployee = employeeBuckets.Select(eb => eb.BucketId).ToList();

                bool hasAccess = false;
                if (hasAdminPermission)
                {
                    hasAccess = true;
                }
                else if (hasManagerPermission && bucketIdsForEmployee.Contains(id))
                {
                    hasAccess = true;
                }
                else if (hasUserPermission && bucket.CreatedByID == loggedInEmployee.Id)
                {
                    hasAccess = true;
                }

                if (!hasAccess)
                {
                    _logger.LogWarning("Employee ID {LoggedInEmployeeId} attempted to update bucket {BucketId} without sufficient permissions.", loggedInEmployee.Id, bucket.Id);
                    return ApiResponse<object>.ErrorResponse("You don't have permission to access this bucket", "You don't have permission to access this bucket", 403);
                }

                var bucketObject = _updateLogsHelper.EntityToBsonDocument(bucket);

                // Update bucket properties safely
                bucket.Name = bucketDto.Name ?? string.Empty;
                bucket.Description = bucketDto.Description ?? string.Empty;

                // Log the update attempt in directory update logs 
                _context.DirectoryUpdateLogs.Add(new DirectoryUpdateLog
                {
                    RefereanceId = bucketDto.Id,
                    UpdatedById = loggedInEmployee.Id,
                    UpdateAt = DateTime.UtcNow
                });

                // Save changes to bucket and logs
                _context.Buckets.Update(bucket);
                await _context.SaveChangesAsync();

                // Now load contacts related to the bucket using a separate context for parallelism
                var contactBuckets = await contactBucketContext.ContactBucketMappings
                    .Where(cb => cb.BucketId == bucket.Id)
                    .ToListAsync();

                // Prepare view model to return
                AssignBucketVM bucketVM = _mapper.Map<AssignBucketVM>(bucket);
                bucketVM.EmployeeIds = employeeBuckets.Where(eb => eb.BucketId == bucket.Id).Select(eb => eb.EmployeeId).ToList();
                bucketVM.NumberOfContacts = contactBuckets.Count;

                await _updateLogsHelper.PushToUpdateLogsAsync(new UpdateLogsObject
                {
                    EntityId = bucket.Id.ToString(),
                    UpdatedById = loggedInEmployee.Id.ToString(),
                    OldObject = bucketObject,
                    UpdatedAt = DateTime.UtcNow
                }, bucketCollection);

                _logger.LogInfo("Employee ID {LoggedInEmployeeId} successfully updated bucket ID {BucketId}.", loggedInEmployee.Id, bucket.Id);

                using var scope = _serviceScopeFactory.CreateScope();
                var _firebase = scope.ServiceProvider.GetRequiredService<IFirebaseService>();

                _ = Task.Run(async () =>
                {
                    // --- Push Notification Section ---
                    // This section attempts to send a test push notification to the user's device.
                    // It's designed to fail gracefully and handle invalid Firebase Cloud Messaging (FCM) tokens.

                    var name = $"{loggedInEmployee.FirstName} {loggedInEmployee.LastName}";

                    var notification = new Notification
                    {
                        Title = $"Bucket updated - \"{bucket.Name}\"",
                        Body = $"Bucket updated \"{bucket.Name}\" by {name}"
                    };

                    await _firebase.SendBucketAsync(bucket.Id, notification, tenantId);

                });

                return ApiResponse<object>.SuccessResponse(bucketVM, "Bucket updated successfully", 200);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "An error occurred while employee ID {LoggedInEmployeeId} attempted to update bucket ID {BucketId}.", loggedInEmployee.Id, id);
                return ApiResponse<object>.ErrorResponse("An unexpected error occurred. Please try again later.", "Internal server error", 500);
            }
        }
        public async Task<ApiResponse<object>> AssignBucketAsync(Guid bucketId, List<AssignBucketDto> assignBuckets, Guid tenantId, Employee loggedInEmployee)
        {
            // Validate input payload
            if (assignBuckets == null || bucketId == Guid.Empty)
            {
                _logger.LogWarning("Employee with ID {EmployeeId} sent empty or invalid payload.", loggedInEmployee.Id);
                return ApiResponse<object>.ErrorResponse("User sent empty or invalid payload", "User sent empty or invalid payload", 400);
            }

            // Check permissions of the logged-in employee
            var (hasAdminPermission, hasManagerPermission, hasUserPermission) = await CheckPermissionsAsync(loggedInEmployee.Id);


            // Load the bucket with related CreatedBy and validate tenant
            var bucket = await _context.Buckets
                .Include(b => b.CreatedBy)
                    .ThenInclude(e => e!.JobRole)
                .FirstOrDefaultAsync(b => b.Id == bucketId && b.TenantId == tenantId);

            if (bucket == null)
            {
                _logger.LogWarning("Employee ID {EmployeeId} attempted to update bucket {BucketId} but it was not found.", loggedInEmployee.Id, bucketId);
                return ApiResponse<object>.ErrorResponse("Bucket not found", "Bucket not found", 404);
            }

            // Load EmployeeBucketMappings related to the bucket
            var employeeBuckets = await _context.EmployeeBucketMappings
                                               .Where(eb => eb.BucketId == bucketId)
                                               .ToListAsync();

            var employeeBucketIds = employeeBuckets.Select(eb => eb.EmployeeId).ToHashSet();

            // Check access permissions to the bucket
            bool hasAccess = false;
            if (hasAdminPermission)
            {
                hasAccess = true;
            }
            else if (hasManagerPermission && employeeBucketIds.Contains(loggedInEmployee.Id))
            {
                hasAccess = true;
            }
            else if (hasUserPermission && bucket.CreatedByID == loggedInEmployee.Id)
            {
                hasAccess = true;
            }

            if (!hasAccess)
            {
                _logger.LogWarning("Employee {EmployeeId} attempted to access bucket {BucketId} without permission.", loggedInEmployee.Id, bucketId);
                return ApiResponse<object>.ErrorResponse("You don't have permission to access this bucket", "You don't have permission to access this bucket", 403);
            }

            // Load active employees tenant-wide
            var activeEmployeeIds = await _context.Employees
                                                 .Where(e => e.TenantId == tenantId && e.IsActive)
                                                 .Select(e => e.Id)
                                                 .ToListAsync();

            int assignedEmployeesCount = 0;
            int removedEmployeesCount = 0;

            List<Guid> assignedEmployeeIds = new List<Guid>();
            List<Guid> removedEmployeeIds = new List<Guid>();

            // Process each assignment request
            foreach (var assignBucket in assignBuckets)
            {
                if (!activeEmployeeIds.Contains(assignBucket.EmployeeId))
                {
                    // Skip employee IDs that are not active or not in tenant
                    _logger.LogWarning("Skipping inactive or non-tenant employee ID {EmployeeId} in assignment.", assignBucket.EmployeeId);
                    continue;
                }

                if (assignBucket.IsActive)
                {
                    // Add mapping if not already exists
                    if (!employeeBucketIds.Contains(assignBucket.EmployeeId))
                    {
                        var newMapping = new EmployeeBucketMapping
                        {
                            EmployeeId = assignBucket.EmployeeId,
                            BucketId = bucketId
                        };
                        _context.EmployeeBucketMappings.Add(newMapping);
                        assignedEmployeesCount++;
                        assignedEmployeeIds.Add(assignBucket.EmployeeId);
                    }
                }
                else
                {
                    // Remove mapping if it exists
                    var existingMapping = employeeBuckets.FirstOrDefault(eb => eb.EmployeeId == assignBucket.EmployeeId);
                    if (existingMapping != null && bucket.CreatedByID != assignBucket.EmployeeId)
                    {
                        removedEmployeeIds.Add(existingMapping.EmployeeId);
                        _context.EmployeeBucketMappings.Remove(existingMapping);
                        removedEmployeesCount++;
                    }
                }
            }

            // Add directory update log
            _context.DirectoryUpdateLogs.Add(new DirectoryUpdateLog
            {
                RefereanceId = bucketId,
                UpdatedById = loggedInEmployee.Id,
                UpdateAt = DateTime.UtcNow
            });

            // Save changes with error handling
            try
            {
                await _context.SaveChangesAsync();
            }
            catch (DbUpdateException ex)
            {
                _logger.LogError(ex, "Failed to save changes while assigning bucket {BucketId} by employee {EmployeeId}.", bucketId, loggedInEmployee.Id);
                return ApiResponse<object>.ErrorResponse("Failed to update bucket assignments", "An error occurred while updating bucket assignments", 500);
            }

            // Reload mappings and contacts for the updated bucket
            var employeeBucketMappingTask = Task.Run(async () =>
            {
                using (var context = _dbContextFactory.CreateDbContext())
                {
                    return await context.EmployeeBucketMappings.Where(eb => eb.BucketId == bucket.Id).ToListAsync();
                }
            });
            var contactBucketMappingTask = Task.Run(async () =>
            {
                using (var context = _dbContextFactory.CreateDbContext())
                {
                    return await context.ContactBucketMappings.Where(eb => eb.BucketId == bucket.Id).ToListAsync();
                }
            });
            await Task.WhenAll(employeeBucketMappingTask, contactBucketMappingTask);
            var employeeBucketMappings = employeeBucketMappingTask.Result;
            var contactBucketMappings = contactBucketMappingTask.Result;

            // Prepare view model response
            var bucketVm = _mapper.Map<AssignBucketVM>(bucket);
            bucketVm.EmployeeIds = employeeBucketMappings.Select(eb => eb.EmployeeId).ToList();
            bucketVm.NumberOfContacts = contactBucketMappings.Count;

            // Log assignment and removal actions
            if (assignedEmployeesCount > 0)
            {
                _logger.LogInfo("Employee {EmployeeId} assigned bucket {BucketId} to {Count} employees.", loggedInEmployee.Id, bucketId, assignedEmployeesCount);
            }
            if (removedEmployeesCount > 0)
            {
                _logger.LogInfo("Employee {EmployeeId} removed {Count} employees from bucket {BucketId}.", loggedInEmployee.Id, removedEmployeesCount, bucketId);
            }
            using var scope = _serviceScopeFactory.CreateScope();
            var _firebase = scope.ServiceProvider.GetRequiredService<IFirebaseService>();

            _ = Task.Run(async () =>
            {
                // --- Push Notification Section ---
                // This section attempts to send a test push notification to the user's device.
                // It's designed to fail gracefully and handle invalid Firebase Cloud Messaging (FCM) tokens.

                var name = $"{loggedInEmployee.FirstName} {loggedInEmployee.LastName}";

                if (assignedEmployeeIds.Any())
                {

                    var notification = new Notification
                    {
                        Title = "You have assigned to Bucket",
                        Body = $"You have assigned to bucket \"{bucket.Name}\" by {name}"
                    };

                    await _firebase.SendAssignBucketAsync(assignedEmployeeIds, notification, tenantId);
                }
                if (removedEmployeeIds.Any())
                {

                    var notification = new Notification
                    {
                        Title = "You have removed from Bucket",
                        Body = $"You have removed from bucket \"{bucket.Name}\" by {name}"
                    };

                    await _firebase.SendAssignBucketAsync(removedEmployeeIds, notification, tenantId);
                }

            });
            return ApiResponse<object>.SuccessResponse(bucketVm, "Bucket details updated successfully", 200);
        }
        public async Task<ApiResponse<object>> DeleteBucketAsync(Guid id, Guid tenantId, Employee loggedInEmployee)
        {
            try
            {
                // Fetch the bucket in the main context to verify existence and tenant scope
                var bucket = await _context.Buckets.FirstOrDefaultAsync(b => b.Id == id && b.TenantId == tenantId);

                if (bucket == null)
                {
                    _logger.LogWarning("Employee {EmployeeId} attempted to delete bucket {BucketId}, bucket not found for tenant {TenantId}.",
                        loggedInEmployee.Id, id, tenantId);

                    // Returning success response to maintain idempotency
                    return ApiResponse<object>.SuccessResponse(new { }, "Bucket deleted successfully", 200);
                }

                // Run parallel tasks to fetch related mappings using separate DbContext instances
                var employeeBucketMappingsTask = Task.Run(async () =>
                {
                    using var context = _dbContextFactory.CreateDbContext();
                    return await context.EmployeeBucketMappings
                                        .Where(eb => eb.BucketId == bucket.Id)
                                        .ToListAsync();
                });

                var contactBucketMappingsTask = Task.Run(async () =>
                {
                    using var context = _dbContextFactory.CreateDbContext();
                    return await context.ContactBucketMappings
                                        .Where(cb => cb.BucketId == bucket.Id)
                                        .ToListAsync();
                });

                // Await both tasks concurrently
                await Task.WhenAll(employeeBucketMappingsTask, contactBucketMappingsTask);

                var employeeBucketMappings = employeeBucketMappingsTask.Result;
                var contactBucketMappings = contactBucketMappingsTask.Result;

                // Check if bucket has any contacts mapped - cannot delete in this state
                if (contactBucketMappings.Any())
                {
                    _logger.LogInfo("Employee {EmployeeId} attempted to delete bucket {BucketId} but bucket contains contacts, deletion blocked.",
                        loggedInEmployee.Id, bucket.Id);

                    return ApiResponse<object>.ErrorResponse("This bucket cannot be deleted because it contains contacts.",
                                                             "This bucket cannot be deleted", 400);
                }

                // Check permissions of the logged-in employee
                var (hasAdminPermission, hasManagerPermission, hasUserPermission) = await CheckPermissionsAsync(loggedInEmployee.Id);

                var accessibleBucket = (Bucket?)null;

                // Get bucket IDs for which the employee has mapping association
                var employeeBucketIds = employeeBucketMappings
                    .Where(eb => eb.EmployeeId == loggedInEmployee.Id)
                    .Select(eb => eb.BucketId)
                    .ToList();

                // Determine if employee has permission to delete this bucket
                if (hasAdminPermission)
                {
                    accessibleBucket = bucket;
                }
                else if (hasManagerPermission && employeeBucketIds.Contains(bucket.Id))
                {
                    accessibleBucket = bucket;
                }
                else if (hasUserPermission && bucket.CreatedByID == loggedInEmployee.Id)
                {
                    accessibleBucket = bucket;
                }

                if (accessibleBucket == null)
                {
                    _logger.LogWarning("Employee {EmployeeId} attempted to delete bucket {BucketId} without sufficient permissions.",
                        loggedInEmployee.Id, bucket.Id);

                    return ApiResponse<object>.ErrorResponse("You don't have permission to access this bucket.",
                                                             "Permission denied", 403);
                }

                var bucketObject = _updateLogsHelper.EntityToBsonDocument(bucket);

                // Remove related employee bucket mappings
                _context.EmployeeBucketMappings.RemoveRange(employeeBucketMappings);
                // Remove the bucket itself
                _context.Buckets.Remove(bucket);
                // Log deletion action
                _context.DirectoryUpdateLogs.Add(new DirectoryUpdateLog
                {
                    RefereanceId = bucket.Id,
                    UpdatedById = loggedInEmployee.Id,
                    UpdateAt = DateTime.UtcNow
                });

                await _context.SaveChangesAsync();

                _logger.LogInfo("Employee {EmployeeId} deleted bucket {BucketId} along with related employee bucket mappings.",
                    loggedInEmployee.Id, bucket.Id);

                await _updateLogsHelper.PushToUpdateLogsAsync(new UpdateLogsObject
                {
                    EntityId = bucket.Id.ToString(),
                    UpdatedById = loggedInEmployee.Id.ToString(),
                    OldObject = bucketObject,
                    UpdatedAt = DateTime.UtcNow
                }, bucketCollection);

                using var scope = _serviceScopeFactory.CreateScope();
                var _firebase = scope.ServiceProvider.GetRequiredService<IFirebaseService>();

                _ = Task.Run(async () =>
                {
                    // --- Push Notification Section ---
                    // This section attempts to send a test push notification to the user's device.
                    // It's designed to fail gracefully and handle invalid Firebase Cloud Messaging (FCM) tokens.

                    var name = $"{loggedInEmployee.FirstName} {loggedInEmployee.LastName}";

                    var notification = new Notification
                    {
                        Title = $"Bucket deleted - \"{bucket.Name}\"",
                        Body = $"Bucket deleted \"{bucket.Name}\" by {name}"
                    };

                    await _firebase.SendBucketAsync(bucket.Id, notification, tenantId);

                });

                return ApiResponse<object>.SuccessResponse(new { }, "Bucket deleted successfully", 200);
            }
            catch (Exception ex)
            {
                // Log the exception with error level
                _logger.LogError(ex, "An error occurred while employee {EmployeeId} attempted to delete bucket {BucketId}.",
                                 loggedInEmployee.Id, id);

                return ApiResponse<object>.ErrorResponse("An unexpected error occurred while deleting the bucket.",
                                                         "Internal Server Error", 500);
            }
        }

        #endregion

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

        private async Task<(bool hasAdmin, bool hasManager, bool hasUser)> CheckPermissionsAsync(Guid employeeId)
        {
            // Run all permission checks in parallel.
            var hasAdminTask = Task.Run(async () =>
            {
                using var scope = _serviceScopeFactory.CreateScope();
                var permissionService = scope.ServiceProvider.GetRequiredService<PermissionServices>();
                return await permissionService.HasPermission(PermissionsMaster.DirectoryAdmin, employeeId);
            });
            var hasManagerTask = Task.Run(async () =>
            {
                using var scope = _serviceScopeFactory.CreateScope();
                var permissionService = scope.ServiceProvider.GetRequiredService<PermissionServices>();
                return await permissionService.HasPermission(PermissionsMaster.DirectoryManager, employeeId);
            });
            var hasUserTask = Task.Run(async () =>
            {
                using var scope = _serviceScopeFactory.CreateScope();
                var permissionService = scope.ServiceProvider.GetRequiredService<PermissionServices>();
                return await permissionService.HasPermission(PermissionsMaster.DirectoryUser, employeeId);
            });

            await Task.WhenAll(hasAdminTask, hasManagerTask, hasUserTask);

            return (hasAdminTask.Result, hasManagerTask.Result, hasUserTask.Result);
        }
        private bool Compare(string sentence, string search)
        {
            sentence = sentence.Trim().ToLower();
            search = search.Trim().ToLower();

            // Check for exact substring
            bool result = sentence.IndexOf(search, StringComparison.OrdinalIgnoreCase) >= 0;
            return result;
        }

        private ContactFilterDto? TryDeserializeContactFilter(string? filter)
        {
            if (string.IsNullOrWhiteSpace(filter))
            {
                return null;
            }

            var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
            ContactFilterDto? expenseFilter = null;

            try
            {
                // First, try to deserialize directly. This is the expected case (e.g., from a web client).
                expenseFilter = JsonSerializer.Deserialize<ContactFilterDto>(filter, options);
            }
            catch (JsonException ex)
            {
                _logger.LogError(ex, "[{MethodName}] Failed to directly deserialize filter. Attempting to unescape and re-parse. Filter: {Filter}", nameof(TryDeserializeContactFilter), filter);

                // If direct deserialization fails, it might be an escaped string (common with tools like Postman or some mobile clients).
                try
                {
                    // Unescape the string first, then deserialize the result.
                    string unescapedJsonString = JsonSerializer.Deserialize<string>(filter, options) ?? "";
                    if (!string.IsNullOrWhiteSpace(unescapedJsonString))
                    {
                        expenseFilter = JsonSerializer.Deserialize<ContactFilterDto>(unescapedJsonString, options);
                    }
                }
                catch (JsonException ex1)
                {
                    // If both attempts fail, log the final error and return null.
                    _logger.LogError(ex1, "[{MethodName}] All attempts to deserialize the filter failed. Filter will be ignored. Filter: {Filter}", nameof(TryDeserializeContactFilter), filter);
                    return null;
                }
            }
            return expenseFilter;
        }
        private ContactNoteFilter? TryDeserializeContactNoteFilter(string? filter)
        {
            if (string.IsNullOrWhiteSpace(filter))
            {
                return null;
            }

            var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
            ContactNoteFilter? expenseFilter = null;

            try
            {
                // First, try to deserialize directly. This is the expected case (e.g., from a web client).
                expenseFilter = JsonSerializer.Deserialize<ContactNoteFilter>(filter, options);
            }
            catch (JsonException ex)
            {
                _logger.LogError(ex, "[{MethodName}] Failed to directly deserialize filter. Attempting to unescape and re-parse. Filter: {Filter}", nameof(TryDeserializeContactNoteFilter), filter);

                // If direct deserialization fails, it might be an escaped string (common with tools like Postman or some mobile clients).
                try
                {
                    // Unescape the string first, then deserialize the result.
                    string unescapedJsonString = JsonSerializer.Deserialize<string>(filter, options) ?? "";
                    if (!string.IsNullOrWhiteSpace(unescapedJsonString))
                    {
                        expenseFilter = JsonSerializer.Deserialize<ContactNoteFilter>(unescapedJsonString, options);
                    }
                }
                catch (JsonException ex1)
                {
                    // If both attempts fail, log the final error and return null.
                    _logger.LogError(ex1, "[{MethodName}] All attempts to deserialize the filter failed. Filter will be ignored. Filter: {Filter}", nameof(TryDeserializeContactNoteFilter), filter);
                    return null;
                }
            }
            return expenseFilter;
        }

        private static object ExceptionMapper(Exception ex)
        {
            return new
            {
                Message = ex.Message,
                StackTrace = ex.StackTrace,
                Source = ex.Source,
                InnerException = new
                {
                    Message = ex.InnerException?.Message,
                    StackTrace = ex.InnerException?.StackTrace,
                    Source = ex.InnerException?.Source,
                }
            };
        }

        // --- Helper Methods for Readability and Separation of Concerns ---

        private List<ContactPhoneVM> ProcessContactPhones(CreateContactDto dto, Contact contact, ISet<string> existingPhones)
        {
            if (!(dto.ContactPhones?.Any() ?? false)) return new List<ContactPhoneVM>();

            var newPhones = dto.ContactPhones
                .Where(p => !string.IsNullOrWhiteSpace(p.PhoneNumber) && existingPhones.Add(p.PhoneNumber)) // .Add returns true if the item was added (i.e., not present)
                .Select(pDto =>
                {
                    var phone = _mapper.Map<ContactPhone>(pDto);
                    phone.ContactId = contact.Id;
                    phone.TenantId = contact.TenantId; // Ensure tenant is set on child entities
                    return phone;
                }).ToList();

            _context.ContactsPhones.AddRange(newPhones);
            _logger.LogInfo("Adding {Count} new phone numbers for Contact {ContactId}.", newPhones.Count, contact.Id);

            return newPhones.Select(p => _mapper.Map<ContactPhoneVM>(p)).ToList();
        }
        private List<ContactEmailVM> ProcessContactEmails(CreateContactDto dto, Contact contact, ISet<string> existingEmails)
        {
            if (!(dto.ContactEmails?.Any() ?? false)) return new List<ContactEmailVM>();

            var newEmails = dto.ContactEmails
                .Where(e => !string.IsNullOrWhiteSpace(e.EmailAddress) && existingEmails.Add(e.EmailAddress)) // HashSet handles case-insensitivity set in constructor
                .Select(eDto =>
                {
                    var email = _mapper.Map<ContactEmail>(eDto);
                    email.ContactId = contact.Id;
                    email.TenantId = contact.TenantId;
                    return email;
                }).ToList();

            _context.ContactsEmails.AddRange(newEmails);
            _logger.LogInfo("Adding {Count} new email addresses for Contact {ContactId}.", newEmails.Count, contact.Id);

            return newEmails.Select(e => _mapper.Map<ContactEmailVM>(e)).ToList();
        }
        private List<ContactTagVM> ProcessTags(CreateContactDto dto, Contact contact, IDictionary<string, ContactTagMaster> tenantTags)
        {
            if (!(dto.Tags?.Any() ?? false)) return new List<ContactTagVM>();

            var tagVMs = new List<ContactTagVM>();
            var newTagMappings = new List<ContactTagMapping>();

            foreach (var tagName in dto.Tags.Select(t => t.Name).Distinct(StringComparer.OrdinalIgnoreCase))
            {
                if (string.IsNullOrWhiteSpace(tagName)) continue;

                var normalizedTag = tagName.ToLowerInvariant();
                if (!tenantTags.TryGetValue(normalizedTag, out var tagMaster))
                {
                    // Tag does not exist, create it.
                    tagMaster = new ContactTagMaster { Name = tagName, Description = tagName, TenantId = contact.TenantId };
                    _context.ContactTagMasters.Add(tagMaster);
                    tenantTags.Add(normalizedTag, tagMaster); // Add to dictionary to handle duplicates in the input list
                    _logger.LogDebug("Creating new tag '{TagName}' for Tenant {TenantId}.", tagName, contact.TenantId);
                }

                newTagMappings.Add(new ContactTagMapping { ContactId = contact.Id, ContactTag = tagMaster });
                tagVMs.Add(_mapper.Map<ContactTagVM>(tagMaster));
            }

            _context.ContactTagMappings.AddRange(newTagMappings);
            _logger.LogInfo("Adding {Count} tag mappings for Contact {ContactId}.", newTagMappings.Count, contact.Id);

            return tagVMs;
        }
        private async Task<List<ContactBucketMapping>> ProcessBucketMappingsAsync(CreateContactDto dto, Contact contact, Guid tenantId)
        {
            if (!(dto.BucketIds?.Any() ?? false)) return new List<ContactBucketMapping>();

            var validBucketIds = await _context.Buckets
                .Where(b => dto.BucketIds.Contains(b.Id) && b.TenantId == tenantId)
                .Select(b => b.Id)
                .ToListAsync();

            var mappings = validBucketIds.Select(bucketId => new ContactBucketMapping
            {
                BucketId = bucketId,
                ContactId = contact.Id
            }).ToList();

            _context.ContactBucketMappings.AddRange(mappings);
            _logger.LogInfo("Adding {Count} bucket mappings for Contact {ContactId}.", mappings.Count, contact.Id);

            return mappings;
        }
        private async Task<List<ContactProjectMapping>> ProcessProjectMappingsAsync(CreateContactDto dto, Contact contact, Guid tenantId)
        {
            if (!(dto.ProjectIds?.Any() ?? false)) return new List<ContactProjectMapping>();

            var validProjectIds = await _context.Projects
                .Where(p => dto.ProjectIds.Contains(p.Id) && p.TenantId == tenantId)
                .Select(p => p.Id)
                .ToListAsync();

            var mappings = validProjectIds.Select(projectId => new ContactProjectMapping
            {
                ProjectId = projectId,
                ContactId = contact.Id,
                TenantId = tenantId
            }).ToList();

            _context.ContactProjectMappings.AddRange(mappings);
            _logger.LogInfo("Adding {Count} project mappings for Contact {ContactId}.", mappings.Count, contact.Id);

            return mappings;
        }

        #endregion
    }
}
