using AutoMapper;
using Marco.Pms.DataAccess.Data;
using Marco.Pms.Model.Dtos.Organization;
using Marco.Pms.Model.Employees;
using Marco.Pms.Model.Entitlements;
using Marco.Pms.Model.OrganizationModel;
using Marco.Pms.Model.Utilities;
using Marco.Pms.Model.ViewModels.Activities;
using Marco.Pms.Model.ViewModels.Master;
using Marco.Pms.Model.ViewModels.Organization;
using Marco.Pms.Model.ViewModels.Projects;
using Marco.Pms.Services.Helpers;
using Marco.Pms.Services.Service;
using MarcoBMS.Services.Helpers;
using MarcoBMS.Services.Service;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System.Net;

namespace Marco.Pms.Services.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    [Authorize]
    public class OrganizationController : ControllerBase
    {
        private readonly IDbContextFactory<ApplicationDbContext> _dbContextFactory;
        private readonly IServiceScopeFactory _serviceScope;
        private readonly UserHelper _userHelper;
        private readonly Guid tenantId;
        private readonly IMapper _mapper;
        private readonly Guid loggedOrganizationId;
        private readonly ILoggingService _logger;

        private static readonly Guid PMCProvider = Guid.Parse("b1877a3b-8832-47b1-bbe3-dc7e98672f49");
        private static readonly Guid ServiceProvider = Guid.Parse("5ee49bcd-b6d3-482f-9aaf-484afe04abec");
        private static readonly Guid SubContractorProvider = Guid.Parse("a283356a-9b02-4029-afb7-e65c703efdd4");
        private static readonly Guid superTenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26");
        public OrganizationController(IDbContextFactory<ApplicationDbContext> dbContextFactory,
            IServiceScopeFactory serviceScope,
            UserHelper userHelper,
            ILoggingService logger,
            IMapper mapper)
        {
            _dbContextFactory = dbContextFactory ?? throw new ArgumentNullException(nameof(dbContextFactory));
            _serviceScope = serviceScope ?? throw new ArgumentNullException(nameof(serviceScope));
            _userHelper = userHelper ?? throw new ArgumentNullException(nameof(userHelper));
            _logger = logger ?? throw new ArgumentNullException(nameof(logger));
            _mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
            loggedOrganizationId = _userHelper.GetCurrentOrganizationId();
            tenantId = userHelper.GetTenantId();
        }
        #region =================================================================== Get Functions ===================================================================

        [HttpGet("list")]
        public async Task<IActionResult> GetOrganizarionListAsync([FromQuery] string? searchString, [FromQuery] double? sprid, [FromQuery] bool active = true,
            [FromQuery] int pageNumber = 1, [FromQuery] int pageSize = 20)
        {
            _logger.LogDebug("Fetching organization list. SearchString: {SearchString}, SPRID: {SPRID}, Active: {Active}, Page: {PageNumber}, Size: {PageSize}",
                searchString ?? "", sprid ?? 0, active, pageNumber, pageSize);

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

            var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();

            // Base query filtering by active status
            IQueryable<Organization> organizationQuery = _context.Organizations.Where(o => o.IsActive == active);

            if (sprid.HasValue)
            {
                // Filter by SPRID if provided
                organizationQuery = organizationQuery.Where(o => o.SPRID == sprid.Value);
                _logger.LogDebug("Filtering organizations by SPRID: {SPRID}", sprid.Value);
            }
            else
            {
                // Get organization IDs mapped to current tenant that are active
                var organizationIdsTask = Task.Run(async () =>
                {
                    await using var context = await _dbContextFactory.CreateDbContextAsync();
                    return await context.TenantOrgMappings
                    .Where(to => to.TenantId == tenantId && to.IsActive)
                    .Select(to => to.OrganizationId)
                    .ToListAsync();
                });

                var tenantTask = Task.Run(async () =>
                {
                    await using var context = await _dbContextFactory.CreateDbContextAsync();
                    return await context.Tenants
                    .FirstOrDefaultAsync(t => t.Id == tenantId && t.IsActive);
                });

                await Task.WhenAll(organizationIdsTask, tenantTask);

                var organizationIds = organizationIdsTask.Result;
                var tenant = tenantTask.Result;

                if (tenant == null)
                {
                    _logger.LogWarning("Tenant {TenantId} is not found", tenantId);
                    return NotFound(ApiResponse<object>.ErrorResponse("Tenant not found", "Tenant not found in database", 404));
                }

                organizationIds.Add(tenant.OrganizationId);

                organizationIds = organizationIds.Distinct().ToList();

                organizationQuery = organizationQuery.Where(o => organizationIds.Contains(o.Id));
                _logger.LogDebug("Filtering organizations by tenant's mapped IDs count: {Count}", organizationIds.Count);

                if (!string.IsNullOrWhiteSpace(searchString))
                {
                    // Filter by search string on organization name -- extend here if needed
                    organizationQuery = organizationQuery.Where(o => o.Name.Contains(searchString));
                    _logger.LogDebug("Filtering organizations by search string: {SearchString}", searchString);
                }
            }

            // Get total count for pagination
            var totalCount = await organizationQuery.CountAsync();
            var totalPages = (int)Math.Ceiling((double)totalCount / pageSize);

            // Fetch page of organizations sorted by name
            var organizations = await organizationQuery
                .OrderBy(o => o.Name)
                .Skip((pageNumber - 1) * pageSize)
                .Take(pageSize)
                .ToListAsync();

            if (!organizations.Any() && !sprid.HasValue)
            {
                organizations = await _context.Tenants.AsNoTracking()
                    .Include(t => t.Organization)
                    .Where(t => t.Id == tenantId && t.OrganizationId == loggedInEmployee.OrganizationId && t.Organization != null && t.IsActive)
                    .Select(t => t.Organization!).ToListAsync();
            }

            // Collect creator and updater employee IDs
            var createdByIds = organizations.Where(o => o.CreatedById != null).Select(o => o.CreatedById!.Value).Distinct().ToList();
            var updatedByIds = organizations.Where(o => o.UpdatedById != null).Select(o => o.UpdatedById!.Value).Distinct().ToList();

            // Fetch corresponding employee details in one query
            var employeeIds = createdByIds.Union(updatedByIds).ToList();
            var employees = await _context.Employees.Where(e => employeeIds.Contains(e.Id)).ToListAsync();

            // Map data to view models including created and updated by employees
            var vm = organizations.Select(o =>
            {
                var orgVm = _mapper.Map<OrganizationVM>(o);
                orgVm.CreatedBy = employees.Where(e => e.Id == o.CreatedById).Select(e => _mapper.Map<BasicEmployeeVM>(e)).FirstOrDefault();
                orgVm.UpdatedBy = employees.Where(e => e.Id == o.UpdatedById).Select(e => _mapper.Map<BasicEmployeeVM>(e)).FirstOrDefault();
                return orgVm;
            }).ToList();

            var response = new
            {
                CurrentPage = pageNumber,
                TotalPages = totalPages,
                TotalEntities = totalCount,
                Data = vm,
            };

            _logger.LogInfo("Fetched {Count} organizations (Page {PageNumber} of {TotalPages})", vm.Count, pageNumber, totalPages);

            return Ok(ApiResponse<object>.SuccessResponse(response, "Successfully fetched the organization list", 200));
        }

        [HttpGet("details/{id}")]
        public async Task<IActionResult> GetOrganizationDetailsAsync(Guid id)
        {
            _logger.LogDebug("Started fetching details for OrganizationId: {OrganizationId}", id);

            await using var _context = await _dbContextFactory.CreateDbContextAsync();
            try
            {
                // Get the logged-in employee (for filter/permission checks)
                var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();

                // Fetch the organization entity by Id
                var organization = await _context.Organizations.FirstOrDefaultAsync(o => o.Id == id);
                if (organization == null)
                {
                    _logger.LogWarning("Organization not found for OrganizationId: {OrganizationId}", id);
                    return NotFound(ApiResponse<object>.ErrorResponse("Organization not found", "Organization not found", 404));
                }

                // Fetch CreatedBy employee (with JobRole)
                var createdByTask = Task.Run(async () =>
                {
                    await using var context = await _dbContextFactory.CreateDbContextAsync();
                    return await context.Employees
                    .Include(e => e.JobRole)
                    .FirstOrDefaultAsync(e => e.Id == organization.CreatedById);
                });

                // Fetch UpdatedBy employee (with JobRole)
                var updatedByTask = Task.Run(async () =>
                {
                    if (organization.UpdatedById.HasValue)
                    {
                        await using var context = await _dbContextFactory.CreateDbContextAsync();
                        return await context.Employees
                        .Include(e => e.JobRole)
                        .FirstOrDefaultAsync(e => e.Id == organization.UpdatedById);
                    }
                    return null;
                });

                // Fetch the organization's service mappings and corresponding services
                var orgServiceMappingTask = Task.Run(async () =>
                {
                    await using var context = await _dbContextFactory.CreateDbContextAsync();
                    return await context.OrgServiceMappings
                    .Include(os => os.Service)
                    .Where(os => os.OrganizationId == id).ToListAsync();
                });

                // Fetch active employees in the organization
                var employeeListTask = Task.Run(async () =>
                {
                    await using var context = await _dbContextFactory.CreateDbContextAsync();
                    return await context.Employees
                    .Where(e => e.OrganizationId == id && e.IsActive).ToListAsync();
                });

                await Task.WhenAll(createdByTask, updatedByTask, orgServiceMappingTask, employeeListTask);

                var createdByEmployee = createdByTask.Result;
                var updatedByEmployee = updatedByTask.Result;
                var orgServiceMappings = orgServiceMappingTask.Result;
                var employeeList = employeeListTask.Result;

                var activeEmployeeCount = employeeList.Count;
                var activeApplicationUserCount = employeeList.Count(e => e.HasApplicationAccess);

                // Start query for projects mapped to this organization (including project and service info)
                var baseProjectOrgMappingQuery = _context.ProjectOrgMappings
                    .Include(po => po.ProjectService)
                        .ThenInclude(ps => ps!.Service)
                    .Include(po => po.ProjectService)
                        .ThenInclude(ps => ps!.Project)
                    .Where(po => po.OrganizationId == id && po.ProjectService != null);

                // If logged-in employee is not from the requested organization, restrict projects to those also mapped to their org
                List<ProjectOrgMapping> projectOrgMappings;
                if (loggedInEmployee.OrganizationId != id)
                {
                    var projectIds = await _context.ProjectOrgMappings
                        .Include(po => po.ProjectService)
                        .Where(po => po.OrganizationId == loggedInEmployee.OrganizationId && po.ProjectService != null)
                        .Select(po => po.ProjectService!.ProjectId)
                        .ToListAsync();

                    projectOrgMappings = await baseProjectOrgMappingQuery
                        .Where(po => projectIds.Contains(po.ProjectService!.ProjectId))
                        .ToListAsync();
                }
                else
                {
                    projectOrgMappings = await baseProjectOrgMappingQuery.ToListAsync();
                }

                // Map results to output view model
                var response = _mapper.Map<OrganizationDetailsVM>(organization);
                response.ActiveApplicationUserCount = activeApplicationUserCount;
                response.ActiveEmployeeCount = activeEmployeeCount;
                response.CreatedBy = _mapper.Map<BasicEmployeeVM>(createdByEmployee);
                response.UpdatedBy = _mapper.Map<BasicEmployeeVM>(updatedByEmployee);
                response.Projects = _mapper.Map<List<ProjectServiceMappingVM>>(projectOrgMappings.Select(po => po.ProjectService).ToList());
                response.Services = orgServiceMappings.Where(os => os.Service != null).Select(os => os.Service!).ToList();

                _logger.LogInfo("Fetched organization details for OrganizationId: {OrganizationId}, Employee count: {EmployeeCount}, App user count: {AppUserCount}, Project count: {ProjectCount}",
                    id, activeEmployeeCount, activeApplicationUserCount, response.Projects.Count);

                return Ok(ApiResponse<object>.SuccessResponse(response, "Successfully fetched the organization details", 200));
            }
            catch (DbUpdateException dbEx)
            {
                _logger.LogError(dbEx, "Database exception while fetching details for OrganizationId: {OrganizationId}", id);
                return StatusCode(500, ApiResponse<object>.ErrorResponse("Internal error", "A database exception occurred", 500));
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Unhandled exception while fetching details for OrganizationId: {OrganizationId}", id);
                return StatusCode(500, ApiResponse<object>.ErrorResponse("Internal error", "An internal exception occurred", 500));
            }
        }

        #endregion

        #region =================================================================== Post Functions ===================================================================

        [HttpPost("create")]
        public async Task<IActionResult> CreateOrganizationAsync([FromBody] CreateOrganizationDto model)
        {
            await using var _context = await _dbContextFactory.CreateDbContextAsync();
            using var scope = _serviceScope.CreateScope(); // Create scope for scoped services
            await using var transaction = await _context.Database.BeginTransactionAsync();

            try
            {
                var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();

                // Concurrent permission check and organization existence check
                var hasPermissionTask = Task.Run(async () =>
                {
                    var permissionService = scope.ServiceProvider.GetRequiredService<PermissionServices>();
                    return await permissionService.HasPermission(PermissionsMaster.AddOrganization, loggedInEmployee.Id);
                });

                var isPrimaryOrganizationTask = Task.Run(async () =>
                {
                    await using var context = await _dbContextFactory.CreateDbContextAsync();
                    return await context.Tenants.AnyAsync(t => t.OrganizationId == loggedInEmployee.OrganizationId);
                });

                await Task.WhenAll(hasPermissionTask, isPrimaryOrganizationTask);

                bool hasPermission = hasPermissionTask.Result;
                bool isPrimaryOrganization = isPrimaryOrganizationTask.Result;

                // Check user access permission
                if (!hasPermission && !isPrimaryOrganization)
                {
                    _logger.LogWarning("User {EmployeeId} attempted to create a new organization without permission", loggedInEmployee.Id);
                    return StatusCode(403, ApiResponse<object>.ErrorResponse("Access Denied", "You do not have permission to create new organization.", 403));
                }

                // 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 = _mapper.Map<Organization>(model);
                organization.SPRID = lastSPRID + 1;
                organization.CreatedAt = DateTime.UtcNow;
                organization.CreatedById = loggedInEmployee.Id;
                organization.IsActive = true;

                _context.Organizations.Add(organization);

                // Create mapping for organization tenant
                var newOrganizationTenantMapping = new TenantOrgMapping
                {
                    OrganizationId = organization.Id,
                    SPRID = organization.SPRID,
                    AssignedDate = DateTime.UtcNow,
                    IsActive = true,
                    AssignedById = loggedInEmployee.Id,
                    TenantId = tenantId
                };
                _context.TenantOrgMappings.Add(newOrganizationTenantMapping);

                // Prepare user creation for identity
                var user = new ApplicationUser
                {
                    UserName = model.Email,
                    Email = model.Email,
                    EmailConfirmed = true
                };

                var configuration = scope.ServiceProvider.GetRequiredService<IConfiguration>();
                var emailSender = scope.ServiceProvider.GetRequiredService<IEmailSender>();
                var userManager = scope.ServiceProvider.GetRequiredService<UserManager<ApplicationUser>>();

                // Create Identity user with a default password (recommend to improve password handling)
                var result = await userManager.CreateAsync(user, "User@123");
                if (!result.Succeeded)
                {
                    _logger.LogWarning("Failed to create identity user for email {Email}: {Errors}", model.Email, result.Errors);
                    return BadRequest(ApiResponse<object>.ErrorResponse("Failed to create user", result.Errors, 400));
                }

                // Get admin job role or fallback role of the tenant
                var jobRole = await _context.JobRoles.FirstOrDefaultAsync(jr => jr.Name == "Admin" && jr.TenantId == tenantId)
                              ?? await _context.JobRoles.FirstOrDefaultAsync(jr => jr.TenantId == tenantId);

                // Parse full name safely (consider improving split logic for multi-part names)
                var fullName = model.ContactPerson.Split(' ', StringSplitOptions.RemoveEmptyEntries);

                Employee newEmployee = new Employee
                {
                    FirstName = fullName.Length > 0 ? fullName[0] : string.Empty,
                    LastName = fullName.Length > 1 ? fullName[^1] : string.Empty,
                    Email = model.Email,
                    PermanentAddress = model.Address,
                    CurrentAddress = model.Address,
                    PhoneNumber = model.ContactNumber,
                    ApplicationUserId = user.Id,
                    JobRoleId = jobRole?.Id ?? Guid.Empty,
                    IsActive = true,
                    IsSystem = false,
                    IsPrimary = true,
                    OrganizationId = organization.Id,
                    HasApplicationAccess = true
                };

                _context.Employees.Add(newEmployee);

                // Map organization services
                if (model.ServiceIds?.Any() ?? false)
                {
                    var serviceOrgMappings = model.ServiceIds.Select(s => new OrgServiceMapping
                    {
                        ServiceId = s,
                        OrganizationId = organization.Id
                    }).ToList();

                    _context.OrgServiceMappings.AddRange(serviceOrgMappings);
                }

                // Persist all changes
                await _context.SaveChangesAsync();

                await transaction.CommitAsync();

                // Send user registration email with password reset link
                var token = await userManager.GeneratePasswordResetTokenAsync(user);
                var resetLink = $"{configuration["AppSettings:WebFrontendUrl"]}/reset-password?token={WebUtility.UrlEncode(token)}";
                if (!string.IsNullOrEmpty(newEmployee.FirstName))
                {
                    await emailSender.SendResetPasswordEmailOnRegister(user.Email, newEmployee.FirstName, resetLink);
                }

                // Prepare response DTO
                var response = _mapper.Map<OrganizationVM>(organization);
                response.CreatedBy = _mapper.Map<BasicEmployeeVM>(loggedInEmployee);

                return Ok(ApiResponse<object>.SuccessResponse(response, "Successfully created the organization", 200));
            }
            catch (DbUpdateException dbEx)
            {
                await transaction.RollbackAsync();
                _logger.LogError(dbEx, "Database exception occurred while creating organization");
                return StatusCode(500, ApiResponse<object>.ErrorResponse("Internal error", "A database exception occurred", 500));
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Unexpected exception occurred while creating organization");
                return StatusCode(500, ApiResponse<object>.ErrorResponse("Internal error", "An unexpected error occurred", 500));
            }
        }

        [HttpPost("assign/project")]
        public async Task<IActionResult> AssignOrganizationToProjectAsync([FromBody] AssignOrganizationDto model)
        {
            _logger.LogDebug("Started assigning organization {OrganizationId} to project {ProjectId} with service IDs {@ServiceIds}",
                model.OrganizationId, model.ProjectId, model.ServiceIds);

            // Create DbContext for the method scope
            await using var _context = await _dbContextFactory.CreateDbContextAsync();

            // Begin a database transaction
            await using var transaction = await _context.Database.BeginTransactionAsync();

            try
            {
                // Get currently logged in employee
                var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
                var today = DateTime.UtcNow.Date;

                // Fetch all needed entities concurrently using the single context
                var projectServicesTask = Task.Run(async () =>
                {
                    await using var context = await _dbContextFactory.CreateDbContextAsync();
                    return await context.ProjectServiceMappings
                    .Where(sp => model.ServiceIds.Contains(sp.ServiceId) && sp.ProjectId == model.ProjectId && sp.IsActive).ToListAsync();
                });

                var projectOrgMappingsTask = Task.Run(async () =>
                {
                    await using var context = await _dbContextFactory.CreateDbContextAsync();
                    return await context.ProjectOrgMappings
                    .Include(po => po.ProjectService)
                    .Where(po => po.ProjectService != null && model.ServiceIds.Contains(po.ProjectService.ServiceId) && po.ProjectService.ProjectId == model.ProjectId).ToListAsync();
                });

                var serviceTask = Task.Run(async () =>
                {
                    await using var context = await _dbContextFactory.CreateDbContextAsync();
                    return await context.ServiceMasters.Where(s => model.ServiceIds.Contains(s.Id) && s.TenantId == tenantId).ToListAsync();
                });

                var orgTypeTask = Task.Run(async () =>
                {
                    await using var context = await _dbContextFactory.CreateDbContextAsync();
                    return await context.OrgTypeMasters.FirstOrDefaultAsync(o => o.Id == model.OrganizationId);
                });

                var organizationTask = Task.Run(async () =>
                {
                    await using var context = await _dbContextFactory.CreateDbContextAsync();
                    return await context.Organizations.FirstOrDefaultAsync(o => o.Id == model.OrganizationId);
                });

                var parentOrgTask = Task.Run(async () =>
                {
                    await using var context = await _dbContextFactory.CreateDbContextAsync();
                    return await context.Organizations.FirstOrDefaultAsync(o => o.Id == model.ParentOrganizationId);
                });

                var projectTask = Task.Run(async () =>
                {
                    await using var context = await _dbContextFactory.CreateDbContextAsync();
                    return await context.Projects.FirstOrDefaultAsync(p => p.Id == model.ProjectId);
                });

                var isPMCTask = Task.Run(async () =>
                {
                    await using var context = await _dbContextFactory.CreateDbContextAsync();
                    return await context.Projects.AnyAsync(p => p.Id == model.ProjectId && p.PMCId == loggedInEmployee.OrganizationId);
                });

                var isServiceProviderTask = Task.Run(async () =>
                {
                    await using var context = await _dbContextFactory.CreateDbContextAsync();
                    return await context.ProjectOrgMappings.AnyAsync(p => p.Id == model.ProjectId && p.OrganizationId == loggedInEmployee.OrganizationId
                    && p.OrganizationTypeId == ServiceProvider);
                });

                await Task.WhenAll(projectTask, organizationTask, parentOrgTask, serviceTask, orgTypeTask, projectServicesTask, projectOrgMappingsTask, isPMCTask, isServiceProviderTask);

                var project = projectTask.Result;
                var organization = organizationTask.Result;
                var parentOrganization = parentOrgTask.Result;
                var services = serviceTask.Result;
                var organizationType = orgTypeTask.Result;
                var projectServices = projectServicesTask.Result;
                var projectOrganizations = projectOrgMappingsTask.Result;
                var isPMC = isPMCTask.Result;
                var isServiceProvider = isServiceProviderTask.Result;

                // Validation checks
                if (organization == null)
                {
                    _logger.LogWarning("Organization with ID {OrganizationId} not found.", model.OrganizationId);
                    return NotFound(ApiResponse<object>.ErrorResponse("Organization not found", "Organization not found in database", 404));
                }
                if (project == null)
                {
                    _logger.LogWarning("Project with ID {ProjectId} not found.", model.ProjectId);
                    return NotFound(ApiResponse<object>.ErrorResponse("Project not found", "Project not found in database", 404));
                }
                if (services == null || !services.Any())
                {
                    _logger.LogWarning("No services found for Service IDs {@ServiceIds}.", model.ServiceIds);
                    return NotFound(ApiResponse<object>.ErrorResponse("Project Service not found", "Project Service not found in database", 404));
                }

                // Check whether mapping exists between service provider organization and tenant
                var serviceProviderTenantMapping = await _context.TenantOrgMappings
                    .FirstOrDefaultAsync(spt => spt.OrganizationId == model.OrganizationId && spt.TenantId == project.TenantId && spt.IsActive);

                if (serviceProviderTenantMapping == null)
                {
                    var newMapping = new TenantOrgMapping
                    {
                        OrganizationId = organization.Id,
                        SPRID = organization.SPRID,
                        AssignedDate = today,
                        IsActive = true,
                        AssignedById = loggedInEmployee.Id,
                        TenantId = project.TenantId
                    };
                    _context.TenantOrgMappings.Add(newMapping);
                    _logger.LogInfo("Created new TenantOrgMapping for OrganizationId {OrganizationId} and TenantId {TenantId}",
                        organization.Id, project.TenantId);
                }

                // Access control validations
                if (isPMC && model.OrganizationTypeId != ServiceProvider && model.OrganizationTypeId != SubContractorProvider)
                {
                    _logger.LogWarning("PMCs cannot assign organization type {OrganizationTypeId}. UserId: {UserId}",
                        model.OrganizationTypeId, loggedInEmployee.Id);
                    return StatusCode(403, ApiResponse<object>.ErrorResponse("Access Denied", "You don't have access to assign this type of organization", 403));
                }
                if (isServiceProvider && model.OrganizationTypeId == ServiceProvider)
                {
                    _logger.LogWarning("Service providers cannot assign organization type {OrganizationTypeId}. UserId: {UserId}",
                        model.OrganizationTypeId, loggedInEmployee.Id);
                    return StatusCode(403, ApiResponse<object>.ErrorResponse("Access Denied", "You don't have access to assign this type of organization", 403));
                }

                var newProjectOrgMappings = new List<ProjectOrgMapping>();
                var newProjectServiceMappings = new List<ProjectServiceMapping>();

                // Loop through each service to create mappings
                foreach (var serviceId in model.ServiceIds)
                {
                    var service = services.FirstOrDefault(s => s.Id == serviceId);
                    if (service == null)
                    {
                        _logger.LogWarning("Service with ID {ServiceId} not found.", serviceId);
                        return NotFound(ApiResponse<object>.ErrorResponse("Service not found", "Service not found in database", 404));
                    }

                    var projectService = projectServices.FirstOrDefault(ps => ps.ServiceId == serviceId);
                    if (projectService == null)
                    {
                        projectService = new ProjectServiceMapping
                        {
                            Id = Guid.NewGuid(),
                            ProjectId = project.Id,
                            ServiceId = serviceId,
                            TenantId = project.TenantId,
                            PlannedStartDate = project.StartDate ?? today,
                            PlannedEndDate = project.EndDate ?? today,
                            ActualStartDate = today,
                            IsActive = true
                        };
                        newProjectServiceMappings.Add(projectService);
                    }

                    // Check if the organization is already assigned for this service
                    var existingAssignment = projectOrganizations.FirstOrDefault(po => po.ProjectService != null
                        && po.ProjectService.ProjectId == project.Id
                        && po.ProjectService.ServiceId == serviceId
                        && po.OrganizationId == model.OrganizationId);

                    if (existingAssignment != null)
                    {
                        _logger.LogWarning("Organization {OrganizationId} is already assigned to project {ProjectId} for service {ServiceId}.",
                            model.OrganizationId, project.Id, serviceId);
                        return Conflict(ApiResponse<object>.ErrorResponse("Organization already assigned", "Organization is already assigned to this project and service", 409));
                    }

                    // Prepare new project-org mapping
                    var projectOrgMapping = new ProjectOrgMapping
                    {
                        ProjectServiceId = projectService.Id,
                        OrganizationId = model.OrganizationId,
                        OrganizationTypeId = model.OrganizationTypeId,
                        ParentOrganizationId = model.ParentOrganizationId ?? loggedInEmployee.OrganizationId,
                        AssignedDate = today,
                        AssignedById = loggedInEmployee.Id,
                        TenantId = project.TenantId
                    };
                    newProjectOrgMappings.Add(projectOrgMapping);
                }

                // Save new project service mappings if any
                if (newProjectServiceMappings.Any())
                {
                    _context.ProjectServiceMappings.AddRange(newProjectServiceMappings);
                    await _context.SaveChangesAsync();
                    _logger.LogInfo("Added {Count} new ProjectServiceMappings for ProjectId {ProjectId}.", newProjectServiceMappings.Count, project.Id);
                }

                // Save new project organization mappings
                _context.ProjectOrgMappings.AddRange(newProjectOrgMappings);
                await _context.SaveChangesAsync();

                // Commit transaction
                await transaction.CommitAsync();

                _logger.LogInfo("Assigned organization {OrganizationId} to project {ProjectId} successfully.", model.OrganizationId, model.ProjectId);

                // Prepare response view models
                var organizationVm = _mapper.Map<BasicOrganizationVm>(organization);
                var parentOrganizationVm = _mapper.Map<BasicOrganizationVm>(parentOrganization);
                var projectVm = _mapper.Map<BasicProjectVM>(project);

                var response = services.Select(s => new AssignOrganizationVm
                {
                    Project = projectVm,
                    OrganizationType = organizationType,
                    Organization = organizationVm,
                    ParentOrganization = parentOrganizationVm,
                    Service = _mapper.Map<ServiceMasterVM>(s)
                }).ToList();

                await AssignApplicationRoleToOrganization(organization.Id, project.TenantId);

                return Ok(ApiResponse<object>.SuccessResponse(response, "Organization successfully assigned to the project", 200));
            }
            catch (DbUpdateException dbEx)
            {
                await transaction.RollbackAsync();
                _logger.LogError(dbEx, "Database exception occurred while assigning organization {OrganizationId} to project {ProjectId}",
                    model.OrganizationId, model.ProjectId);
                return StatusCode(500, ApiResponse<object>.ErrorResponse("Internal error", "A database exception occurred", 500));
            }
            catch (Exception ex)
            {
                await transaction.RollbackAsync();
                _logger.LogError(ex, "Unhandled exception occurred while assigning organization {OrganizationId} to project {ProjectId}",
                    model.OrganizationId, model.ProjectId);
                return StatusCode(500, ApiResponse<object>.ErrorResponse("Internal error", "An internal exception occurred", 500));
            }
        }

        [HttpPost("assign/tenant/{organizationId}")]
        public async Task<IActionResult> AssignOrganizationToTenantAsync(Guid organizationId)
        {
            _logger.LogInfo("Started assigning organization {OrganizationId} to tenant {TenantId}", organizationId, tenantId);

            // Create a DbContext instance for this method scope
            await using var _context = await _dbContextFactory.CreateDbContextAsync();

            // Begin a database transaction
            await using var transaction = await _context.Database.BeginTransactionAsync();

            try
            {
                // Get currently logged in employee for auditing purposes
                var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();

                // Fetch existing tenant-organization mapping if any
                var organizationTenantMapping = await _context.TenantOrgMappings
                    .FirstOrDefaultAsync(spt => spt.OrganizationId == organizationId && spt.TenantId == tenantId && spt.IsActive);

                // Fetch the organization details
                var organization = await _context.Organizations.FirstOrDefaultAsync(o => o.Id == organizationId);

                // Validate organization existence
                if (organization == null)
                {
                    _logger.LogWarning("Organization with ID {OrganizationId} not found.", organizationId);
                    return NotFound(ApiResponse<object>.ErrorResponse("Organization not found", "Organization not found in database", 404));
                }

                if (organizationTenantMapping != null)
                {
                    _logger.LogInfo("Organization {OrganizationId} is already assigned to tenant {TenantId}. No action taken.", organizationId, tenantId);
                    // Commit transaction anyway to complete scope cleanly (optional)
                    await transaction.RollbackAsync();
                    return StatusCode(409, ApiResponse<object>.ErrorResponse("Organization is already assigned to tenant", "Organization is already assigned to tenant", 409));
                }

                // Create new tenant-organization mapping if none exists
                var newMapping = new TenantOrgMapping
                {
                    OrganizationId = organization.Id,
                    SPRID = organization.SPRID,
                    AssignedDate = DateTime.UtcNow,
                    IsActive = true,
                    AssignedById = loggedInEmployee.Id,
                    TenantId = tenantId
                };
                _context.TenantOrgMappings.Add(newMapping);
                await _context.SaveChangesAsync();
                await transaction.CommitAsync();

                _logger.LogInfo("Assigned organization {OrganizationId} to tenant {TenantId} successfully.", organizationId, tenantId);


                // Prepare response view model
                var response = _mapper.Map<BasicOrganizationVm>(organization);

                await AssignApplicationRoleToOrganization(organization.Id, tenantId);

                return Ok(ApiResponse<object>.SuccessResponse(response, "Organization has been assigned to tenant", 200));
            }
            catch (DbUpdateException dbEx)
            {
                await transaction.RollbackAsync();
                _logger.LogError(dbEx, "Database exception occurred while assigning organization {OrganizationId} to tenant {TenantId}.", organizationId, tenantId);
                return StatusCode(500, ApiResponse<object>.ErrorResponse("Internal error", "A database exception has occurred", 500));
            }
            catch (Exception ex)
            {
                await transaction.RollbackAsync();
                _logger.LogError(ex, "Unhandled exception occurred while assigning organization {OrganizationId} to tenant {TenantId}.", organizationId, tenantId);
                return StatusCode(500, ApiResponse<object>.ErrorResponse("Internal error", "An internal exception has occurred", 500));
            }
        }

        #endregion

        #region =================================================================== Put Functions ===================================================================

        [HttpPut("edit/{id}")]
        public async Task<IActionResult> UpdateOrganiationAsync(Guid id, [FromBody] UpdateOrganizationDto model)
        {
            await using var _context = await _dbContextFactory.CreateDbContextAsync();

            try
            {
                // Get the current logged-in employee
                var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();

                _logger.LogDebug("Started updating organization OrganizationId: {OrganizationId} by EmployeeId: {EmployeeId}",
                id, loggedInEmployee.Id);

                // Check if the user is a tenant-level employee and restrict editing to their own org
                var isTenantEmployee = await _context.Tenants.AnyAsync(t => t.Id == tenantId && t.OrganizationId == loggedInEmployee.OrganizationId);
                if (!isTenantEmployee && loggedInEmployee.OrganizationId != id)
                {
                    _logger.LogWarning("Access denied. Tenant-level employee {EmployeeId} attempted to update another organization (OrganizationId: {OrganizationId})",
                        loggedInEmployee.Id, id);
                    return StatusCode(403, ApiResponse<object>.ErrorResponse("Access Denied", "User does not have permission to update the organization", 403));
                }

                // Fetch the active organization entity
                var organization = await _context.Organizations.FirstOrDefaultAsync(o => o.Id == id && o.IsActive);
                if (organization == null)
                {
                    _logger.LogWarning("Organization with Id {OrganizationId} not found or inactive.", id);
                    return NotFound(ApiResponse<object>.ErrorResponse("Organization not found", "Organization not found", 404));
                }

                // Update basic organization fields
                organization.Name = model.Name;
                organization.ContactPerson = model.ContactPerson;
                organization.Address = model.Address;
                organization.ContactNumber = model.ContactNumber;
                organization.UpdatedById = loggedInEmployee.Id;
                organization.UpdatedAt = DateTime.UtcNow;

                // Fetch the primary active employee of the organization
                var employee = await _context.Employees.FirstOrDefaultAsync(e => e.OrganizationId == id && e.IsPrimary && e.IsActive);
                if (employee == null)
                {
                    _logger.LogWarning("Primary employee not found for OrganizationId: {OrganizationId}", id);
                    return NotFound(ApiResponse<object>.ErrorResponse("Primary employee not found", "Primary employee not found", 404));
                }

                // Split contact person's name into first and last names
                var fullName = (model.ContactPerson ?? string.Empty).Split(' ', StringSplitOptions.RemoveEmptyEntries);
                employee.FirstName = fullName.Length > 0 ? fullName[0] : string.Empty;
                employee.LastName = fullName.Length > 1 ? fullName[^1] : string.Empty;
                employee.CurrentAddress = model.Address;
                employee.PermanentAddress = model.Address;
                employee.PhoneNumber = model.ContactNumber;

                // Update organization's service mappings if service IDs are provided
                if (model.ServiceIds?.Any() ?? false)
                {
                    // Fetch existing service mappings (as no tracking for diff logic)
                    var orgServiceMappings = await _context.OrgServiceMappings
                        .AsNoTracking()
                        .Where(os => os.OrganizationId == id)
                        .ToListAsync();

                    var existedServiceIds = orgServiceMappings.Select(os => os.ServiceId).ToList();

                    // Determine new service mappings to add
                    var newServiceIds = model.ServiceIds.Except(existedServiceIds).ToList();
                    var orgServicesToDelete = orgServiceMappings
                        .Where(s => !model.ServiceIds.Contains(s.ServiceId))
                        .ToList();

                    // Add new service mappings
                    if (newServiceIds.Any())
                    {
                        var newMappings = newServiceIds.Select(sid => new OrgServiceMapping
                        {
                            OrganizationId = id,
                            ServiceId = sid
                        });
                        await _context.OrgServiceMappings.AddRangeAsync(newMappings);
                    }

                    // Remove deleted service mappings
                    if (orgServicesToDelete.Any())
                    {
                        _context.OrgServiceMappings.RemoveRange(orgServicesToDelete);
                    }
                }

                await _context.SaveChangesAsync();

                var response = _mapper.Map<OrganizationVM>(organization);

                var createdByEmployee = await _context.Employees.Include(e => e.JobRole).FirstOrDefaultAsync(e => e.Id == organization.CreatedById);
                response.CreatedBy = _mapper.Map<BasicEmployeeVM>(createdByEmployee);
                response.UpdatedBy = _mapper.Map<BasicEmployeeVM>(loggedInEmployee);

                _logger.LogInfo("Successfully updated organization OrganizationId: {OrganizationId} by EmployeeId: {EmployeeId}",
                    id, loggedInEmployee.Id);

                return Ok(ApiResponse<object>.SuccessResponse(response, "Organization updated Successfully", 200));
            }
            catch (DbUpdateException dbEx)
            {
                _logger.LogError(dbEx, "Database exception occurred while updating OrganizationId: {OrganizationId}", id);
                return StatusCode(500, ApiResponse<object>.ErrorResponse("Internal error", "A database exception occurred", 500));
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Unhandled exception occurred while updating OrganizationId: {OrganizationId}", id);
                return StatusCode(500, ApiResponse<object>.ErrorResponse("Internal error", "An internal exception occurred", 500));
            }
        }

        #endregion

        #region =================================================================== Delete Functions ===================================================================

        //[HttpDelete("delete/{id}")]
        //public async Task<IActionResult> DeleteServiceProviderAsync(Guid id, [FromQuery] bool active)
        //{
        //    await using var _context = await _dbContextFactory.CreateDbContextAsync();
        //    using var scope = _serviceScope.CreateScope();

        //    var message = active ? "Restore" : "Delete";

        //    var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
        //    var client = await _context.Clients.FirstOrDefaultAsync(c => c.PrimaryEmployeeId == loggedInEmployee.Id && c.TenantId == tenantId);

        //    if (!(loggedInEmployee.ApplicationUser?.IsRootUser ?? false) && !loggedInEmployee.IsPrimary && client == null)
        //    {
        //        return StatusCode(403, ApiResponse<object>.ErrorResponse("Access Denied", $"You do not have permission to {message}d service provider.", 403));
        //    }

        //    var serviceProvider = await _context.ServiceProviders.FirstOrDefaultAsync(sp => sp.Id == id);
        //    if (serviceProvider == null)
        //    {
        //        return NotFound(ApiResponse<object>.ErrorResponse("Service Provider not Found", "Service Provider not Found in database", 404));
        //    }
        //    if (serviceProvider.IsActive == active)
        //    {
        //        return BadRequest(ApiResponse<object>.ErrorResponse($"Service Provider is already {message}d", $"Service Provider is already {message}d", 400));
        //    }

        //    var employeeIds = await _context.Employees.Where(e => e.ServiceProviderId == id).Select(e => e.Id).ToListAsync();

        //    var isPendingTask = await _context.TaskMembers.AnyAsync(tm => employeeIds.Contains(tm.EmployeeId));

        //    if (isPendingTask && !active)
        //    {
        //        return BadRequest(ApiResponse<object>.ErrorResponse("There is an unfinshed task, Service provider cannot be deleted", "There is an unfinshed task, Service provider cannot be deleted", 400));
        //    }

        //    serviceProvider.IsActive = active;

        //    if (!active)
        //    {
        //        var servicePeroviderTenant = await _context.ServiceProviderTenantMappings.AsNoTracking().Where(spt => spt.ServiceProviderId == id && spt.IsActive).ToListAsync();
        //        var newServiceProviderTenant = servicePeroviderTenant.Select(spt =>
        //        {
        //            spt.IsActive = false;
        //            return spt;

        //        }).ToList();
        //        _context.ServiceProviderTenantMappings.UpdateRange(newServiceProviderTenant);
        //    }

        //    await _context.SaveChangesAsync();
        //    return Ok(ApiResponse<object>.SuccessResponse(new { }, $"Service Provider is {message}d", 200));
        //}
        #endregion

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

        private async Task AssignApplicationRoleToOrganization(Guid organizationId, Guid tenantId)
        {
            if (loggedOrganizationId == organizationId)
            {
                return;
            }
            await using var _context = await _dbContextFactory.CreateDbContextAsync();
            using var scope = _serviceScope.CreateScope();

            var rootEmployee = await _context.Employees
                .Include(e => e.ApplicationUser)
                .FirstOrDefaultAsync(e => e.ApplicationUser != null && e.ApplicationUser.IsRootUser.HasValue && e.ApplicationUser.IsRootUser.Value && e.OrganizationId == organizationId && e.IsPrimary);
            if (rootEmployee == null)
            {
                return;
            }
            string serviceProviderRoleName = "Service Provider Role";

            var serviceProviderRole = await _context.ApplicationRoles.FirstOrDefaultAsync(ar => ar.Role == serviceProviderRoleName && ar.TenantId == tenantId);
            if (serviceProviderRole == null)
            {
                serviceProviderRole = new Model.Roles.ApplicationRole
                {
                    Id = Guid.NewGuid(),
                    Role = serviceProviderRoleName,
                    Description = serviceProviderRoleName,
                    IsSystem = true,
                    TenantId = tenantId
                };
                _context.ApplicationRoles.Add(serviceProviderRole);

                var rolePermissionMappigs = new List<RolePermissionMappings> {
                        new RolePermissionMappings
                        {
                            ApplicationRoleId = serviceProviderRole.Id,
                            FeaturePermissionId = PermissionsMaster.ViewProject
                        },
                        new RolePermissionMappings
                        {
                            ApplicationRoleId = serviceProviderRole.Id,
                            FeaturePermissionId = PermissionsMaster.ViewProjectInfra
                        },
                        new RolePermissionMappings
                        {
                            ApplicationRoleId = serviceProviderRole.Id,
                            FeaturePermissionId = PermissionsMaster.ViewTask
                        },
                        new RolePermissionMappings
                        {
                            ApplicationRoleId = serviceProviderRole.Id,
                            FeaturePermissionId = PermissionsMaster.ViewAllEmployees
                        },
                        new RolePermissionMappings
                        {
                            ApplicationRoleId = serviceProviderRole.Id,
                            FeaturePermissionId = PermissionsMaster.TeamAttendance
                        },
                        new RolePermissionMappings
                        {
                            ApplicationRoleId = serviceProviderRole.Id,
                            FeaturePermissionId = PermissionsMaster.AssignRoles
                        },
                        new RolePermissionMappings
                        {
                            ApplicationRoleId = serviceProviderRole.Id,
                            FeaturePermissionId = PermissionsMaster.ManageProjectInfra
                        },
                        new RolePermissionMappings
                        {
                            ApplicationRoleId = serviceProviderRole.Id,
                            FeaturePermissionId = PermissionsMaster.AssignAndReportProgress
                        },
                        new RolePermissionMappings
                        {
                            ApplicationRoleId = serviceProviderRole.Id,
                            FeaturePermissionId = PermissionsMaster.AddAndEditTask
                        }
                    };
                _context.RolePermissionMappings.AddRange(rolePermissionMappigs);
            }
            _context.EmployeeRoleMappings.Add(new EmployeeRoleMapping
            {
                EmployeeId = rootEmployee.Id,
                RoleId = serviceProviderRole.Id,
                IsEnabled = true,
                TenantId = tenantId
            });

            var _cache = scope.ServiceProvider.GetRequiredService<CacheUpdateHelper>();
            await _cache.ClearAllPermissionIdsByEmployeeID(rootEmployee.Id, tenantId);
        }
        #endregion
    }
}
