using AutoMapper;
using Marco.Pms.DataAccess.Data;
using Marco.Pms.Model.Authentication;
using Marco.Pms.Model.Dtos.Authentication;
using Marco.Pms.Model.Dtos.Util;
using Marco.Pms.Model.Employees;
using Marco.Pms.Model.Entitlements;
using Marco.Pms.Model.Utilities;
using Marco.Pms.Model.ViewModels.Tenant;
using Marco.Pms.Services.Helpers;
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;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;

namespace MarcoBMS.Services.Controllers
{
    [ApiController]
    [Route("api/[controller]")]
    public class AuthController : ControllerBase
    {
        private readonly IDbContextFactory<ApplicationDbContext> _dbContextFactory;
        private readonly IServiceScopeFactory _serviceScopeFactory;
        private readonly UserManager<ApplicationUser> _userManager;
        private readonly JwtSettings _jwtSettings;
        private readonly IConfiguration _configuration;
        private readonly ILoggingService _logger;

        public AuthController(IDbContextFactory<ApplicationDbContext> dbContextFactory,
            IServiceScopeFactory serviceScopeFactory,
            UserManager<ApplicationUser> userManager,
            JwtSettings jwtSettings,
            IConfiguration configuration,
            ILoggingService logger)
        {
            _dbContextFactory = dbContextFactory ?? throw new ArgumentNullException(nameof(dbContextFactory));
            _serviceScopeFactory = serviceScopeFactory ?? throw new ArgumentNullException(nameof(serviceScopeFactory));
            _userManager = userManager ?? throw new ArgumentNullException(nameof(userManager));
            _jwtSettings = jwtSettings ?? throw new ArgumentNullException(nameof(jwtSettings));
            _configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
            _logger = logger ?? throw new ArgumentNullException(nameof(logger));
        }

        // old login APIs

        [HttpPost("login/v1")]
        public async Task<IActionResult> Login([FromBody] LoginDto loginDto)
        {
            await using var _context = await _dbContextFactory.CreateDbContextAsync();
            using var scope = _serviceScopeFactory.CreateScope();
            var _refreshTokenService = scope.ServiceProvider.GetRequiredService<RefreshTokenService>();
            var _employeeHelper = scope.ServiceProvider.GetRequiredService<EmployeeHelper>();
            try
            {

                var user = await _context.ApplicationUsers
                    .FirstOrDefaultAsync(u => u.Email == loginDto.Username || u.UserName == loginDto.Username);

                if (user == null)
                {
                    _logger.LogWarning("Login failed: User not found for input {Username}", loginDto.Username ?? string.Empty);
                    return Unauthorized(ApiResponse<object>.ErrorResponse("Invalid username or password.", "Invalid username or password.", 401));
                }

                // Check if the user is active
                if (!user.IsActive)
                {
                    _logger.LogWarning("Login failed: Inactive user attempted login - UserId: {UserId}", user.Id);
                    return BadRequest(ApiResponse<object>.ErrorResponse("User is inactive", "User is inactive", 400));
                }

                // Ensure the user's email is confirmed
                if (!user.EmailConfirmed)
                {
                    _logger.LogWarning("Login failed: Email not confirmed for UserId: {UserId}", user.Id);
                    return BadRequest(ApiResponse<object>.ErrorResponse("Email not verified", "Your email is not verified, please verify your email", 400));
                }

                // Validate the password
                if (!await _userManager.CheckPasswordAsync(user, loginDto.Password ?? string.Empty))
                {
                    _logger.LogWarning("Login failed: Incorrect password for UserId: {UserId}", user.Id);
                    return Unauthorized(ApiResponse<object>.ErrorResponse("Invalid username or password.", "Invalid username or password.", 401));
                }

                // Retrieve employee details
                var emp = await _employeeHelper.GetEmployeeByApplicationUserID(user.Id);
                if (emp == null)
                {
                    _logger.LogWarning("Login failed: No employee record found for UserId: {UserId}", user.Id);
                    return NotFound(ApiResponse<object>.ErrorResponse("Employee record not found", "Employee not found", 404));
                }

                // Ensure UserName exists for JWT
                if (string.IsNullOrWhiteSpace(user.UserName))
                {
                    _logger.LogWarning("Login failed: Username not found for UserId: {UserId}", user.Id);
                    return NotFound(ApiResponse<object>.ErrorResponse("Username not found", "Username not found", 404));
                }

                var tenants = await _context.Tenants.Where(t => t.OrganizationId == emp.OrganizationId).ToListAsync();

                var tenant = tenants.OrderBy(t => t.OnBoardingDate).FirstOrDefault();

                // Generate tokens
                var token = _refreshTokenService.GenerateJwtToken(user.UserName, tenant?.Id ?? Guid.Empty, emp.OrganizationId, _jwtSettings);
                var refreshToken = await _refreshTokenService.CreateRefreshToken(user.Id, tenant?.Id.ToString(), emp.OrganizationId, _jwtSettings);

                _logger.LogInfo("User login successful - UserId: {UserId}", user.Id);
                return Ok(ApiResponse<object>.SuccessResponse(new
                {
                    token,
                    refreshToken
                }, "User logged in successfully.", 200));
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Unexpected error during login");
                return StatusCode(500, ApiResponse<object>.ErrorResponse("Unexpected error", ex.Message, 500));
            }
        }

        /// <summary>
        /// Handles mobile user login, validates credentials, sends a test push notification,
        /// and generates JWT, Refresh, and MPIN tokens upon successful authentication.
        /// </summary>
        /// <param name="loginDto">Data Transfer Object containing the user's login credentials and device token.</param>
        /// <returns>An IActionResult containing the authentication tokens or an error response.</returns>

        [HttpPost("login-mobile")]
        public async Task<IActionResult> LoginMobile([FromBody] LoginDto loginDto)
        {
            await using var _context = await _dbContextFactory.CreateDbContextAsync();
            using var scope = _serviceScopeFactory.CreateScope();
            var _refreshTokenService = scope.ServiceProvider.GetRequiredService<RefreshTokenService>();
            var _employeeHelper = scope.ServiceProvider.GetRequiredService<EmployeeHelper>();
            try
            {
                // Validate input DTO
                if (loginDto == null || string.IsNullOrWhiteSpace(loginDto.Username) || string.IsNullOrWhiteSpace(loginDto.Password))
                {
                    return BadRequest(ApiResponse<object>.ErrorResponse("Username or password is missing.", "Invalid request", 400));
                }

                // --- User Retrieval ---
                // Find the user in the database by their email or phone number.
                _logger.LogInfo("Searching for user: {Username}", loginDto.Username);
                var user = await _context.ApplicationUsers
                    .FirstOrDefaultAsync(u => u.Email == loginDto.Username || u.PhoneNumber == loginDto.Username);

                // If no user is found, return an unauthorized response.
                if (user == null || string.IsNullOrWhiteSpace(user.UserName))
                {
                    _logger.LogWarning("Login failed: User not found for username {Username}", loginDto.Username);
                    return Unauthorized(ApiResponse<object>.ErrorResponse("Invalid username or password.", "Invalid username or password.", 401));
                }

                // --- User Status Checks ---
                // Check if the user's account is marked as inactive.
                if (!user.IsActive)
                {
                    _logger.LogWarning("Login failed: User '{Username}' account is inactive.", user.UserName);
                    return BadRequest(ApiResponse<object>.ErrorResponse("User is inactive", "User is inactive", 400));
                }

                // Check if the user has confirmed their email address.
                if (!user.EmailConfirmed)
                {
                    _logger.LogWarning("Login failed: User '{Username}' email is not verified.", user.UserName);
                    return BadRequest(ApiResponse<object>.ErrorResponse("Your email is not verified. Please verify your email.", "Email not verified", 400));
                }

                // --- Password Validation ---
                // Use ASP.NET Identity's UserManager to securely check the password.
                _logger.LogInfo("Validating password for user: {Username}", user.UserName);
                var isPasswordValid = await _userManager.CheckPasswordAsync(user, loginDto.Password);
                if (!isPasswordValid)
                {
                    _logger.LogWarning("Login failed: Invalid password for user {Username}", user.UserName);
                    return Unauthorized(ApiResponse<object>.ErrorResponse("Invalid username or password.", "Invalid credentials", 401));
                }
                _logger.LogInfo("Password validation successful for user: {Username}", user.UserName);

                // Check if the username property on the user object is populated.
                if (string.IsNullOrWhiteSpace(user.UserName))
                {
                    // This is an unlikely edge case, but good to handle.
                    _logger.LogWarning("Login failed: User object for ID {UserId} is missing a UserName.", user.Id);
                    return NotFound(ApiResponse<object>.ErrorResponse("UserName not found", "Username is missing", 404));
                }

                // --- Employee and Tenant Context Retrieval ---
                // Fetch associated employee details to get tenant context for token generation.
                _logger.LogInfo("Fetching employee details for user ID: {UserId}", user.Id);
                var emp = await _employeeHelper.GetEmployeeByApplicationUserID(user.Id);
                if (emp == null)
                {
                    _logger.LogWarning("Login failed: Could not find associated employee record for user ID {UserId}", user.Id);
                    return NotFound(ApiResponse<object>.ErrorResponse("Employee not found", "Employee details missing", 404));
                }
                _logger.LogInfo("Successfully found employee details for tenant ID: {TenantId}", emp.TenantId ?? Guid.Empty);


                var tenants = await _context.Tenants.Where(t => t.OrganizationId == emp.OrganizationId).ToListAsync();

                var tenant = tenants.OrderBy(t => t.OnBoardingDate).FirstOrDefault();

                // Generate JWT token
                var token = _refreshTokenService.GenerateJwtToken(user.UserName, tenant?.Id ?? Guid.Empty, emp.OrganizationId, _jwtSettings);

                // Generate a new refresh token and store it in the database.
                _logger.LogInfo("Generating and storing Refresh Token for user: {Username}", user.UserName);
                var refreshToken = await _refreshTokenService.CreateRefreshToken(user.Id, tenant?.Id.ToString(), emp.OrganizationId, _jwtSettings);

                // Fetch MPIN Token
                var mpinToken = await _context.MPINDetails.FirstOrDefaultAsync(p => p.UserId == Guid.Parse(user.Id));

                // --- Response Assembly ---
                // Combine all tokens into a single response object.
                var responseData = new
                {
                    token,
                    refreshToken,
                    mpinToken = mpinToken?.MPINToken // Safely access the MPIN token, will be null if not found.
                };

                // Return a successful response with the generated tokens.
                _logger.LogInfo("User {Username} logged in successfully.", user.UserName);

                try
                {
                    await _context.SaveChangesAsync();
                }
                catch (Exception ex)
                {
                    _logger.LogError(ex, "Exception occured while saving FCM Token for employee {EmployeeId}", emp.Id);
                    return StatusCode(500, ApiResponse<object>.ErrorResponse("Internal Error", ex.Message, 500));
                }

                return Ok(ApiResponse<object>.SuccessResponse(responseData, "User logged in successfully.", 200));
            }
            catch (Exception ex)
            {
                // --- Global Exception Handling ---
                // Catch any unexpected exceptions during the login process.
                _logger.LogError(ex, "An unexpected error occurred during the LoginMobile process for user: {Username}", loginDto?.Username ?? "N/A");

                // Return a generic 500 Internal Server Error to avoid leaking implementation details.
                return StatusCode(500, ApiResponse<object>.ErrorResponse("An internal server error occurred.", "Server Error", 500));
            }
        }

