using AutoMapper;
using Marco.Pms.DataAccess.Data;
using Marco.Pms.Helpers.Utility;
using Marco.Pms.Model.Dtos.Tenant;
using Marco.Pms.Model.Employees;
using Marco.Pms.Model.Entitlements;
using Marco.Pms.Model.OrganizationModel;
using Marco.Pms.Model.Projects;
using Marco.Pms.Model.Roles;
using Marco.Pms.Model.TenantModels;
using Marco.Pms.Model.TenantModels.MongoDBModel;
using Marco.Pms.Model.Utilities;
using Marco.Pms.Model.ViewModels.Activities;
using Marco.Pms.Model.ViewModels.Tenant;
using Marco.Pms.Services.Helpers;
using Marco.Pms.Services.Service.ServiceInterfaces;
using MarcoBMS.Services.Service;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using System.Net;

namespace Marco.Pms.Services.Service
{
    public class TenantService : ITenantService
    {
        private readonly IDbContextFactory<ApplicationDbContext> _dbContextFactory;
        private readonly IServiceScopeFactory _serviceScopeFactory;
        private readonly ILoggingService _logger;
        private readonly UserManager<ApplicationUser> _userManager;
        private readonly IMapper _mapper;
        private readonly FeatureDetailsHelper _featureDetailsHelper;

        private readonly static Guid tenantActiveStatus = Guid.Parse("62b05792-5115-4f99-8ff5-e8374859b191");
        private readonly static Guid activePlanStatus = Guid.Parse("cd3a68ea-41fd-42f0-bd0c-c871c7337727");
        private readonly static Guid EmployeeFeatureId = Guid.Parse("81ab8a87-8ccd-4015-a917-0627cee6a100");
        private readonly static string AdminRoleName = "Admin";
        public TenantService(IDbContextFactory<ApplicationDbContext> dbContextFactory,
            IServiceScopeFactory serviceScopeFactory,
            ILoggingService logger,
            UserManager<ApplicationUser> userManager,
            IMapper mapper,
            FeatureDetailsHelper featureDetailsHelper)
        {
            _dbContextFactory = dbContextFactory ?? throw new ArgumentNullException(nameof(dbContextFactory));
            _serviceScopeFactory = serviceScopeFactory ?? throw new ArgumentNullException(nameof(serviceScopeFactory));
            _logger = logger ?? throw new ArgumentNullException(nameof(logger));
            _userManager = userManager ?? throw new ArgumentNullException(nameof(userManager));
            _mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
            _featureDetailsHelper = featureDetailsHelper ?? throw new ArgumentNullException(nameof(featureDetailsHelper));
        }

        public async Task<ApiResponse<object>> CreateTenantAsync(Guid enquireId, Guid paymentDetailId, Guid planId)
        {
            using var scope = _serviceScopeFactory.CreateScope();
            await using var _context = await _dbContextFactory.CreateDbContextAsync();

            var _configuration = scope.ServiceProvider.GetRequiredService<IConfiguration>();
            var _emailSender = scope.ServiceProvider.GetRequiredService<IEmailSender>();

            var tenantEnquireTask = Task.Run(async () =>
            {
                await using var context = await _dbContextFactory.CreateDbContextAsync();
                return await context.TenantEnquires.FirstOrDefaultAsync(te => te.Id == enquireId);
            });
            var paymentDetailTask = Task.Run(async () =>
            {
                await using var context = await _dbContextFactory.CreateDbContextAsync();
                return await context.PaymentDetails.FirstOrDefaultAsync(pd => pd.Id == paymentDetailId);
            });
            var subscriptionPlanTask = Task.Run(async () =>
            {
                await using var context = await _dbContextFactory.CreateDbContextAsync();
                return await context.SubscriptionPlanDetails.Include(sp => sp.Plan).FirstOrDefaultAsync(sp => sp.Id == planId);
            });

            await Task.WhenAll(tenantEnquireTask, paymentDetailTask, subscriptionPlanTask);

            var tenantEnquire = tenantEnquireTask.Result;
            var paymentDetail = paymentDetailTask.Result;
            var subscriptionPlan = subscriptionPlanTask.Result;

            if (tenantEnquire == null)
            {
                _logger.LogWarning("Tenant Enquire {TenantEnquireId} not found in database", enquireId);
                return ApiResponse<object>.ErrorResponse("Tenant Enquire not found", "Tenant Enquire not found", 404);
            }
            if (paymentDetail == null)
            {
                _logger.LogWarning("Payment Details {PaymentDetailsId} not found in database", paymentDetailId);
                return ApiResponse<object>.ErrorResponse("Payment Details not found", "Payment Details not found", 404);
            }
            if (subscriptionPlan == null)
            {
                _logger.LogWarning("Subscription plan {PlanId} not found in database", planId);
                return ApiResponse<object>.ErrorResponse("Subscription plan not found", "Subscription plan not found", 404);
            }

            var existingUser = await _userManager.FindByEmailAsync(tenantEnquire.Email);
            if (existingUser != null)
            {
                _logger.LogWarning("Tenant creation failed for email {Email}: an application user with this email already exists.", tenantEnquire.Email);
                return ApiResponse<object>.ErrorResponse("Tenant cannot be created", "A user with the specified email already exists.", 409);
            }

            await using var transaction = await _context.Database.BeginTransactionAsync();
            try
            {
                Guid employeeId = Guid.NewGuid();
                DateTime onBoardingDate = DateTime.UtcNow;
                Guid tenantId = Guid.NewGuid();

                // Get last SPRID and increment for new organization
                var lastOrganization = await _context.Organizations.OrderByDescending(sp => sp.SPRID).FirstOrDefaultAsync();
                double lastSPRID = lastOrganization?.SPRID ?? 5400;

                // Map DTO to entity and set defaults
                Organization organization = new Organization
                {
                    Name = tenantEnquire.OrganizationName,
                    Email = tenantEnquire.Email,
                    ContactPerson = $"{tenantEnquire.FirstName} {tenantEnquire.LastName}",
                    Address = tenantEnquire.BillingAddress,
                    ContactNumber = tenantEnquire.ContactNumber,
                    SPRID = lastSPRID + 1,
                    CreatedAt = DateTime.UtcNow,
                    CreatedById = employeeId,
                    IsActive = true
                };

                _context.Organizations.Add(organization);

                // Create the primary Tenant entity

                var tenant = new Tenant
                {
                    Id = tenantId,
                    Name = tenantEnquire.OrganizationName,
                    Email = tenantEnquire.Email,
                    ContactName = $"{tenantEnquire.FirstName} {tenantEnquire.LastName}",
                    ContactNumber = tenantEnquire.ContactNumber,
                    OrganizationSize = tenantEnquire.OrganizationSize,
                    BillingAddress = tenantEnquire.BillingAddress,
                    IndustryId = tenantEnquire.IndustryId,
                    Reference = tenantEnquire.Reference,
                    OnBoardingDate = onBoardingDate,
                    TenantStatusId = tenantActiveStatus,
                    OrganizationId = organization.Id,
                    CreatedById = employeeId,
                    IsActive = true,
                    IsSuperTenant = false
                };

                _context.Tenants.Add(tenant);

                // Create the root ApplicationUser for the new tenant
                var applicationUser = new ApplicationUser
                {
                    Email = tenantEnquire.Email,
                    UserName = tenantEnquire.Email, // Best practice to use email as username for simplicity
                    IsRootUser = true,
                    EmailConfirmed = true // Auto-confirming email as it's part of a trusted setup process
                };

                // SECURITY WARNING: Hardcoded passwords are a major vulnerability.
                // Replace "User@123" with a securely generated random password.
                var initialPassword = "User@123"; // TODO: Replace with password generation service.
                var result = await _userManager.CreateAsync(applicationUser, initialPassword);

                if (!result.Succeeded)
                {
                    // If user creation fails, roll back the transaction immediately and return the errors.
                    await transaction.RollbackAsync();
                    var errors = result.Errors.Select(e => e.Description).ToList();
                    _logger.LogWarning("Failed to create ApplicationUser for tenant {TenantName}. Errors: {Errors}", tenantEnquire.OrganizationName, string.Join(", ", errors));
                    return ApiResponse<object>.ErrorResponse("Failed to create user", errors, 400);
                }

                // Create the default "Admin" Job Role for the tenant
                var adminJobRole = new JobRole
                {
                    Name = AdminRoleName,
                    Description = "Default administrator role for the tenant.",
                    TenantId = tenantId
                };
                _context.JobRoles.Add(adminJobRole);

                // Create the primary Employee record and link it to the ApplicationUser and JobRole
                var employeeUser = new Employee
                {
                    Id = employeeId,
                    FirstName = tenantEnquire.FirstName,
                    LastName = tenantEnquire.LastName,
                    Email = tenantEnquire.Email,
                    PhoneNumber = tenantEnquire.ContactNumber,
                    JoiningDate = onBoardingDate,
                    ApplicationUserId = applicationUser.Id,
                    JobRole = adminJobRole, // Link to the newly created role
                    CurrentAddress = tenantEnquire.BillingAddress,
                    IsActive = true,
                    IsSystem = false,
                    IsPrimary = true,
                    OrganizationId = organization.Id,
                    HasApplicationAccess = true
                };
                _context.Employees.Add(employeeUser);

                var applicationRole = new ApplicationRole
                {
                    Role = "Super User",
                    Description = "Super User",
                    IsSystem = true,
                    TenantId = tenantId
                };
                _context.ApplicationRoles.Add(applicationRole);

                var rolePermissionMappigs = new List<RolePermissionMappings> {
                    new RolePermissionMappings
                    {
                        ApplicationRoleId = applicationRole.Id,
                        FeaturePermissionId = PermissionsMaster.ModifyTenant
                    },
                    new RolePermissionMappings
                    {
                        ApplicationRoleId = applicationRole.Id,
                        FeaturePermissionId = PermissionsMaster.ViewTenant
                    },
                    new RolePermissionMappings
                    {
                        ApplicationRoleId = applicationRole.Id,
                        FeaturePermissionId = PermissionsMaster.ManageMasters
                    },
                    new RolePermissionMappings
                    {
                        ApplicationRoleId = applicationRole.Id,
                        FeaturePermissionId = PermissionsMaster.ViewMasters
                    },
                    new RolePermissionMappings
                    {
                        ApplicationRoleId = applicationRole.Id,
                        FeaturePermissionId = PermissionsMaster.ViewOrganization
                    },
                    new RolePermissionMappings
                    {
                        ApplicationRoleId = applicationRole.Id,
                        FeaturePermissionId = PermissionsMaster.AddOrganization
                    },
                    new RolePermissionMappings
                    {
                        ApplicationRoleId = applicationRole.Id,
                        FeaturePermissionId = PermissionsMaster.EditOrganization
                    }
                };
                _context.RolePermissionMappings.AddRange(rolePermissionMappigs);

                _context.EmployeeRoleMappings.Add(new EmployeeRoleMapping
                {
                    EmployeeId = employeeUser.Id,
                    RoleId = applicationRole.Id,
                    IsEnabled = true,
                    TenantId = tenantId
                });

                // Create a default project for the new tenant
                var project = new Project
                {
                    Name = "Default Project",
                    ProjectStatusId = Guid.Parse("b74da4c2-d07e-46f2-9919-e75e49b12731"), // Consider using a constant for this GUID
                    ProjectAddress = tenantEnquire.BillingAddress,
                    StartDate = onBoardingDate,
                    EndDate = DateTime.MaxValue,
                    PromoterId = organization.Id,
                    PMCId = organization.Id,
                    ContactPerson = tenant.ContactName,
                    TenantId = tenantId
                };
                _context.Projects.Add(project);

                var projectAllocation = new ProjectAllocation
                {
                    ProjectId = project.Id,
                    EmployeeId = employeeUser.Id,
                    AllocationDate = onBoardingDate,
                    IsActive = true,
                    JobRoleId = adminJobRole.Id,
                    TenantId = tenantId
                };
                _context.ProjectAllocations.Add(projectAllocation);

                // All entities are now added to the context. Save them all in a single database operation.
                await _context.SaveChangesAsync();

                // 4. --- POST-CREATION ACTIONS ---
                // Generate a password reset token so the new user can set their own password.
                _logger.LogInfo("User {Email} created. Sending password setup email.", applicationUser.Email);
                var token = await _userManager.GeneratePasswordResetTokenAsync(applicationUser);
                var resetLink = $"{_configuration["AppSettings:WebFrontendUrl"]}/reset-password?token={WebUtility.UrlEncode(token)}&email={WebUtility.UrlEncode(applicationUser.Email)}";
                await _emailSender.SendResetPasswordEmailOnRegister(applicationUser.Email, employeeUser.FirstName, resetLink);

                // Map the result to a ViewModel for the API response.
                var tenantVM = _mapper.Map<TenantVM>(tenant);
                tenantVM.CreatedBy = _mapper.Map<BasicEmployeeVM>(employeeUser);

                // Commit the transaction as all operations were successful.
                await transaction.CommitAsync();

                await AddSubscriptionAsync(tenantId, employeeId, paymentDetailId, planId);

                _logger.LogInfo("Successfully created tenant {TenantId} for organization {OrganizationName}.", tenant.Id, tenant.Name);
                return ApiResponse<object>.SuccessResponse(tenantVM, "Tenant created successfully.", 201);
            }
            catch (DbUpdateException dbEx)
            {
                await transaction.RollbackAsync();
                // Log the detailed database exception, including the inner exception if available.
                _logger.LogError(dbEx, "A database update exception occurred while creating tenant for email {Email}. Inner Exception: {InnerException}",
                    tenantEnquire.Email, dbEx.InnerException?.Message ?? string.Empty);
                return ApiResponse<object>.ErrorResponse("An internal database error occurred.", ExceptionMapper(dbEx), 500);
            }
            catch (Exception ex)
            {
                // Log the general exception.
                _logger.LogError(ex, "An unexpected exception occurred while creating tenant for email {Email}.", tenantEnquire.Email);
                return ApiResponse<object>.ErrorResponse("An unexpected internal error occurred.", ExceptionMapper(ex), 500);
            }
        }
        public async Task<ApiResponse<object>> AddSubscriptionAsync(Guid tenantId, Guid employeeId, Guid paymentDetailId, Guid planId)
        {

            _logger.LogInfo("AddSubscription called for Tenant {TenantId} and Plan {PlanId}", tenantId, planId);

            await using var _context = await _dbContextFactory.CreateDbContextAsync();
            using var scope = _serviceScopeFactory.CreateScope();

            var subscriptionPlan = await _context.SubscriptionPlanDetails.Include(sp => sp.Plan).FirstOrDefaultAsync(sp => sp.Id == planId);

            var tenant = await _context.Tenants.FirstOrDefaultAsync(t => t.Id == tenantId);
            if (tenant == null)
            {
                _logger.LogWarning("Tenant {TenantId} not found in database", tenantId);
                return ApiResponse<object>.ErrorResponse("Tenant not found", "Tenant not found", 404);
            }
            if (subscriptionPlan == null)
            {
                _logger.LogWarning("Subscription plan {PlanId} not found in database", planId);
                return ApiResponse<object>.ErrorResponse("Subscription plan not found", "Subscription plan not found", 404);
            }
            var activeUsers = await _context.Employees.CountAsync(e => e.Email != null && e.ApplicationUserId != null && e.TenantId == tenant.Id && e.IsActive);
            if (activeUsers > subscriptionPlan.MaxUser)
            {
                _logger.LogWarning("Add less max user than the active user in the tenant {TenantId}", tenant.Id);
                return ApiResponse<object>.ErrorResponse("Invalid Max user count", "Max User count must be higher than active user count", 400);
            }
            await using var transaction = await _context.Database.BeginTransactionAsync();
            var utcNow = DateTime.UtcNow;

            // Prepare subscription dates based on frequency
            var endDate = subscriptionPlan.Frequency switch
            {
                PLAN_FREQUENCY.MONTHLY => utcNow.AddDays(30),
                PLAN_FREQUENCY.QUARTERLY => utcNow.AddDays(90),
                PLAN_FREQUENCY.HALF_YEARLY => utcNow.AddDays(120),
                PLAN_FREQUENCY.YEARLY => utcNow.AddDays(360),
                _ => utcNow // default if unknown
            };

            var tenantSubscription = new TenantSubscriptions
            {
                TenantId = tenantId,
                PlanId = planId,
                StatusId = activePlanStatus,
                CreatedAt = utcNow,
                MaxUsers = subscriptionPlan.MaxUser,
                CreatedById = employeeId,
                CurrencyId = subscriptionPlan.CurrencyId,
                PaymentDetailId = paymentDetailId,
                IsTrial = false,
                StartDate = utcNow,
                EndDate = endDate,
                NextBillingDate = endDate,
                AutoRenew = false
            };

            _context.TenantSubscriptions.Add(tenantSubscription);

            try
            {
                await _context.SaveChangesAsync();
                _logger.LogInfo("Tenant subscription added successfully for Tenant {TenantId}, Plan {PlanId}",
                    tenantId, planId);
            }
            catch (DbUpdateException dbEx)
            {
                _logger.LogError(dbEx, "Database exception while adding subscription plan to tenant {TenantId}", tenantId);
                return ApiResponse<object>.ErrorResponse("Internal error occured", ExceptionMapper(dbEx), 500);
            }

            try
            {
                var features = await _featureDetailsHelper.GetFeatureDetails(subscriptionPlan.FeaturesId);
                if (features == null)
                {
                    _logger.LogInfo("No features found for subscription plan {PlanId}", planId);
                    await transaction.CommitAsync();
                    return ApiResponse<object>.SuccessResponse(tenantSubscription, "Tenant subscription successfully added", 200);
                }

                // Helper to get permissions for a module asynchronously
                async Task<List<Guid>> GetPermissionsForModuleAsync(List<Guid>? featureIds)
                {
                    if (featureIds == null || featureIds.Count == 0) return new List<Guid>();

                    await using var ctx = await _dbContextFactory.CreateDbContextAsync();
                    return await ctx.FeaturePermissions.AsNoTracking()
                        .Where(fp => featureIds.Contains(fp.FeatureId))
                        .Select(fp => fp.Id)
                        .ToListAsync();
                }

                // Fetch permission tasks for all modules in parallel
                var projectPermissionTask = GetPermissionsForModuleAsync(features.Modules?.ProjectManagement?.FeatureId);
                var attendancePermissionTask = GetPermissionsForModuleAsync(features.Modules?.Attendance?.FeatureId);
                var directoryPermissionTask = GetPermissionsForModuleAsync(features.Modules?.Directory?.FeatureId);
                var expensePermissionTask = GetPermissionsForModuleAsync(features.Modules?.Expense?.FeatureId);
                var employeePermissionTask = GetPermissionsForModuleAsync(new List<Guid> { EmployeeFeatureId });

                await Task.WhenAll(projectPermissionTask, attendancePermissionTask, directoryPermissionTask, expensePermissionTask, employeePermissionTask);

                var newPermissionIds = new List<Guid>();
                var deletePermissionIds = new List<Guid>();

                // Add or remove permissions based on modules enabled status
                void ProcessPermissions(bool? enabled, List<Guid> permissions)
                {
                    if (enabled == true)
                        newPermissionIds.AddRange(permissions);
                    else
                        deletePermissionIds.AddRange(permissions);
                }

                ProcessPermissions(features.Modules?.ProjectManagement?.Enabled, projectPermissionTask.Result);
                ProcessPermissions(features.Modules?.Attendance?.Enabled, attendancePermissionTask.Result);
                ProcessPermissions(features.Modules?.Directory?.Enabled, directoryPermissionTask.Result);
                ProcessPermissions(features.Modules?.Expense?.Enabled, expensePermissionTask.Result);

                newPermissionIds = newPermissionIds.Distinct().ToList();
                deletePermissionIds = deletePermissionIds.Distinct().ToList();

                // Get root employee and role for this tenant
                var rootEmployee = await _context.Employees
                    .Include(e => e.ApplicationUser)
                    .FirstOrDefaultAsync(e => e.ApplicationUser != null && (e.ApplicationUser.IsRootUser ?? false) && e.OrganizationId == tenant.OrganizationId);

                if (rootEmployee == null)
                {
                    _logger.LogWarning("Root employee not found for tenant {TenantId}", tenantId);
                    await transaction.CommitAsync();
                    return ApiResponse<object>.SuccessResponse(tenantSubscription, "Tenant subscription successfully added", 200);
                }

                var roleId = await _context.EmployeeRoleMappings
                    .AsNoTracking()
                    .Where(er => er.EmployeeId == rootEmployee.Id && er.TenantId == tenantId)
                    .Select(er => er.RoleId)
                    .FirstOrDefaultAsync();

                if (roleId == Guid.Empty)
                {
                    _logger.LogWarning("RoleId for root employee {EmployeeId} in tenant {TenantId} not found", rootEmployee.Id, tenantId);
                    await transaction.CommitAsync();
                    return ApiResponse<object>.SuccessResponse(tenantSubscription, "Tenant subscription successfully added", 200);
                }

                var oldRolePermissionMappings = await _context.RolePermissionMappings
                    .Where(rp => rp.ApplicationRoleId == roleId)
                    .ToListAsync();

                var oldPermissionIds = oldRolePermissionMappings.Select(rp => rp.FeaturePermissionId).ToList();

                // Prevent accidentally deleting essential employee permissions
                var permissionIdCount = oldPermissionIds.Count - deletePermissionIds.Count;
                if (permissionIdCount <= 4 && deletePermissionIds.Any())
                {
                    var employeePermissionIds = employeePermissionTask.Result;
                    deletePermissionIds = deletePermissionIds.Where(p => !employeePermissionIds.Contains(p)).ToList();
                }

                // Prepare mappings to delete and add
                var deleteMappings = oldRolePermissionMappings.Where(rp => deletePermissionIds.Contains(rp.FeaturePermissionId)).ToList();
                var addRolePermissionMappings = newPermissionIds
                    .Where(p => !oldPermissionIds.Contains(p))
                    .Select(p => new RolePermissionMappings
                    {
                        ApplicationRoleId = roleId,
                        FeaturePermissionId = p
                    })
                    .ToList();

                if (addRolePermissionMappings.Any())
                {
                    _context.RolePermissionMappings.AddRange(addRolePermissionMappings);
                    _logger.LogInfo("Added {Count} new role permission mappings for role {RoleId}", addRolePermissionMappings.Count, roleId);
                }
                if (deleteMappings.Any())
                {
                    _context.RolePermissionMappings.RemoveRange(deleteMappings);
                    _logger.LogInfo("Removed {Count} role permission mappings for role {RoleId}", deleteMappings.Count, roleId);
                }

                var _cache = scope.ServiceProvider.GetRequiredService<CacheUpdateHelper>();
                await _cache.ClearAllEmployeesFromCacheByTenantId(tenant.Id);

                var _masteData = scope.ServiceProvider.GetRequiredService<MasterDataService>();

                if (features.Modules?.ProjectManagement?.Enabled ?? false)
                {
                    var workCategoryMaster = _masteData.GetWorkCategoriesData(tenant.Id);
                    var workStatusMaster = _masteData.GetWorkStatusesData(tenant.Id);

                    _context.WorkCategoryMasters.AddRange(workCategoryMaster);
                    _context.WorkStatusMasters.AddRange(workStatusMaster);
                }
                if (features.Modules?.Expense?.Enabled ?? false)
                {
                    var expenseCategoryMaster = _masteData.GetExpenseCategoryData(tenant.Id);

                    _context.ExpenseCategoryMasters.AddRange(expenseCategoryMaster);
                }

                await _context.SaveChangesAsync();

                await transaction.CommitAsync();

                _logger.LogInfo("Permissions updated successfully for tenant {TenantId} subscription", tenantId);

                return ApiResponse<object>.SuccessResponse(tenantSubscription, "Tenant Subscription Successfully", 200);
            }
            catch (Exception ex)
            {
                await transaction.RollbackAsync();
                _logger.LogError(ex, "Exception occurred while updating permissions for tenant {TenantId}", tenantId);
                return ApiResponse<object>.ErrorResponse("Internal error occured", ExceptionMapper(ex), 500);
            }
        }
        public async Task<ApiResponse<object>> RenewSubscriptionAsync(Guid tenantId, Guid employeeId, Guid paymentDetailId, Guid planId)
        {
            // 2. Create a new DbContext instance for this request.
            await using var context = await _dbContextFactory.CreateDbContextAsync();

            // 3. Get PermissionServices from DI inside a fresh scope (rarely needed, but retained for your design).
            using var scope = _serviceScopeFactory.CreateScope();
            var permissionService = scope.ServiceProvider.GetRequiredService<PermissionServices>();
            var _updateLogHelper = scope.ServiceProvider.GetRequiredService<UtilityMongoDBHelper>();

            // 4. Check user permissions: must be both Root user and have ManageTenants permission.
            var hasPermission = await permissionService.HasPermission(PermissionsMaster.ManageTenants, employeeId);

            if (!hasPermission)
            {
                _logger.LogWarning("Permission denied for EmployeeId={EmployeeId}. HasPermission: {HasPermission}",
                    employeeId, hasPermission);
                return ApiResponse<object>.ErrorResponse("Access denied", "User does not have the required permissions.", 403);
            }

            // 5. Fetch Tenant, SubscriptionPlan, and TenantSubscription in parallel (efficiently).
            var tenantTask = _dbContextFactory.CreateDbContextAsync().ContinueWith(ctx =>
                ctx.Result.Tenants.AsNoTracking().FirstOrDefaultAsync(t => t.Id == tenantId)).Unwrap();

            var planDetailsTask = _dbContextFactory.CreateDbContextAsync().ContinueWith(ctx =>
                ctx.Result.SubscriptionPlanDetails.Include(sp => sp.Plan).AsNoTracking().FirstOrDefaultAsync(sp => sp.Id == planId)).Unwrap();

            var currentSubscriptionTask = _dbContextFactory.CreateDbContextAsync().ContinueWith(ctx =>
                ctx.Result.TenantSubscriptions.Include(ts => ts.Currency).AsNoTracking().FirstOrDefaultAsync(ts => ts.TenantId == tenantId && !ts.IsCancelled)).Unwrap();

            var paymentDetailsTask = _dbContextFactory.CreateDbContextAsync().ContinueWith(ctx =>
                ctx.Result.PaymentDetails.AsNoTracking().FirstOrDefaultAsync(pd => pd.Id == paymentDetailId)).Unwrap();


            await Task.WhenAll(tenantTask, planDetailsTask, currentSubscriptionTask, paymentDetailsTask);

            var tenant = tenantTask.Result;
            if (tenant == null)
            {
                _logger.LogWarning("Tenant {TenantId} not found.", tenantId);
                return ApiResponse<object>.ErrorResponse("Tenant not found", "Tenant not found", 404);
            }

            var subscriptionPlan = planDetailsTask.Result;
            if (subscriptionPlan == null)
            {
                _logger.LogWarning("Subscription plan {PlanId} not found.", planId);
                return ApiResponse<object>.ErrorResponse("Subscription plan not found", "Subscription plan not found", 404);
            }

            var paymentDetail = paymentDetailsTask.Result;
            if (paymentDetail == null)
            {
                _logger.LogWarning("Payment details {PaymentDetailId} not found", paymentDetailId);
                return ApiResponse<object>.ErrorResponse("Payment details not found", "Payment details not found", 404);
            }

            var currentSubscription = currentSubscriptionTask.Result;
            var utcNow = DateTime.UtcNow;

            var activeUsers = await context.Employees.CountAsync(e => e.Email != null && e.ApplicationUserId != null && e.TenantId == tenant.Id && e.IsActive);
            if (activeUsers > subscriptionPlan.MaxUser)
            {
                _logger.LogWarning("Employee {EmployeeId} add less max user than the active user in the tenant {TenantId}", employeeId, tenant.Id);
                return ApiResponse<object>.ErrorResponse("Invalid Max user count", "Max User count must be higher than active user count", 400);
            }

            // 7. Else, change plan: new subscription record, close the old if exists.
            await using var transaction = await context.Database.BeginTransactionAsync();
            try
            {
                // 7a. Compute new plan dates
                var endDate = subscriptionPlan.Frequency switch
                {
                    PLAN_FREQUENCY.MONTHLY => utcNow.AddDays(30),
                    PLAN_FREQUENCY.QUARTERLY => utcNow.AddDays(90),
                    PLAN_FREQUENCY.HALF_YEARLY => utcNow.AddDays(120),
                    PLAN_FREQUENCY.YEARLY => utcNow.AddDays(360),
                    _ => utcNow // default if unknown
                };

                var newSubscription = new TenantSubscriptions
                {
                    TenantId = tenantId,
                    PlanId = planId,
                    StatusId = activePlanStatus,
                    CreatedAt = utcNow,
                    MaxUsers = subscriptionPlan.MaxUser,
                    CreatedById = employeeId,
                    CurrencyId = subscriptionPlan.CurrencyId,
                    PaymentDetailId = paymentDetailId,
                    StartDate = utcNow,
                    EndDate = endDate,
                    NextBillingDate = endDate,
                    IsTrial = currentSubscription?.IsTrial ?? false,
                    AutoRenew = currentSubscription?.AutoRenew ?? false
                };
                context.TenantSubscriptions.Add(newSubscription);

                // 7b. If an old subscription exists, cancel it.
                if (currentSubscription != null)
                {
                    currentSubscription.IsCancelled = true;
                    currentSubscription.CancellationDate = utcNow;
                    currentSubscription.UpdateAt = utcNow;
                    currentSubscription.UpdatedById = employeeId;
                    context.TenantSubscriptions.Update(currentSubscription);
                }
                await context.SaveChangesAsync();
                _logger.LogInfo("Subscription plan changed: Tenant={TenantId}, NewPlan={PlanId}",
                    tenantId, planId);

                _ = Task.Run(async () =>
                {
                    await ClearPermissionForTenant(tenantId);
                });

                // 8. Update tenant permissions based on subscription features.
                var features = await _featureDetailsHelper.GetFeatureDetails(subscriptionPlan.FeaturesId);
                if (features == null)
                {
                    _logger.LogInfo("No features for Plan={PlanId}.", planId);
                    await transaction.CommitAsync();
                    return ApiResponse<object>.SuccessResponse(newSubscription, "Tenant subscription updated (no features)", 200);
                }

                // 8a. Async helper to get all permission IDs for a given module.
                async Task<List<Guid>> GetPermissionsForFeaturesAsync(List<Guid>? featureIds)
                {
                    if (featureIds == null || featureIds.Count == 0) return new List<Guid>();

                    await using var ctx = await _dbContextFactory.CreateDbContextAsync();
                    return await ctx.FeaturePermissions.AsNoTracking()
                        .Where(fp => featureIds.Contains(fp.FeatureId))
                        .Select(fp => fp.Id)
                        .ToListAsync();
                }

                // 8b. Fetch all module permissions concurrently.
                var projectPermTask = GetPermissionsForFeaturesAsync(features.Modules?.ProjectManagement?.FeatureId);
                var attendancePermTask = GetPermissionsForFeaturesAsync(features.Modules?.Attendance?.FeatureId);
                var directoryPermTask = GetPermissionsForFeaturesAsync(features.Modules?.Directory?.FeatureId);
                var expensePermTask = GetPermissionsForFeaturesAsync(features.Modules?.Expense?.FeatureId);
                var employeePermTask = GetPermissionsForFeaturesAsync(new List<Guid> { EmployeeFeatureId }); // assumed defined

                await Task.WhenAll(projectPermTask, attendancePermTask, directoryPermTask, expensePermTask, employeePermTask);

                // 8c. Find root employee & role for this tenant.
                var rootEmployee = await context.Employees
                    .Include(e => e.ApplicationUser)
                    .FirstOrDefaultAsync(e => e.ApplicationUser != null && (e.ApplicationUser.IsRootUser ?? false) && e.TenantId == tenantId);

                if (rootEmployee == null)
                {
                    _logger.LogWarning("No root employee for Tenant={TenantId}.", tenantId);
                    await transaction.CommitAsync();
                    return ApiResponse<object>.SuccessResponse(newSubscription, "Tenant subscription updated (no root employee)", 200);
                }

                var rootRoleId = await context.EmployeeRoleMappings
                    .AsNoTracking()
                    .Where(er => er.EmployeeId == rootEmployee.Id && er.TenantId == tenantId)
                    .Select(er => er.RoleId)
                    .FirstOrDefaultAsync();

                if (rootRoleId == Guid.Empty)
                {
                    _logger.LogWarning("No root role for Employee={EmployeeId}, Tenant={TenantId}.", rootEmployee.Id, tenantId);
                    await transaction.CommitAsync();
                    return ApiResponse<object>.SuccessResponse(newSubscription, "Tenant subscription updated (no root role)", 200);
                }

                var dbOldRolePerms = await context.RolePermissionMappings.Where(x => x.ApplicationRoleId == rootRoleId).ToListAsync();
                var oldPermIds = dbOldRolePerms.Select(rp => rp.FeaturePermissionId).ToList();

                // 8d. Prepare add and remove permission lists.
                var newPermissionIds = new List<Guid>();
                var revokePermissionIds = new List<Guid>();
                var employeePerms = employeePermTask.Result;
                var isOldEmployeePermissionIdExist = oldPermIds.Any(fp => employeePerms.Contains(fp));

                void ProcessPerms(bool? enabled, List<Guid> ids)
                {
                    var isOldPermissionIdExist = oldPermIds.Any(fp => ids.Contains(fp) && !employeePerms.Contains(fp));

                    if (enabled == true && !isOldPermissionIdExist) newPermissionIds.AddRange(ids);
                    if (enabled == true && !isOldEmployeePermissionIdExist) newPermissionIds.AddRange(ids);
                    if (enabled == false && isOldPermissionIdExist)
                        revokePermissionIds.AddRange(ids);
                }
                ProcessPerms(features.Modules?.ProjectManagement?.Enabled, projectPermTask.Result);
                ProcessPerms(features.Modules?.Attendance?.Enabled, attendancePermTask.Result);
                ProcessPerms(features.Modules?.Directory?.Enabled, directoryPermTask.Result);
                ProcessPerms(features.Modules?.Expense?.Enabled, expensePermTask.Result);

                newPermissionIds = newPermissionIds.Distinct().ToList();
                revokePermissionIds = revokePermissionIds.Distinct().ToList();


                // 8e. Prevent accidental loss of basic employee permissions.
                if ((features.Modules?.ProjectManagement?.Enabled == true ||
                    features.Modules?.Attendance?.Enabled == true ||
                    features.Modules?.Directory?.Enabled == true ||
                    features.Modules?.Expense?.Enabled == true) && isOldEmployeePermissionIdExist)
                {
                    revokePermissionIds = revokePermissionIds.Where(pid => !employeePerms.Contains(pid)).ToList();
                }

                // 8f. Prepare permission-mapping records to add/remove.
                var mappingsToRemove = dbOldRolePerms.Where(rp => revokePermissionIds.Contains(rp.FeaturePermissionId)).ToList();
                var mappingsToAdd = newPermissionIds
                    .Where(pid => !oldPermIds.Contains(pid))
                    .Select(pid => new RolePermissionMappings { ApplicationRoleId = rootRoleId, FeaturePermissionId = pid })
                    .ToList();

                if (mappingsToAdd.Any())
                {
                    context.RolePermissionMappings.AddRange(mappingsToAdd);
                    _logger.LogInfo("Permissions granted: {Count} for Role={RoleId}", mappingsToAdd.Count, rootRoleId);
                }
                if (mappingsToRemove.Any())
                {
                    context.RolePermissionMappings.RemoveRange(mappingsToRemove);
                    _logger.LogInfo("Permissions revoked: {Count} for Role={RoleId}", mappingsToRemove.Count, rootRoleId);
                }

                var _cache = scope.ServiceProvider.GetRequiredService<CacheUpdateHelper>();
                await _cache.ClearAllEmployeesFromCacheByTenantId(tenant.Id);

                var _masteData = scope.ServiceProvider.GetRequiredService<MasterDataService>();

                if (features.Modules?.ProjectManagement?.Enabled ?? false)
                {
                    var workCategoryMaster = _masteData.GetWorkCategoriesData(tenant.Id);
                    var workStatusMaster = _masteData.GetWorkStatusesData(tenant.Id);

                    var workCategoryTask = Task.Run(async () =>
                    {
                        await using var _context = await _dbContextFactory.CreateDbContextAsync();
                        return await _context.WorkCategoryMasters.AnyAsync(wc => wc.IsSystem && wc.TenantId == tenant.Id);
                    });
                    var workStatusTask = Task.Run(async () =>
                    {
                        await using var _context = await _dbContextFactory.CreateDbContextAsync();
                        return await _context.WorkStatusMasters.AnyAsync(ws => ws.IsSystem && ws.TenantId == tenant.Id);
                    });

                    await Task.WhenAll(workCategoryTask, workStatusTask);

                    var workCategoryExist = workCategoryTask.Result;
                    var workStatusExist = workStatusTask.Result;
                    if (!workCategoryExist)
                    {
                        context.WorkCategoryMasters.AddRange(workCategoryMaster);
                    }
                    if (!workStatusExist)
                    {
                        context.WorkStatusMasters.AddRange(workStatusMaster);
                    }
                }
                if (features.Modules?.Expense?.Enabled ?? false)
                {
                    var expenseCategoryMaster = _masteData.GetExpenseCategoryData(tenant.Id);

                    var expensesTypeNames = expenseCategoryMaster.Select(et => et.Name).ToList();
                    var expenseCategoryExist = await context.ExpenseCategoryMasters.AnyAsync(et => expensesTypeNames.Contains(et.Name) && et.TenantId == tenant.Id);

                    if (!expenseCategoryExist)
                    {
                        context.ExpenseCategoryMasters.AddRange(expenseCategoryMaster);
                    }
                }

                await context.SaveChangesAsync();
                await transaction.CommitAsync();

                _logger.LogInfo("Tenant subscription and permissions updated: Tenant={TenantId}", tenantId);

                return ApiResponse<object>.SuccessResponse(newSubscription, "Tenant subscription successfully updated", 200);
            }
            catch (DbUpdateException dbEx)
            {
                await transaction.RollbackAsync();
                _logger.LogError(dbEx, "Database exception updating subscription for TenantId={TenantId}", tenantId);
                return ApiResponse<object>.ErrorResponse("Internal database error", ExceptionMapper(dbEx), 500);
            }
            catch (Exception ex)
            {
                await transaction.RollbackAsync();
                _logger.LogError(ex, "General exception for TenantId={TenantId}", tenantId);
                return ApiResponse<object>.ErrorResponse("Internal error occurred", ExceptionMapper(ex), 500);
            }
        }