        [HttpPost("login-mpin/v1")]
        public async Task<IActionResult> VerifyMPIN([FromBody] VerifyMPINDto verifyMPIN)
        {
            await using var _context = await _dbContextFactory.CreateDbContextAsync();
            using var scope = _serviceScopeFactory.CreateScope();
            var _refreshTokenService = scope.ServiceProvider.GetRequiredService<RefreshTokenService>();

            try
            {
                // Validate the MPIN token and extract claims
                var claimsPrincipal = _refreshTokenService.ValidateToken(verifyMPIN.MPINToken, _jwtSettings);
                if (claimsPrincipal?.Identity == null || !claimsPrincipal.Identity.IsAuthenticated)
                {
                    _logger.LogWarning("Invalid or unauthenticated MPIN token");
                    return Unauthorized(ApiResponse<object>.ErrorResponse("Invalid MPIN token", "Unauthorized", 401));
                }

                string? tokenType = claimsPrincipal.FindFirst("token_type")?.Value;
                string? tokenUserId = claimsPrincipal.FindFirst(ClaimTypes.NameIdentifier)?.Value;

                // Validate essential claims
                if (string.IsNullOrWhiteSpace(tokenType) || string.IsNullOrWhiteSpace(tokenUserId))
                {
                    _logger.LogWarning("MPIN token claims are incomplete");
                    return Unauthorized(ApiResponse<object>.ErrorResponse("Invalid token claims", "MPIN token does not match your identity", 401));
                }

                // Fetch employee by ID and tenant
                var requestEmployee = await _context.Employees
                    .Include(e => e.ApplicationUser)
                    .FirstOrDefaultAsync(e => e.Id == verifyMPIN.EmployeeId && e.HasApplicationAccess && e.ApplicationUserId == tokenUserId && e.IsActive);

                if (requestEmployee == null || string.IsNullOrWhiteSpace(requestEmployee.ApplicationUserId))
                {
                    _logger.LogWarning("Employee not found or invalid for verification - EmployeeId: {EmployeeId}", verifyMPIN.EmployeeId);
                    return BadRequest(ApiResponse<object>.ErrorResponse("Invalid request", "Provided invalid employee information", 400));
                }
                var tenants = await _context.Tenants.Where(t => t.OrganizationId == requestEmployee.OrganizationId).ToListAsync();

                var tenant = tenants.OrderBy(t => t.OnBoardingDate).FirstOrDefault();
                Guid tenantId = tenant?.Id ?? Guid.Empty;

                // Validate that the token belongs to the same employee making the request
                if (requestEmployee.ApplicationUserId != tokenUserId || tokenType != "mpin" || tenantId == Guid.Empty)
                {
                    _logger.LogWarning("Token identity does not match employee info - EmployeeId: {EmployeeId}", requestEmployee.Id);
                    return Unauthorized(ApiResponse<object>.ErrorResponse("Unauthorized", "MPIN token does not match your identity", 401));
                }

                // Ensure MPIN input is valid
                if (string.IsNullOrWhiteSpace(verifyMPIN.MPIN))
                {
                    _logger.LogWarning("MPIN not provided for EmployeeId: {EmployeeId}", requestEmployee.Id);
                    return BadRequest(ApiResponse<object>.ErrorResponse("Invalid request", "MPIN not provided", 400));
                }

                // Retrieve MPIN details
                var mpinDetails = await _context.MPINDetails
                    .FirstOrDefaultAsync(p => p.UserId == Guid.Parse(requestEmployee.ApplicationUserId));

                if (mpinDetails == null)
                {
                    _logger.LogWarning("MPIN not set for EmployeeId: {EmployeeId}", requestEmployee.Id);
                    return BadRequest(ApiResponse<object>.ErrorResponse("MPIN not set", "You have not set an MPIN", 400));
                }

                // Compare hashed MPIN
                var providedMPINHash = ComputeSha256Hash(verifyMPIN.MPIN);
                if (providedMPINHash != mpinDetails.MPIN)
                {
                    _logger.LogWarning("MPIN mismatch for EmployeeId: {EmployeeId}", requestEmployee.Id);
                    return Unauthorized(ApiResponse<object>.ErrorResponse("MPIN mismatch", "MPIN did not match", 401));
                }

                // Generate new tokens
                var jwtToken = _refreshTokenService.GenerateJwtToken(requestEmployee.Email, tenantId, requestEmployee.OrganizationId, _jwtSettings);
                var refreshToken = await _refreshTokenService.CreateRefreshToken(requestEmployee.ApplicationUserId, tenantId.ToString(), requestEmployee.OrganizationId, _jwtSettings);

                _logger.LogInfo("MPIN verification successful - EmployeeId: {EmployeeId}", requestEmployee.Id);

                return Ok(ApiResponse<object>.SuccessResponse(new
                {
                    token = jwtToken,
                    refreshToken
                }, "User logged in successfully.", 200));
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Unexpected error occurred while verifying MPIN");
                return StatusCode(500, ApiResponse<object>.ErrorResponse("Unexpected error", ex.Message, 500));
            }
        }

        [HttpPost("login-otp/v1")]
        public async Task<IActionResult> LoginWithOTP([FromBody] VerifyOTPDto verifyOTP)
        {
            await using var _context = await _dbContextFactory.CreateDbContextAsync();
            using var scope = _serviceScopeFactory.CreateScope();
            var _refreshTokenService = scope.ServiceProvider.GetRequiredService<RefreshTokenService>();

            try
            {
                // Validate input
                if (string.IsNullOrWhiteSpace(verifyOTP.Email) ||
                    string.IsNullOrWhiteSpace(verifyOTP.OTP) ||
                    verifyOTP.OTP.Length != 4 ||
                    !verifyOTP.OTP.All(char.IsDigit))
                {
                    _logger.LogWarning("OTP login failed - invalid input provided");
                    return BadRequest(ApiResponse<object>.ErrorResponse("Invalid input", "Please provide a valid 4-digit OTP and Email", 400));
                }

                // Fetch employee by email
                var requestEmployee = await _context.Employees
                    .Include(e => e.ApplicationUser)
                    .FirstOrDefaultAsync(e => e.Email == verifyOTP.Email && e.IsActive);

                if (requestEmployee == null || string.IsNullOrWhiteSpace(requestEmployee.ApplicationUserId))
                {
                    _logger.LogWarning("OTP login failed - user not found for email {Email}", verifyOTP.Email);
                    return NotFound(ApiResponse<object>.ErrorResponse("User not found", "User not found", 404));
                }

                Guid userId = Guid.Parse(requestEmployee.ApplicationUserId);

                // Fetch most recent OTP
                var otpDetails = await _context.OTPDetails
                    .Where(o => o.UserId == userId)
                    .OrderByDescending(o => o.TimeStamp)
                    .FirstOrDefaultAsync();

                if (otpDetails == null)
                {
                    _logger.LogWarning("OTP login failed - no OTP found for user {UserId}", userId);
                    return NotFound(ApiResponse<object>.ErrorResponse("OTP not found", "No OTP was generated for this user", 404));
                }

                // Validate OTP expiration
                var validUntil = otpDetails.TimeStamp.AddSeconds(otpDetails.ExpriesInSec);
                if (DateTime.UtcNow > validUntil || otpDetails.IsUsed)
                {
                    _logger.LogWarning("OTP login failed - OTP expired for user {UserId}", userId);
                    return BadRequest(ApiResponse<object>.ErrorResponse("OTP expired", "The OTP has expired, please request a new one", 400));
                }

                // Match OTP
                if (otpDetails.OTP != verifyOTP.OTP)
                {
                    _logger.LogWarning("OTP login failed - incorrect OTP entered for user {UserId}", userId);
                    return Unauthorized(ApiResponse<object>.ErrorResponse("Invalid OTP", "OTP did not match", 401));
                }

                // Generate access and refresh tokens
                //var accessToken = _refreshTokenService.GenerateJwtTokenWithOrganization(requestEmployee.ApplicationUser?.UserName, requestEmployee.OrganizationId, _jwtSettings);
                //var refreshToken = await _refreshTokenService.CreateRefreshTokenWithOrganization(requestEmployee.ApplicationUserId, requestEmployee.OrganizationId, _jwtSettings);

                var tenants = await _context.Tenants.Where(t => t.OrganizationId == requestEmployee.OrganizationId).ToListAsync();

                var tenant = tenants.OrderBy(t => t.OnBoardingDate).FirstOrDefault();

                var accessToken = _refreshTokenService.GenerateJwtToken(requestEmployee.ApplicationUser?.UserName,
                    tenant?.Id ?? Guid.Empty, requestEmployee.OrganizationId, _jwtSettings);

                var refreshToken = await _refreshTokenService.CreateRefreshToken(requestEmployee.ApplicationUserId,
                    tenant?.Id.ToString(), requestEmployee.OrganizationId, _jwtSettings);

                // Fetch MPIN token if exists
                var mpinDetails = await _context.MPINDetails
                    .FirstOrDefaultAsync(p => p.UserId == userId);

                // Build and return response
                var response = new
                {
                    token = accessToken,
                    refreshToken,
                    mpinToken = mpinDetails?.MPINToken
                };
                otpDetails.IsUsed = true;
                await _context.SaveChangesAsync();
                _logger.LogInfo("OTP login successful for employee {EmployeeId}", requestEmployee.Id);
                return Ok(ApiResponse<object>.SuccessResponse(response, "User logged in successfully.", 200));
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "An unexpected error occurred during OTP login for email {Email}", verifyOTP.Email ?? string.Empty);
                return StatusCode(500, ApiResponse<object>.ErrorResponse("Unexpected error", ex.Message, 500));
            }
        }

        // new login APIs

        [HttpPost("login")]
        public async Task<IActionResult> LoginAsync([FromBody] LoginDto loginDto)
        {
            await using var _context = await _dbContextFactory.CreateDbContextAsync();
            using var scope = _serviceScopeFactory.CreateScope();
            var _refreshTokenService = scope.ServiceProvider.GetRequiredService<RefreshTokenService>();
            try
            {
                // Retrieve employee details
                var emp = await _context.Employees.FirstOrDefaultAsync(e => e.Email == loginDto.Username && e.IsActive && e.HasApplicationAccess);
                if (emp == null)
                {
                    _logger.LogWarning("Login failed: No employee record found for Email: {Email}", loginDto.Username ?? string.Empty);
                    return Unauthorized(ApiResponse<object>.ErrorResponse("Invalid username or password.", "Invalid username or password.", 404));
                }

                // Find user by email
                var user = await _context.ApplicationUsers
                    .FirstOrDefaultAsync(u => u.Email == loginDto.Username || u.PhoneNumber == loginDto.Username);

                if (user == null)
                {
                    _logger.LogWarning("Login failed: User not found for input {Username}", loginDto.Username ?? string.Empty);
                    return Unauthorized(ApiResponse<object>.ErrorResponse("Invalid username or password.", "Invalid username or password.", 401));
                }

                // Check if the user is active
                if (!user.IsActive)
                {
                    _logger.LogWarning("Login failed: Inactive user attempted login - UserId: {UserId}", user.Id);
                    return Unauthorized(ApiResponse<object>.ErrorResponse("Invalid username or password.", "Invalid username or password.", 400));
                }

                // Ensure the user's email is confirmed
                if (!user.EmailConfirmed)
                {
                    _logger.LogWarning("Login failed: Email not confirmed for UserId: {UserId}", user.Id);
                    return Unauthorized(ApiResponse<object>.ErrorResponse("Invalid username or password.", "Invalid username or password.", 400));
                }

                // Validate the password
                if (!await _userManager.CheckPasswordAsync(user, loginDto.Password ?? string.Empty))
                {
                    _logger.LogWarning("Login failed: Incorrect password for UserId: {UserId}", user.Id);
                    return Unauthorized(ApiResponse<object>.ErrorResponse("Invalid username or password.", "Invalid username or password.", 401));
                }

                // Ensure UserName exists for JWT
                if (string.IsNullOrWhiteSpace(user.UserName))
                {
                    _logger.LogWarning("Login failed: Username not found for UserId: {UserId}", user.Id);
                    return Unauthorized(ApiResponse<object>.ErrorResponse("Invalid username or password.", "Invalid username or password.", 404));
                }

                // Generate tokens
                var token = _refreshTokenService.GenerateJwtTokenWithOrganization(user.UserName, emp.OrganizationId, _jwtSettings);
                var refreshToken = await _refreshTokenService.CreateRefreshTokenWithOrganization(user.Id, emp.OrganizationId, _jwtSettings);

                //var token = _refreshTokenService.GenerateJwtToken(user.UserName, emp.TenantId ?? Guid.Empty, _jwtSettings);
                //var refreshToken = await _refreshTokenService.CreateRefreshToken(user.Id, emp.TenantId.ToString(), _jwtSettings);

                _logger.LogInfo("User login successful - UserId: {UserId}", user.Id);
                return Ok(ApiResponse<object>.SuccessResponse(new
                {
                    token,
                    refreshToken
                }, "User logged in successfully.", 200));
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Unexpected error during login");
                return StatusCode(500, ApiResponse<object>.ErrorResponse("Unexpected error", ex.Message, 500));
            }
        }