        #region =================================================================== Helper Functions ===================================================================
        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,
                }
            };
        }
        private bool IsBase64String(string? input)
        {
            if (string.IsNullOrWhiteSpace(input))
            {
                return false;
            }

            string base64Data = input;
            const string dataUriMarker = "base64,";
            int markerIndex = input.IndexOf(dataUriMarker, StringComparison.Ordinal);

            // If the marker is found, extract the actual Base64 data
            if (markerIndex >= 0)
            {
                base64Data = input.Substring(markerIndex + dataUriMarker.Length);
            }

            // Now, validate the extracted payload
            base64Data = base64Data.Trim();

            // Check for valid length (must be a multiple of 4) and non-empty
            if (base64Data.Length == 0 || base64Data.Length % 4 != 0)
            {
                return false;
            }

            // The most reliable test is to simply try to convert it.
            // The .NET converter is strict and will throw a FormatException
            // for invalid characters or incorrect padding.
            try
            {
                Convert.FromBase64String(base64Data);
                return true;
            }
            catch (FormatException)
            {
                // The string is not a valid Base64 payload.
                return false;
            }
        }

        /// <summary>
        /// Handles the creation and persistence of SubscriptionPlanDetails for a particular frequency.
        /// </summary>
        private async Task<ApiResponse<SubscriptionPlanVM>> CreateSubscriptionPlanDetails(SubscriptionPlanDetailsDto? model, SubscriptionPlan plan, Employee loggedInEmployee, PLAN_FREQUENCY frequency)
        {
            if (model == null)
            {
                _logger.LogInfo("No plan detail provided for {Frequency} - skipping.", frequency);
                return ApiResponse<SubscriptionPlanVM>.ErrorResponse("Invalid", "No data provided for this frequency", 400);
            }

            await using var _dbContext = await _dbContextFactory.CreateDbContextAsync();

            // Fetch currency master record
            var currencyMaster = await _dbContext.CurrencyMaster.AsNoTracking().FirstOrDefaultAsync(c => c.Id == model.CurrencyId);
            if (currencyMaster == null)
            {
                _logger.LogWarning("Currency with Id {CurrencyId} not found for plan {PlanId}/{Frequency}.", model.CurrencyId, plan.Id, frequency);
                return ApiResponse<SubscriptionPlanVM>.ErrorResponse("Currency not found", "Specified currency not found", 404);
            }

            // Map to entity and create related feature details
            var planDetails = _mapper.Map<SubscriptionPlanDetails>(model);
            var features = _mapper.Map<FeatureDetails>(model.Features);

            try
            {
                await _featureDetailsHelper.AddFeatureDetails(features);
                _logger.LogInfo("FeatureDetails for plan {PlanId}/{Frequency} saved in MongoDB.", plan.Id, frequency);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Exception occurred while saving features in MongoDB for {PlanId}/{Frequency}.", plan.Id, frequency);
                return ApiResponse<SubscriptionPlanVM>.ErrorResponse("Internal error occurred", ExceptionMapper(ex), 500);
            }

            planDetails.PlanId = plan.Id;
            planDetails.Frequency = frequency;
            planDetails.FeaturesId = features.Id;
            planDetails.CreatedById = loggedInEmployee.Id;
            planDetails.CreateAt = DateTime.UtcNow;

            _dbContext.SubscriptionPlanDetails.Add(planDetails);

            // Prepare view model
            var VM = _mapper.Map<SubscriptionPlanVM>(planDetails);
            VM.PlanName = plan.PlanName;
            VM.Description = plan.Description;
            VM.Features = features;
            VM.Currency = currencyMaster;

            try
            {
                await _dbContext.SaveChangesAsync();
                _logger.LogInfo("Subscription plan details for {PlanId}/{Frequency} saved to SQL.", plan.Id, frequency);
            }
            catch (DbUpdateException dbEx)
            {
                _logger.LogError(dbEx, "Database exception occurred while saving plan details for {PlanId}/{Frequency}.", plan.Id, frequency);
                return ApiResponse<SubscriptionPlanVM>.ErrorResponse("Internal error occurred", ExceptionMapper(dbEx), 500);
            }

            return ApiResponse<SubscriptionPlanVM>.SuccessResponse(VM, "Success", 200);
        }

        private async Task ClearPermissionForTenant(Guid tenantId)
        {
            await using var _context = await _dbContextFactory.CreateDbContextAsync();
            using var scope = _serviceScopeFactory.CreateScope();

            var _cache = scope.ServiceProvider.GetRequiredService<CacheUpdateHelper>();
            var _cacheLogger = scope.ServiceProvider.GetRequiredService<ILoggingService>();

            var employeeIds = await _context.Employees.Where(e => e.TenantId == tenantId).Select(e => e.Id).ToListAsync();
            await _cache.ClearAllEmployeesFromCacheByEmployeeIds(employeeIds, tenantId);
            _cacheLogger.LogInfo("{EmployeeCount} number of employee deleted", employeeIds.Count);
        }

        #endregion
    }
}