        [HttpPost("app/login")]
        public async Task<IActionResult> LoginMobileAsync([FromBody] LoginDto loginDto)
        {
            await using var _context = await _dbContextFactory.CreateDbContextAsync();
            using var scope = _serviceScopeFactory.CreateScope();
            var _refreshTokenService = scope.ServiceProvider.GetRequiredService<RefreshTokenService>();
            var _employeeHelper = scope.ServiceProvider.GetRequiredService<EmployeeHelper>();

            // Validate input DTO
            if (loginDto == null || string.IsNullOrWhiteSpace(loginDto.Username) || string.IsNullOrWhiteSpace(loginDto.Password))
            {
                return BadRequest(ApiResponse<object>.ErrorResponse("Username or password is missing.", "Invalid request", 400));
            }

            // Find user by email or phone number
            var user = await _context.ApplicationUsers
                .FirstOrDefaultAsync(u => u.Email == loginDto.Username || u.PhoneNumber == loginDto.Username);

            // If user not found, return unauthorized
            if (user == null)
            {
                return Unauthorized(ApiResponse<object>.ErrorResponse("Invalid username or password.", "Invalid username or password.", 401));
            }

            // Check if user is inactive
            if (!user.IsActive)
            {
                return BadRequest(ApiResponse<object>.ErrorResponse("User is inactive", "User is inactive", 400));
            }

            // Check if user email is not confirmed
            if (!user.EmailConfirmed)
            {
                return BadRequest(ApiResponse<object>.ErrorResponse("Your email is not verified. Please verify your email.", "Email not verified", 400));
            }

            // Validate password using ASP.NET Identity
            var isPasswordValid = await _userManager.CheckPasswordAsync(user, loginDto.Password);
            if (!isPasswordValid)
            {
                return Unauthorized(ApiResponse<object>.ErrorResponse("Invalid username or password.", "Invalid credentials", 401));
            }

            // Check if username is missing
            if (string.IsNullOrWhiteSpace(user.UserName))
            {
                return NotFound(ApiResponse<object>.ErrorResponse("UserName not found", "Username is missing", 404));
            }

            // Get employee information for tenant context
            var emp = await _employeeHelper.GetEmployeeByApplicationUserID(user.Id);
            if (emp == null)
            {
                return NotFound(ApiResponse<object>.ErrorResponse("Employee not found", "Employee details missing", 404));
            }

            // Generate JWT token
            var token = _refreshTokenService.GenerateJwtTokenWithOrganization(user.UserName, emp.OrganizationId, _jwtSettings);

            // Generate Refresh Token and store in DB
            var refreshToken = await _refreshTokenService.CreateRefreshTokenWithOrganization(user.Id, emp.OrganizationId, _jwtSettings);

            //// Generate JWT token
            //var token = _refreshTokenService.GenerateJwtToken(user.UserName, emp.TenantId ?? Guid.Empty, _jwtSettings);

            //// Generate Refresh Token and store in DB
            //var refreshToken = await _refreshTokenService.CreateRefreshToken(user.Id, emp.TenantId.ToString(), _jwtSettings);

            // Fetch MPIN Token
            var mpinToken = await _context.MPINDetails.FirstOrDefaultAsync(p => p.UserId == Guid.Parse(user.Id));

            // Combine all tokens in response
            var responseData = new
            {
                token,
                refreshToken,
                mpinToken = mpinToken?.MPINToken
            };

            // Return success response
            return Ok(ApiResponse<object>.SuccessResponse(responseData, "User logged in successfully.", 200));
        }

        [HttpPost("login-mpin")]
        public async Task<IActionResult> VerifyMPINAsync([FromBody] VerifyMPINDto verifyMPIN)
        {
            await using var _context = await _dbContextFactory.CreateDbContextAsync();
            using var scope = _serviceScopeFactory.CreateScope();
            var _refreshTokenService = scope.ServiceProvider.GetRequiredService<RefreshTokenService>();

            try
            {
                // Validate the MPIN token and extract claims
                var claimsPrincipal = _refreshTokenService.ValidateToken(verifyMPIN.MPINToken, _jwtSettings);
                if (claimsPrincipal?.Identity == null || !claimsPrincipal.Identity.IsAuthenticated)
                {
                    _logger.LogWarning("Invalid or unauthenticated MPIN token");
                    return Unauthorized(ApiResponse<object>.ErrorResponse("Invalid MPIN token", "Unauthorized", 401));
                }

                string? tokenType = claimsPrincipal.FindFirst("token_type")?.Value;
                string? tokenUserId = claimsPrincipal.FindFirst(ClaimTypes.NameIdentifier)?.Value;

                // Validate essential claims
                if (string.IsNullOrWhiteSpace(tokenType) || string.IsNullOrWhiteSpace(tokenUserId))
                {
                    _logger.LogWarning("MPIN token claims are incomplete");
                    return Unauthorized(ApiResponse<object>.ErrorResponse("Invalid token claims", "MPIN token does not match your identity", 401));
                }

                // Fetch employee by ID and tenant
                var requestEmployee = await _context.Employees
                    .Include(e => e.ApplicationUser)
                    .FirstOrDefaultAsync(e => e.Id == verifyMPIN.EmployeeId && e.HasApplicationAccess && e.ApplicationUserId == tokenUserId && e.IsActive);

                if (requestEmployee == null || string.IsNullOrWhiteSpace(requestEmployee.ApplicationUserId))
                {
                    _logger.LogWarning("Employee not found or invalid for verification - EmployeeId: {EmployeeId}", verifyMPIN.EmployeeId);
                    return BadRequest(ApiResponse<object>.ErrorResponse("Invalid request", "Provided invalid employee information", 400));
                }

                // Validate that the token belongs to the same employee making the request
                if (requestEmployee.ApplicationUserId != tokenUserId || tokenType != "mpin")
                {
                    _logger.LogWarning("Token identity does not match employee info - EmployeeId: {EmployeeId}", requestEmployee.Id);
                    return Unauthorized(ApiResponse<object>.ErrorResponse("Unauthorized", "MPIN token does not match your identity", 401));
                }

                // Ensure MPIN input is valid
                if (string.IsNullOrWhiteSpace(verifyMPIN.MPIN))
                {
                    _logger.LogWarning("MPIN not provided for EmployeeId: {EmployeeId}", requestEmployee.Id);
                    return BadRequest(ApiResponse<object>.ErrorResponse("Invalid request", "MPIN not provided", 400));
                }

                // Retrieve MPIN details
                var mpinDetails = await _context.MPINDetails
                    .FirstOrDefaultAsync(p => p.UserId == Guid.Parse(requestEmployee.ApplicationUserId));

                if (mpinDetails == null)
                {
                    _logger.LogWarning("MPIN not set for EmployeeId: {EmployeeId}", requestEmployee.Id);
                    return BadRequest(ApiResponse<object>.ErrorResponse("MPIN not set", "You have not set an MPIN", 400));
                }

                // Compare hashed MPIN
                var providedMPINHash = ComputeSha256Hash(verifyMPIN.MPIN);
                if (providedMPINHash != mpinDetails.MPIN)
                {
                    _logger.LogWarning("MPIN mismatch for EmployeeId: {EmployeeId}", requestEmployee.Id);
                    return Unauthorized(ApiResponse<object>.ErrorResponse("MPIN mismatch", "MPIN did not match", 401));
                }

                // Generate new tokens
                var jwtToken = _refreshTokenService.GenerateJwtTokenWithOrganization(requestEmployee.ApplicationUser?.UserName, requestEmployee.OrganizationId, _jwtSettings);
                var refreshToken = await _refreshTokenService.CreateRefreshTokenWithOrganization(requestEmployee.ApplicationUserId, requestEmployee.OrganizationId, _jwtSettings);

                //var jwtToken = _refreshTokenService.GenerateJwtToken(requestEmployee.Email, tenantId, _jwtSettings);
                //var refreshToken = await _refreshTokenService.CreateRefreshToken(requestEmployee.ApplicationUserId, tenantId.ToString(), _jwtSettings);

                _logger.LogInfo("MPIN verification successful - EmployeeId: {EmployeeId}", requestEmployee.Id);

                return Ok(ApiResponse<object>.SuccessResponse(new
                {
                    token = jwtToken,
                    refreshToken
                }, "User logged in successfully.", 200));
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Unexpected error occurred while verifying MPIN");
                return StatusCode(500, ApiResponse<object>.ErrorResponse("Unexpected error", ex.Message, 500));
            }
        }

        [HttpPost("logout")]
        public async Task<IActionResult> Logout([FromBody] LogoutDto logoutDto)
        {
            await using var _context = await _dbContextFactory.CreateDbContextAsync();
            using var scope = _serviceScopeFactory.CreateScope();
            var _refreshTokenService = scope.ServiceProvider.GetRequiredService<RefreshTokenService>();
            var _userHelper = scope.ServiceProvider.GetRequiredService<UserHelper>();

            var tenantId = _userHelper.GetTenantId();
            var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();


            if (string.IsNullOrWhiteSpace(logoutDto.RefreshToken))
            {
                _logger.LogWarning("Logout failed: Refresh token is missing");
                return BadRequest(ApiResponse<object>.ErrorResponse("Refresh token is required", "Refresh token is required", 400));
            }

            try
            {
                // Revoke the refresh token
                bool isRevoked = await _refreshTokenService.RevokeRefreshTokenAsync(logoutDto.RefreshToken);
                if (!isRevoked)
                {
                    _logger.LogWarning("Logout failed: Invalid or expired refresh token");
                    return Unauthorized(ApiResponse<object>.ErrorResponse("Invalid or expired refresh token", "Invalid or expired refresh token", 401));
                }

                // Optional: Blacklist the JWT access token
                string jwtToken = HttpContext.Request.Headers["Authorization"].ToString().Replace("Bearer ", "");
                if (!string.IsNullOrWhiteSpace(jwtToken))
                {
                    await _refreshTokenService.BlacklistJwtTokenAsync(jwtToken);
                    _logger.LogInfo("JWT access token blacklisted successfully");
                }
                if (!string.IsNullOrWhiteSpace(logoutDto.FcmToken))
                {
                    var fcmTokenMapping = await _context.FCMTokenMappings.FirstOrDefaultAsync(ft => ft.EmployeeId == loggedInEmployee.Id && ft.FcmToken == logoutDto.FcmToken && ft.TenantId == tenantId);
                    if (fcmTokenMapping != null)
                    {
                        _context.FCMTokenMappings.Remove(fcmTokenMapping);
                        await _context.SaveChangesAsync();
                    }
                }
                _logger.LogInfo("User logged out successfully");
                return Ok(ApiResponse<object>.SuccessResponse(new { }, "Logged out successfully", 200));
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Unexpected error during logout");
                return StatusCode(500, ApiResponse<object>.ErrorResponse("Unexpected error occurred", ex.Message, 500));
            }
        }

        [HttpPost("refresh-token")]
        public async Task<IActionResult> RefreshToken([FromBody] RefreshTokenDto refreshTokenDto)
        {
            await using var _context = await _dbContextFactory.CreateDbContextAsync();
            using var scope = _serviceScopeFactory.CreateScope();
            var _refreshTokenService = scope.ServiceProvider.GetRequiredService<RefreshTokenService>();
            var _employeeHelper = scope.ServiceProvider.GetRequiredService<EmployeeHelper>();

            if (string.IsNullOrWhiteSpace(refreshTokenDto.RefreshToken))
            {
                _logger.LogWarning("Refresh token is missing from the request body.");
                return BadRequest(ApiResponse<object>.ErrorResponse("Refresh token is required.", "Missing refresh token.", 400));
            }

            try
            {
                // Validate the MPIN token and extract claims
                var claimsPrincipal = _refreshTokenService.ValidateToken(refreshTokenDto.RefreshToken, _jwtSettings);
                if (claimsPrincipal?.Identity == null || !claimsPrincipal.Identity.IsAuthenticated)
                {
                    _logger.LogWarning("Invalid or unauthenticated MPIN token");
                    return Unauthorized(ApiResponse<object>.ErrorResponse("Invalid MPIN token", "Unauthorized", 401));
                }

                string? tokenTenantId = claimsPrincipal.FindFirst("TenantId")?.Value ?? string.Empty;

                var tenantId = Guid.Parse(tokenTenantId);

                // Step 1: Fetch and validate the refresh token
                var refreshToken = await _refreshTokenService.GetRefreshToken(refreshTokenDto.RefreshToken);
                if (refreshToken == null)
                {
                    _logger.LogWarning("Refresh token not found in the database");
                    return Unauthorized(ApiResponse<object>.ErrorResponse("Invalid or expired refresh token.", "Token not found.", 401));
                }

                if (refreshToken.ExpiryDate < DateTime.UtcNow)
                {
                    _logger.LogWarning("Refresh token expired");
                    return Unauthorized(ApiResponse<object>.ErrorResponse("Refresh token expired.", "Token expired.", 401));
                }

                // Step 2: Mark the token as used
                await _refreshTokenService.MarkRefreshTokenAsUsed(refreshToken);
                _logger.LogInfo("Refresh token marked as used");

                // Step 3: Validate and retrieve user
                var user = await _userManager.FindByIdAsync(refreshToken.UserId ?? string.Empty);
                if (user == null)
                {
                    _logger.LogWarning("User not found for RefreshToken: {Token}", refreshTokenDto.RefreshToken);
                    return BadRequest(ApiResponse<object>.ErrorResponse("Invalid request.", "User not found.", 400));
                }

                if (string.IsNullOrWhiteSpace(user.UserName))
                {
                    _logger.LogWarning("Username missing for user ID: {UserId}", user.Id);
                    return NotFound(ApiResponse<object>.ErrorResponse("Username not found.", "Username not found.", 404));
                }

                // Step 4: Fetch employee and generate new tokens
                var emp = await _employeeHelper.GetEmployeeByApplicationUserID(user.Id);

                var newJwtToken = _refreshTokenService.GenerateJwtToken(user.UserName, tenantId, emp.OrganizationId, _jwtSettings);
                var newRefreshToken = await _refreshTokenService.CreateRefreshToken(user.Id, tenantId.ToString(), emp.OrganizationId, _jwtSettings);

                _logger.LogInfo("New access and refresh token issued for user: {UserId}", user.Id);

                return Ok(ApiResponse<object>.SuccessResponse(
                    new { token = newJwtToken, refreshToken = newRefreshToken },
                    "User refresh token generated successfully.",
                    200));
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "An unexpected error occurred during token refresh.");
                return StatusCode(500, ApiResponse<object>.ErrorResponse("Unexpected error occurred.", ex.Message, 500));
            }
        }

        [HttpPost("forgot-password")]
        public async Task<IActionResult> ForgotPassword([FromBody] ForgotPasswordDto forgotPasswordDto)
        {
            await using var _context = await _dbContextFactory.CreateDbContextAsync();
            using var scope = _serviceScopeFactory.CreateScope();
            var _emailSender = scope.ServiceProvider.GetRequiredService<IEmailSender>();

            if (string.IsNullOrWhiteSpace(forgotPasswordDto.Email))
            {
                _logger.LogWarning("ForgotPassword request received without email.");
                return BadRequest(ApiResponse<object>.ErrorResponse("Email is required.", "Email is required.", 400));
            }

            var user = await _userManager.FindByEmailAsync(forgotPasswordDto.Email);
            if (user == null || user.Email == null)
            {
                _logger.LogWarning("ForgotPassword requested for non-existent or null-email user: {Email}", forgotPasswordDto.Email);
                // Do not disclose whether the email exists (security best practice)
                return Ok(ApiResponse<object>.SuccessResponse(true, "Password reset link sent if the account exists.", 200));
            }

            try
            {
                // Generate token and build reset link
                var token = await _userManager.GeneratePasswordResetTokenAsync(user);
                var resetLink = $"{_configuration["AppSettings:WebFrontendUrl"]}/reset-password?token={WebUtility.UrlEncode(token)}";

                // Send reset email
                await _emailSender.SendResetPasswordEmail(user.Email, user.UserName ?? "User", resetLink);

                _logger.LogInfo("Password reset link sent to user: {Email}", user.Email);
                return Ok(ApiResponse<object>.SuccessResponse(true, "Password reset link sent if the account exists.", 200));
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error while sending password reset email to");
                return StatusCode(500, ApiResponse<object>.ErrorResponse("Error sending password reset email.", ex.Message, 500));
            }
        }

        [HttpPost("reset-password")]
        public async Task<IActionResult> ResetPassword([FromBody] ResetPasswordDto model)
        {
            await using var _context = await _dbContextFactory.CreateDbContextAsync();
            using var scope = _serviceScopeFactory.CreateScope();
            var _emailSender = scope.ServiceProvider.GetRequiredService<IEmailSender>();
            var _employeeHelper = scope.ServiceProvider.GetRequiredService<EmployeeHelper>();

            _logger.LogInfo("Password reset request received for email: {Email}", model.Email ?? string.Empty);

            if (string.IsNullOrWhiteSpace(model.Email) || string.IsNullOrWhiteSpace(model.Token) || string.IsNullOrWhiteSpace(model.NewPassword))
            {
                _logger.LogWarning("Reset password failed due to missing input fields for email: {Email}", model.Email ?? string.Empty);
                return BadRequest(ApiResponse<object>.ErrorResponse("All fields are required.", "Invalid input.", 400));
            }

            var user = await _userManager.FindByEmailAsync(model.Email);
            if (user == null)
            {
                _logger.LogWarning("Reset password failed - user not found for email: {Email}", model.Email);
                return BadRequest(ApiResponse<object>.ErrorResponse("Invalid request.", "Invalid user.", 400));
            }

            var isTokenValid = await _userManager.VerifyUserTokenAsync(
                   user,
                   TokenOptions.DefaultProvider, // This is the token provider
                   UserManager<ApplicationUser>.ResetPasswordTokenPurpose,
                  WebUtility.UrlDecode(model.Token)
               );
            string token = "";

            if (!isTokenValid)
            {
                _logger.LogWarning("Decoded token failed, retrying with raw token for email: {Email}", model.Email);

                var isDecodedTokenValid = await _userManager.VerifyUserTokenAsync(
                    user,
                    TokenOptions.DefaultProvider,
                    UserManager<ApplicationUser>.ResetPasswordTokenPurpose,
                    model.Token
                );

                if (!isDecodedTokenValid)
                {
                    _logger.LogWarning("Both decoded and raw token failed for email: {Email}", model.Email);
                    return BadRequest(ApiResponse<object>.ErrorResponse("Invalid request.", "Invalid or expired reset token.", 400));
                }

                token = model.Token;
            }
            else
            {
                token = WebUtility.UrlDecode(model.Token);
            }

            var result = await _userManager.ResetPasswordAsync(user, token, model.NewPassword);
            if (!result.Succeeded)
            {
                var errors = result.Errors.Select(e => e.Description).ToList();
                _logger.LogWarning("Reset password failed for user: {Email}. Errors: {Errors}", model.Email, string.Join(", ", errors));
                return BadRequest(ApiResponse<object>.ErrorResponse("Failed to reset password.", errors, 400));
            }

            try
            {
                var emp = await _employeeHelper.GetEmployeeByApplicationUserID(user.Id);
                string fullName = $"{emp.FirstName} {emp.LastName}".Trim();

                await _emailSender.SendResetPasswordSuccessEmail(user.Email!, fullName);

                _logger.LogInfo("Reset password success email sent to user: {Email}", model.Email);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error while sending reset password success email to user");
                // Continue, do not fail because of email issue
            }

            _logger.LogInfo("Password reset successful for user: {Email}", model.Email);
            return Ok(ApiResponse<object>.SuccessResponse(true, "Password reset successfully.", 200));
        }

        [HttpPost("send-otp")]
        public async Task<IActionResult> SendOtpEmail([FromBody] GenerateOTPDto generateOTP)
        {
            await using var _context = await _dbContextFactory.CreateDbContextAsync();
            using var scope = _serviceScopeFactory.CreateScope();
            var _emailSender = scope.ServiceProvider.GetRequiredService<IEmailSender>();

            try
            {
                // Validate input email
                if (string.IsNullOrWhiteSpace(generateOTP.Email))
                {
                    _logger.LogWarning("Send OTP failed - Email is missing");
                    return BadRequest(ApiResponse<object>.ErrorResponse("Email is required", "Invalid email", 400));
                }

                // Fetch user by email
                var requestedUser = await _userManager.FindByEmailAsync(generateOTP.Email);
                string title = "Send OTP";

                if (requestedUser != null && requestedUser.IsActive)
                {
                    // Fetch employee details
                    var requestedEmployee = await _context.Employees
                        .FirstOrDefaultAsync(e => e.ApplicationUserId == requestedUser.Id);

                    // Generate a random 4-digit OTP
                    string otp = GenerateSecureOtp();

                    // Store OTP in database
                    var otpDetails = new OTPDetails
                    {
                        UserId = Guid.Parse(requestedUser.Id),
                        OTP = otp,
                        ExpriesInSec = 300, // 5 minutes
                        TimeStamp = DateTime.UtcNow
                    };

                    _context.OTPDetails.Add(otpDetails);
                    await _context.SaveChangesAsync();

                    // Prepare email
                    List<string> toEmails = [generateOTP.Email];
                    string name = $"{requestedEmployee?.FirstName} {requestedEmployee?.LastName}".Trim();
                    var mailTemplate = await _context.MailingList
                        .FirstOrDefaultAsync(t => t.Title.ToLower() == title.ToLower());

                    string subject = mailTemplate?.Subject ?? string.Empty;
                    string emailBody = mailTemplate?.Body ?? string.Empty;

                    // Send OTP via email
                    await _emailSender.SendOTP(toEmails, emailBody, name, otp, subject);

                    _logger.LogInfo("OTP sent successfully to {Email}", generateOTP.Email);
                    return Ok(ApiResponse<object>.SuccessResponse("Success", "OTP generated successfully", 200));
                }

                _logger.LogWarning("Send OTP failed - Invalid or inactive user: {Email}", generateOTP.Email);
                return BadRequest(ApiResponse<object>.ErrorResponse("Provided invalid information", "User not found or inactive", 400));
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "An unexpected error occurred while sending OTP to {Email}", generateOTP.Email ?? "");
                return StatusCode(500, ApiResponse<object>.ErrorResponse("An unexpected error occurred.", ex.Message, 500));
            }
        }

        [HttpPost("login-otp")]
        public async Task<IActionResult> LoginWithOTPAsync([FromBody] VerifyOTPDto verifyOTP)
        {
            await using var _context = await _dbContextFactory.CreateDbContextAsync();
            using var scope = _serviceScopeFactory.CreateScope();
            var _refreshTokenService = scope.ServiceProvider.GetRequiredService<RefreshTokenService>();

            try
            {
                // Validate input
                if (string.IsNullOrWhiteSpace(verifyOTP.Email) ||
                    string.IsNullOrWhiteSpace(verifyOTP.OTP) ||
                    verifyOTP.OTP.Length != 4 ||
                    !verifyOTP.OTP.All(char.IsDigit))
                {
                    _logger.LogWarning("OTP login failed - invalid input provided");
                    return BadRequest(ApiResponse<object>.ErrorResponse("Invalid input", "Please provide a valid 4-digit OTP and Email", 400));
                }

                // Fetch employee by email
                var requestEmployee = await _context.Employees
                    .Include(e => e.ApplicationUser)
                    .FirstOrDefaultAsync(e => e.Email == verifyOTP.Email && e.IsActive);

                if (requestEmployee == null || string.IsNullOrWhiteSpace(requestEmployee.ApplicationUserId))
                {
                    _logger.LogWarning("OTP login failed - user not found for email {Email}", verifyOTP.Email);
                    return NotFound(ApiResponse<object>.ErrorResponse("User not found", "User not found", 404));
                }

                Guid userId = Guid.Parse(requestEmployee.ApplicationUserId);

                // Fetch most recent OTP
                var otpDetails = await _context.OTPDetails
                    .Where(o => o.UserId == userId)
                    .OrderByDescending(o => o.TimeStamp)
                    .FirstOrDefaultAsync();

                if (otpDetails == null)
                {
                    _logger.LogWarning("OTP login failed - no OTP found for user {UserId}", userId);
                    return NotFound(ApiResponse<object>.ErrorResponse("OTP not found", "No OTP was generated for this user", 404));
                }

                // Validate OTP expiration
                var validUntil = otpDetails.TimeStamp.AddSeconds(otpDetails.ExpriesInSec);
                if (DateTime.UtcNow > validUntil || otpDetails.IsUsed)
                {
                    _logger.LogWarning("OTP login failed - OTP expired for user {UserId}", userId);
                    return BadRequest(ApiResponse<object>.ErrorResponse("OTP expired", "The OTP has expired, please request a new one", 400));
                }

                // Match OTP
                if (otpDetails.OTP != verifyOTP.OTP)
                {
                    _logger.LogWarning("OTP login failed - incorrect OTP entered for user {UserId}", userId);
                    return Unauthorized(ApiResponse<object>.ErrorResponse("Invalid OTP", "OTP did not match", 401));
                }

                // Generate access and refresh tokens
                var accessToken = _refreshTokenService.GenerateJwtTokenWithOrganization(requestEmployee.ApplicationUser?.UserName, requestEmployee.OrganizationId, _jwtSettings);
                var refreshToken = await _refreshTokenService.CreateRefreshTokenWithOrganization(requestEmployee.ApplicationUserId, requestEmployee.OrganizationId, _jwtSettings);

                //var accessToken = _refreshTokenService.GenerateJwtToken(
                //    requestEmployee.ApplicationUser?.UserName,
                //    requestEmployee.TenantId ?? Guid.Empty,
                //    _jwtSettings
                //);

                //var refreshToken = await _refreshTokenService.CreateRefreshToken(
                //    requestEmployee.ApplicationUserId,
                //    requestEmployee.TenantId.ToString(),
                //    _jwtSettings
                //);

                // Fetch MPIN token if exists
                var mpinDetails = await _context.MPINDetails
                    .FirstOrDefaultAsync(p => p.UserId == userId);

                // Build and return response
                var response = new
                {
                    token = accessToken,
                    refreshToken,
                    mpinToken = mpinDetails?.MPINToken
                };
                otpDetails.IsUsed = true;
                await _context.SaveChangesAsync();
                _logger.LogInfo("OTP login successful for employee {EmployeeId}", requestEmployee.Id);
                return Ok(ApiResponse<object>.SuccessResponse(response, "User logged in successfully.", 200));
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "An unexpected error occurred during OTP login for email {Email}", verifyOTP.Email ?? string.Empty);
                return StatusCode(500, ApiResponse<object>.ErrorResponse("Unexpected error", ex.Message, 500));
            }
        }

        [HttpPost("sendmail")]
        public async Task<IActionResult> SendEmail([FromBody] EmailDot emailDot)
        {
            await using var _context = await _dbContextFactory.CreateDbContextAsync();
            using var scope = _serviceScopeFactory.CreateScope();
            var _emailSender = scope.ServiceProvider.GetRequiredService<IEmailSender>();

            var user = await _userManager.FindByEmailAsync(emailDot.ToEmail ?? string.Empty);
            if (user == null)
            {
                return NotFound(ApiResponse<object>.ErrorResponse("User not found.", "User not found.", 404));
            }

            /* New User*/
            //var token = await _userManager.GeneratePasswordResetTokenAsync(user);
            //var resetLink = $"{_configuration["AppSettings:WebFrontendUrl"]}/reset-password?token={WebUtility.UrlEncode(token)}";

            //await _emailSender.SendResetPasswordEmailOnRegister(emailDot.ToEmail, "Vikas", resetLink);


            /* Forget password*/
            // var token = await _userManager.GeneratePasswordResetTokenAsync(user);

            var token = await _userManager.GenerateUserTokenAsync(user, TokenOptions.DefaultProvider, "ResetPassword");

            var isTokenValid = await _userManager.VerifyUserTokenAsync(user, TokenOptions.DefaultProvider, "ResetPassword", token);

            var resetLink = $"{_configuration["AppSettings:WebFrontendUrl"]}/reset-password?token={WebUtility.UrlEncode(token)}";

            if (user.Email == null) return NotFound(ApiResponse<object>.ErrorResponse("Email Not found", "Email Not found", 404));

            await _emailSender.SendResetPasswordEmail(user.Email, "", resetLink);

            return Ok(ApiResponse<object>.SuccessResponse(new { }, "Password reset link sent.", 200));
        }

        [Authorize]
        [HttpPost("change-password")]
        public async Task<IActionResult> ChangePassword([FromBody] ChangePasswordDto changePassword)
        {
            await using var _context = await _dbContextFactory.CreateDbContextAsync();
            using var scope = _serviceScopeFactory.CreateScope();

            try
            {
                var _userHelper = scope.ServiceProvider.GetRequiredService<UserHelper>();
                var _emailSender = scope.ServiceProvider.GetRequiredService<IEmailSender>();
                var _employeeHelper = scope.ServiceProvider.GetRequiredService<EmployeeHelper>();

                // Get the currently logged-in user
                var loggedUser = await _userHelper.GetCurrentUserAsync();

                // Validate email
                if (string.IsNullOrWhiteSpace(changePassword.Email))
                {
                    _logger.LogWarning("Change password attempt failed - Email is missing");
                    return BadRequest(ApiResponse<object>.ErrorResponse("Email is missing", "Email is missing", 400));
                }

                // Find the user by email
                var requestedUser = await _userManager.FindByEmailAsync(changePassword.Email);
                if (requestedUser == null)
                {
                    _logger.LogWarning("Change password attempt failed - Email not found: {Email}", changePassword.Email);
                    return BadRequest(ApiResponse<object>.ErrorResponse("Invalid email", "User not found.", 400));
                }

                // Validate the old password
                bool isOldPasswordCorrect = await _userManager.CheckPasswordAsync(requestedUser, changePassword.OldPassword ?? string.Empty);

                // Ensure user identity and old password match
                if (loggedUser?.Email != requestedUser.Email || !isOldPasswordCorrect)
                {
                    _logger.LogWarning("Change password denied - User {Email} provided incorrect credentials", changePassword.Email);
                    return BadRequest(ApiResponse<object>.ErrorResponse("Incorrect credentials", "Invalid request.", 400));
                }

                // Generate reset token and change password
                var resetToken = await _userManager.GeneratePasswordResetTokenAsync(requestedUser);
                var result = await _userManager.ResetPasswordAsync(requestedUser, resetToken, changePassword.NewPassword ?? string.Empty);

                if (!result.Succeeded)
                {
                    var errors = result.Errors.Select(e => e.Description).ToList();
                    _logger.LogWarning("Password reset failed for user {Email}. Errors: {Errors}", changePassword.Email, string.Join("; ", errors));
                    return BadRequest(ApiResponse<object>.ErrorResponse("Failed to change password", errors, 400));
                }

                // Send confirmation email
                var emp = await _employeeHelper.GetEmployeeByApplicationUserID(requestedUser.Id);
                await _emailSender.SendResetPasswordSuccessEmail(requestedUser.Email ?? string.Empty, $"{emp.FirstName} {emp.LastName}");

                _logger.LogInfo("Password changed successfully for user {Email}", requestedUser.Email ?? string.Empty);
                return Ok(ApiResponse<object>.SuccessResponse(true, "Password changed successfully.", 200));
            }
            catch (Exception exp)
            {
                _logger.LogError(exp, "An unexpected error occurred while changing password");
                return StatusCode(500, ApiResponse<object>.ErrorResponse("An unexpected error occurred.", exp.Message, 500));
            }
        }

        [Authorize]
        [HttpPost("generate-mpin")]
        public async Task<IActionResult> GenerateMPIN([FromBody] GenerateMPINDto generateMPINDto)
        {
            await using var _context = await _dbContextFactory.CreateDbContextAsync();
            using var scope = _serviceScopeFactory.CreateScope();

            var _userHelper = scope.ServiceProvider.GetRequiredService<UserHelper>();
            var _refreshTokenService = scope.ServiceProvider.GetRequiredService<RefreshTokenService>();

            Employee loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();

            // Get the employee for whom MPIN is being generated
            var requestEmployee = await _context.Employees
                .Include(e => e.ApplicationUser)
                .FirstOrDefaultAsync(e => e.Id == generateMPINDto.EmployeeId);

            // Validate employee and MPIN input
            if (requestEmployee == null || string.IsNullOrWhiteSpace(generateMPINDto.MPIN) || generateMPINDto.MPIN.Length != 4 || !generateMPINDto.MPIN.All(char.IsDigit))
            {
                _logger.LogWarning("Employee {EmployeeId} provided invalid information to generate MPIN", loggedInEmployee.Id);
                return BadRequest(ApiResponse<object>.ErrorResponse("Provided invalid information", "Provided invalid information", 400));
            }

            // Ensure the logged-in user is only generating their own MPIN
            if (requestEmployee.Id != loggedInEmployee.Id)
            {
                _logger.LogWarning("Employee {EmployeeId} tried to set MPIN for a different employee", loggedInEmployee.Id);
                return BadRequest(ApiResponse<object>.ErrorResponse("You can't create MPIN for another employee", "Unauthorized MPIN creation", 400));
            }

            // Generate hash and token
            string mpinHash = ComputeSha256Hash(generateMPINDto.MPIN);
            string mpinToken = _refreshTokenService.CreateMPINToken(
                requestEmployee.ApplicationUserId,
                requestEmployee.OrganizationId.ToString(),
                _jwtSettings
            );

            // Prepare MPIN entity
            Guid userId = Guid.Parse(requestEmployee.ApplicationUserId ?? string.Empty);
            var existingMPIN = await _context.MPINDetails.FirstOrDefaultAsync(p => p.UserId == userId);

            if (existingMPIN == null)
            {
                // Add new MPIN record
                var mPINDetails = new MPINDetails
                {
                    UserId = userId,
                    MPIN = mpinHash,
                    MPINToken = mpinToken,
                    TimeStamp = DateTime.UtcNow
                };

                _context.MPINDetails.Add(mPINDetails);
                await _context.SaveChangesAsync();

                _logger.LogInfo("MPIN generated successfully for employee {EmployeeId}", requestEmployee.Id);
                return Ok(ApiResponse<object>.SuccessResponse(mpinToken, "MPIN generated successfully", 200));
            }
            else
            {
                // Update existing MPIN record
                existingMPIN.MPIN = mpinHash;
                existingMPIN.MPINToken = mpinToken;
                existingMPIN.TimeStamp = DateTime.UtcNow;

                await _context.SaveChangesAsync();

                _logger.LogInfo("MPIN updated successfully for employee {EmployeeId}", requestEmployee.Id);
                return Ok(ApiResponse<object>.SuccessResponse(mpinToken, "MPIN updated successfully", 200));
            }
        }

        [Authorize]
        [HttpPost("set/device-token")]
        public async Task<IActionResult> StoreDeviceToken([FromBody] FCMTokenDto model)
        {
            // Create DbContext asynchronously for fresh connection
            await using var _context = await _dbContextFactory.CreateDbContextAsync();

            // Create a scope to get scoped services
            using var scope = _serviceScopeFactory.CreateScope();
            var _httpContextAccessor = scope.ServiceProvider.GetRequiredService<IHttpContextAccessor>();
            var _userHelper = scope.ServiceProvider.GetRequiredService<UserHelper>();

            var tenantId = _userHelper.GetTenantId();
            var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();

            var existingFCMTokenMapping = await _context.FCMTokenMappings.Where(ft => ft.FcmToken == model.FcmToken).ToListAsync();

            if (existingFCMTokenMapping.Any())
            {
                _context.FCMTokenMappings.RemoveRange(existingFCMTokenMapping);
            }

            var fcmTokenMapping = new FCMTokenMapping
            {
                EmployeeId = loggedInEmployee.Id,
                FcmToken = model.FcmToken,
                ExpiredAt = DateTime.UtcNow.AddDays(6),
                TenantId = tenantId
            };
            _context.FCMTokenMappings.Add(fcmTokenMapping);
            _logger.LogInfo("New FCM Token registering for employee {EmployeeId}", loggedInEmployee.Id);
            try
            {
                await _context.SaveChangesAsync();
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Exception occured while saving FCM Token for employee {EmployeeId}", loggedInEmployee.Id);
                return StatusCode(500, ApiResponse<object>.ErrorResponse("Internal Error", ex.Message, 500));
            }
            return Ok(ApiResponse<object>.SuccessResponse(new { }, "FCM Token registered Successfuly", 200));
        }

        [Authorize]
        [HttpGet("get/user/tenants")]
        public async Task<IActionResult> GetTenantListByEmployeeAsync()
        {
            // Create DbContext asynchronously to ensure DB connection is established fresh
            await using var _context = await _dbContextFactory.CreateDbContextAsync();

            // Create a service scope to resolve scoped services like IHttpContextAccessor, IMapper, ILogger
            using var scope = _serviceScopeFactory.CreateScope();
            var _httpContextAccessor = scope.ServiceProvider.GetRequiredService<IHttpContextAccessor>();
            var _mapper = scope.ServiceProvider.GetRequiredService<IMapper>();
            var _userHelper = scope.ServiceProvider.GetRequiredService<UserHelper>();

            _logger.LogDebug("Starting GetTenantAsync method.");

            // Extract OrganizationId from current user's claims
            string stringOrganizationId = _httpContextAccessor.HttpContext?.User.FindFirst("OrganizationId")?.Value ?? string.Empty;
            Employee loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();

            if (string.IsNullOrEmpty(stringOrganizationId))
            {
                _logger.LogWarning("OrganizationId claim missing in user token.");
                return BadRequest(ApiResponse<object>.ErrorResponse("OrganizationId claim is missing", 400));
            }

            if (!Guid.TryParse(stringOrganizationId, out var organizationId))
            {
                _logger.LogWarning("Invalid OrganizationId format: {OrganizationId}", stringOrganizationId);
                return BadRequest(ApiResponse<object>.ErrorResponse("Invalid OrganizationId format", 400));
            }

            _logger.LogInfo("Fetching TenantOrgMappings for OrganizationId: {OrganizationId}", organizationId);

            // Retrieve all TenantOrgMappings that match the organizationId and have a related Tenant
            var tenantOrgMappingTask = Task.Run(async () =>
            {
                await using var context = await _dbContextFactory.CreateDbContextAsync();
                return await context.TenantOrgMappings.Where(to => to.OrganizationId == organizationId && to.Tenant != null).Select(to => to.TenantId).ToListAsync();
            });
            var projectTask = Task.Run(async () =>
            {
                await using var context = await _dbContextFactory.CreateDbContextAsync();
                return await context.Projects.Where(to => to.PromoterId == organizationId || to.PMCId == organizationId).Select(to => to.TenantId).ToListAsync();
            });

            await Task.WhenAll(tenantOrgMappingTask, projectTask);

            var tenantIds = tenantOrgMappingTask.Result;

            tenantIds.AddRange(projectTask.Result);

            tenantIds = tenantIds.Distinct().ToList();

            // Additionally fetch the Tenant record associated directly with this OrganizationId if any
            var tenants = await _context.Tenants
                .Include(t => t.Industry)
                .Include(t => t.TenantStatus)
                .Where(t => t.OrganizationId == organizationId || tenantIds.Contains(t.Id)).ToListAsync();

            tenants = tenants.Distinct().ToList();

            // Map the tenant entities to TenantListVM view models
            var response = _mapper.Map<List<TenantListVM>>(tenants);

            _logger.LogInfo("Fetched {Count} tenants for OrganizationId: {OrganizationId}", tenants.Count, organizationId);
            _logger.LogDebug("GetTenantAsync method completed successfully.");

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

        [Authorize]
        [HttpPost("select-tenant/{tenantId}")]
        public async Task<IActionResult> SelectTenantAsync(Guid tenantId)
        {
            // Create DbContext asynchronously for fresh connection
            await using var _context = await _dbContextFactory.CreateDbContextAsync();

            // Create a scope to get scoped services
            using var scope = _serviceScopeFactory.CreateScope();
            var _httpContextAccessor = scope.ServiceProvider.GetRequiredService<IHttpContextAccessor>();
            var _userHelper = scope.ServiceProvider.GetRequiredService<UserHelper>();
            var _refreshTokenService = scope.ServiceProvider.GetRequiredService<RefreshTokenService>();

            _logger.LogDebug("Starting SelectTenantAsync for tenantId: {TenantId}", tenantId);

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

            // Extract OrganizationId from user claims
            string stringOrganizationId = _httpContextAccessor.HttpContext?.User.FindFirst("OrganizationId")?.Value ?? string.Empty;

            if (string.IsNullOrEmpty(stringOrganizationId))
            {
                _logger.LogWarning("OrganizationId claim is missing.");
                return BadRequest(ApiResponse<object>.ErrorResponse("OrganizationId claim is missing", 400));
            }

            if (!Guid.TryParse(stringOrganizationId, out var organizationId))
            {
                _logger.LogWarning("Invalid OrganizationId format: {OrganizationId}", stringOrganizationId);
                return BadRequest(ApiResponse<object>.ErrorResponse("Invalid OrganizationId format", 400));
            }

            // Find TenantOrgMapping for given tenantId and organizationId to validate access
            var tenantOrganizationTask = Task.Run(async () =>
            {
                await using var context = await _dbContextFactory.CreateDbContextAsync();
                return await context.TenantOrgMappings
                .FirstOrDefaultAsync(to => to.TenantId == tenantId && to.OrganizationId == organizationId);

            });

            var primaryOrganizationTask = Task.Run(async () =>
            {
                await using var context = await _dbContextFactory.CreateDbContextAsync();
                return await context.Tenants
                .Where(t => t.Id == tenantId && t.OrganizationId == organizationId).ToListAsync();

            });

            await Task.WhenAll(tenantOrganizationTask, primaryOrganizationTask);

            var tenantOrganization = tenantOrganizationTask.Result;
            var primaryOrganization = primaryOrganizationTask.Result;

            if (tenantOrganization == null && !primaryOrganization.Any())
            {
                _logger.LogWarning("Tenant Organization Mapping not found for TenantId: {TenantId} and OrganizationId: {OrganizationId}", tenantId, organizationId);
                return NotFound(ApiResponse<object>.ErrorResponse("Tenant Organization Mapping not found", "Tenant Organization Mapping not found in database", 404));
            }

            // Optional: Blacklist the JWT access token
            string jwtToken = HttpContext.Request.Headers["Authorization"].ToString().Replace("Bearer ", "");
            if (!string.IsNullOrWhiteSpace(jwtToken))
            {
                await _refreshTokenService.BlacklistJwtTokenAsync(jwtToken);
                _logger.LogInfo("JWT access token blacklisted successfully");
            }

            // Generate JWT token scoped to selected tenant and logged-in employee
            var token = _refreshTokenService.GenerateJwtToken(loggedInEmployee.Email, tenantId, loggedInEmployee.OrganizationId, _jwtSettings);

            // Generate and store refresh token
            var refreshToken = await _refreshTokenService.CreateRefreshToken(loggedInEmployee.ApplicationUserId, tenantId.ToString(), loggedInEmployee.OrganizationId, _jwtSettings);

            var _cache = scope.ServiceProvider.GetRequiredService<CacheUpdateHelper>();
            await _cache.ClearAllEmployeesFromCacheByOnlyEmployeeId(loggedInEmployee.Id);

            _logger.LogInfo("Tenant selected and tokens generated for TenantId: {TenantId} and Employee: {EmployeeEmail}", tenantId, loggedInEmployee.Email ?? string.Empty);

            // Return success response including tokens
            return Ok(ApiResponse<object>.SuccessResponse(new { Token = token, RefreshToken = refreshToken }, "Tenant is selected", 200));
        }

        private static string ComputeSha256Hash(string rawData)
        {
            using (SHA256 sha256 = SHA256.Create())
            {
                // Convert the input string to bytes and compute the hash
                byte[] bytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(rawData));

                // Convert byte array to a readable hex string
                StringBuilder builder = new StringBuilder();
                foreach (var b in bytes)
                    builder.Append(b.ToString("x2"));

                return builder.ToString();
            }
        }

        private static string GenerateSecureOtp()
        {
            var randomNumber = new byte[2]; // 2 bytes can store values up to 65535
            using (var rng = RandomNumberGenerator.Create())
            {
                rng.GetBytes(randomNumber);
            }

            // Convert to int and restrict to 4 digit range (0-9999)
            int randomValue = BitConverter.ToUInt16(randomNumber, 0) % 10000;

            // Format with leading zeros if necessary to ensure 4 digits
            return randomValue.ToString("D4");
        }

    }
}
