using Common;
using Common.Constant;
using Common.Http;
using Common.Logic;
using Database;
using Database.Database;
//using Esim.Apis.Logic;
using Esim.Apis.Singleton;
using log4net;
using Microsoft.AspNetCore.Mvc;
using Microsoft.VisualBasic;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Serialization;

namespace Esim.Apis.Business
{
    public class UserBusinessImpl : IUserBusiness
    {
        private static readonly log4net.ILog log = log4net.LogManager.GetLogger(
            typeof(UserBusinessImpl)
        );

        private ModelContext dbContext;
        IConfiguration configuration;

        public UserBusinessImpl(ModelContext _dbContext, IConfiguration _configuration)
        {
            dbContext = _dbContext;
            configuration = _configuration;
        }

        private string GetParameter(string key)
        {
            return configuration.GetSection(key).Value ?? "";
        }

        /// <summary>
        /// Request OTP to be sent to email
        /// </summary>
        public async Task<IActionResult> RequestOtp(HttpRequest httpRequest, RequestOtpReq request)
        {
            var url = httpRequest.Path;
            var json = JsonConvert.SerializeObject(request);
            log.Debug("URL: " + url + " => Request: " + json);
            try
            {
                if (string.IsNullOrEmpty(request.email))
                {
                    return DotnetLib.Http.HttpResponse.BuildResponse(
                        log,
                        url,
                        json,
                        CommonErrorCode.RequiredFieldMissing,
                        ConfigManager.Instance.GetConfigWebValue("EMAIL_REQUIRED"),
                        new { }
                    );
                }

                // Generate 6-digit OTP (fixed 111111 for test account abc@gmail.com)
                bool isTestAccount = request.email.ToLower() == "abc@gmail.com";
                string otpCode = isTestAccount ? "111111" : GenerateOtp();

                // Check if customer exists, if not create new
                var customer = dbContext
                    .CustomerInfos.Where(c => c.Email == request.email)
                    .FirstOrDefault();

                decimal? customerId = customer?.Id;

                if (customer == null)
                {
                    // Create new customer record - manually get ID from Oracle sequence
                    var newCustomerId = await Database.DbLogic.GenIdAsync(
                        dbContext,
                        "CUSTOMER_INFO_SEQ"
                    );

                    // Extract name from email (part before @)
                    string emailUsername = request.email.Split('@')[0];

                    var newCustomer = new CustomerInfo
                    {
                        Id = newCustomerId,
                        Email = request.email,
                        SurName = emailUsername,
                        LastName = emailUsername,
                        Status = true,
                        IsVerified = false,
                        CreatedDate = DateTime.Now,
                        LastUpdate = DateTime.Now
                    };
                    dbContext.CustomerInfos.Add(newCustomer);
                    await dbContext.SaveChangesAsync();
                    customerId = newCustomerId;
                }

                // Check if there's an existing OTP record for this email (ANY record, used or unused)
                int otpExpireMinutes = 5;
                int minSecondsBetweenRequests = 60; // Anti-spam: minimum 60 seconds between requests

                var existingOtp = dbContext
                    .OtpVerifications.Where(o => o.UserEmail == request.email)
                    .OrderByDescending(o => o.CreatedDate)
                    .FirstOrDefault();

                if (existingOtp != null)
                {
                    // Anti-spam check: prevent rapid OTP requests
                    var secondsSinceLastRequest = (
                        DateTime.Now - (existingOtp.CreatedDate ?? DateTime.Now)
                    ).TotalSeconds;
                    if (secondsSinceLastRequest < minSecondsBetweenRequests)
                    {
                        var waitSeconds = (int)(
                            minSecondsBetweenRequests - secondsSinceLastRequest
                        );
                        log.Warn(
                            $"Spam prevention: OTP request too soon for {request.email}. Last request {secondsSinceLastRequest:F0}s ago."
                        );

                        return DotnetLib.Http.HttpResponse.BuildResponse(
                            log,
                            url,
                            json,
                            CommonErrorCode.OtpTooManyRequests,
                            $"Please wait {waitSeconds} seconds before requesting a new OTP.",
                            new
                            {
                                waitSeconds = waitSeconds,
                                canRequestAt = existingOtp.CreatedDate?.AddSeconds(
                                    minSecondsBetweenRequests
                                )
                            }
                        );
                    }

                    // UPDATE existing record - reuse for this email to prevent table bloat
                    existingOtp.OtpCode = otpCode;
                    existingOtp.ExpiredAt = DateTime.Now.AddMinutes(otpExpireMinutes);
                    existingOtp.AttemptCount = 0; // Reset attempt count
                    existingOtp.IsUsed = false; // Reset to unused (allow reuse)
                    existingOtp.CustomerId = customerId; // Update customer ID if changed
                    existingOtp.CreatedDate = DateTime.Now; // Update to current time

                    log.Info(
                        $"Updated existing OTP record ID={existingOtp.Id} for {request.email} (was {(existingOtp.IsUsed == true ? "used" : "unused")})"
                    );
                }
                else
                {
                    // INSERT new record only for first-time email
                    var otpId = (int)
                        await Database.DbLogic.GenIdAsync(dbContext, "OTP_VERIFICATION_SEQ");
                    var otpVerification = new OtpVerification
                    {
                        Id = otpId,
                        CustomerId = customerId,
                        UserEmail = request.email,
                        OtpCode = otpCode,
                        OtpType = 1, // Login OTP
                        ExpiredAt = DateTime.Now.AddMinutes(otpExpireMinutes),
                        IsUsed = false,
                        AttemptCount = 0,
                        CreatedDate = DateTime.Now
                    };

                    dbContext.OtpVerifications.Add(otpVerification);
                    log.Info($"Created new OTP record for {request.email} (first time)");
                }

                await dbContext.SaveChangesAsync();

                // Skip email sending for test account
                if (!isTestAccount)
                {
                    // Add to MESSAGE_QUEUE for background email sending
                    // Resolve template content now so Worker only needs to send email
                    string lang = CommonLogic.GetLanguage(httpRequest, request.lang);
                    string templateCode = "OTP_LOGIN";

                    // Query template and get language-specific content
                    var template = dbContext.MessageTemplates.FirstOrDefault(
                        t => t.TemplateCode == templateCode && t.Status.HasValue && t.Status.Value
                    );

                    if (template == null)
                    {
                        log.Error($"Template '{templateCode}' not found in MESSAGE_TEMPLATE");
                        throw new Exception($"Email template '{templateCode}' not found");
                    }

                    // Get subject based on language: vi=default, en=_EN, lo=_LO (default)
                    string emailSubject =
                        lang == "en"
                            ? (template.SubjectEn ?? template.Subject ?? "")
                            : lang == "vi"
                                ? (template.Subject ?? "")
                                : (template.SubjectLo ?? template.Subject ?? "");

                    // Get content based on language: vi=default, en=_EN, lo=_LO (default)
                    string emailContent =
                        lang == "en"
                            ? (template.ContentEn ?? template.Content ?? "")
                            : lang == "vi"
                                ? (template.Content ?? "")
                                : (template.ContentLo ?? template.Content ?? "");

                    // Replace placeholders in content
                    emailContent = emailContent
                        .Replace("{{OTP_CODE}}", otpCode)
                        .Replace("{{EXPIRE_MINUTES}}", otpExpireMinutes.ToString());

                    // Replace placeholders in subject (if any)
                    emailSubject = emailSubject
                        .Replace("{{OTP_CODE}}", otpCode)
                        .Replace("{{EXPIRE_MINUTES}}", otpExpireMinutes.ToString());
                    var emailMessageID = (int)
                        await Database.DbLogic.GenIdAsync(dbContext, "MESSAGE_QUEUE_SEQ");
                    var emailMessage = new MessageQueue
                    {
                        Id = emailMessageID,
                        MessageType = 1, // Email
                        Recipient = request.email,
                        Subject = emailSubject, // Pre-resolved subject
                        Content = emailContent, // Pre-resolved content
                        Priority = true, // High priority
                        Status = 0, // Pending
                        ScheduledAt = DateTime.Now,
                        RetryCount = 0,
                        MaxRetry = 3,
                        CreatedBy = customerId,
                        CreatedDate = DateTime.Now
                    };

                    dbContext.MessageQueues.Add(emailMessage);
                    await dbContext.SaveChangesAsync();
                }

                log.Info(
                    $"OTP generated for {request.email}: {otpCode} - {(isTestAccount ? "Test account, no email sent" : "Email queued")}"
                );

                //return DotnetLib.Http.HttpResponse.BuildResponse(
                //    log,
                //    url,
                //    json,
                //    CommonErrorCode.Success,
                //    ConfigManager.Instance.GetConfigWebValue("OTP_SENT_SUCCESS"),
                //    new
                //    {
                //        email = request.email,
                //        expireInSeconds = otpExpireMinutes * 60
                //    }
                //);

                return DotnetLib.Http.HttpResponse.BuildResponse(
                    log,
                    url,
                    json,
                    CommonErrorCode.Success,
                    ConfigManager.Instance.GetConfigWebValue("OTP_SENT_SUCCESS"),
                    new { email = request.email, expireInSeconds = otpExpireMinutes * 60 }
                );
            }
            catch (Exception exception)
            {
                log.Error("Exception: ", exception);
            }
            return DotnetLib.Http.HttpResponse.BuildResponse(
                log,
                url,
                json,
                CommonErrorCode.SystemError,
                ConfigManager.Instance.GetConfigWebValue("SYSTEM_FAILURE"),
                new { }
            );
        }

        /// <summary>
        /// Resend OTP - Only works if user has already requested OTP before
        /// Has reduced cooldown (30 seconds vs 60 seconds for RequestOtp)
        /// </summary>
        public async Task<IActionResult> ResendOtp(HttpRequest httpRequest, RequestOtpReq request)
        {
            var url = httpRequest.Path;
            var json = JsonConvert.SerializeObject(request);
            log.Debug("URL: " + url + " => ResendOtp Request: " + json);

            try
            {
                string lang = CommonLogic.GetLanguage(httpRequest, request.lang);

                // Validate email is required
                if (string.IsNullOrEmpty(request.email))
                {
                    return DotnetLib.Http.HttpResponse.BuildResponse(
                        log,
                        url,
                        json,
                        CommonErrorCode.RequiredFieldMissing,
                        ConfigManager.Instance.GetConfigWebValue("EMAIL_REQUIRED", lang),
                        new { }
                    );
                }

                // Check if there's an existing OTP record for this email
                var existingOtp = dbContext
                    .OtpVerifications.Where(o => o.UserEmail == request.email)
                    .OrderByDescending(o => o.CreatedDate)
                    .FirstOrDefault();

                // RESEND requires existing OTP - must have requested OTP before
                if (existingOtp == null)
                {
                    log.Warn($"ResendOtp failed: No existing OTP record for {request.email}");
                    return DotnetLib.Http.HttpResponse.BuildResponse(
                        log,
                        url,
                        json,
                        CommonErrorCode.OtpNotRequested,
                        ConfigManager.Instance.GetConfigWebValue("OTP_NOT_REQUESTED", lang),
                        new { }
                    );
                }

                // RESEND has reduced cooldown: 60 seconds (vs 60 seconds for RequestOtp)
                int minSecondsBetweenResend = 60;
                var secondsSinceLastRequest = (
                    DateTime.Now - (existingOtp.CreatedDate ?? DateTime.Now)
                ).TotalSeconds;

                if (secondsSinceLastRequest < minSecondsBetweenResend)
                {
                    var waitSeconds = (int)(minSecondsBetweenResend - secondsSinceLastRequest);
                    log.Warn(
                        $"ResendOtp: Too soon for {request.email}. Last request {secondsSinceLastRequest:F0}s ago."
                    );

                    return DotnetLib.Http.HttpResponse.BuildResponse(
                        log,
                        url,
                        json,
                        CommonErrorCode.OtpTooManyRequests,
                        $"Please wait {waitSeconds} seconds before resending OTP.",
                        new
                        {
                            waitSeconds = waitSeconds,
                            canResendAt = existingOtp.CreatedDate?.AddSeconds(
                                minSecondsBetweenResend
                            )
                        }
                    );
                }

                // Generate new 6-digit OTP (fixed 111111 for test account abc@gmail.com)
                bool isTestAccount = request.email.ToLower() == "abc@gmail.com";
                string otpCode = isTestAccount ? "111111" : GenerateOtp();

                // OTP expires in 5 minutes
                int otpExpireMinutes = 5;

                // Get customer ID (should exist since OTP record exists)
                var customer = dbContext
                    .CustomerInfos.Where(c => c.Email == request.email)
                    .FirstOrDefault();
                decimal? customerId = customer?.Id ?? existingOtp.CustomerId;

                // UPDATE existing OTP record with new code and expiry
                existingOtp.OtpCode = otpCode;
                existingOtp.ExpiredAt = DateTime.Now.AddMinutes(otpExpireMinutes);
                existingOtp.AttemptCount = 0; // Reset attempt count
                existingOtp.IsUsed = false; // Reset to unused
                existingOtp.CustomerId = customerId;
                existingOtp.CreatedDate = DateTime.Now; // Update to track resend time

                log.Info($"ResendOtp: Updated OTP record ID={existingOtp.Id} for {request.email}");

                await dbContext.SaveChangesAsync();

                // Skip email sending for test account
                if (!isTestAccount)
                {
                    // Add to MESSAGE_QUEUE for background email sending
                    string templateCode = "OTP_LOGIN";

                    var template = dbContext.MessageTemplates.FirstOrDefault(
                        t => t.TemplateCode == templateCode && t.Status.HasValue && t.Status.Value
                    );

                    if (template == null)
                    {
                        log.Error($"Template '{templateCode}' not found in MESSAGE_TEMPLATE");
                        throw new Exception($"Email template '{templateCode}' not found");
                    }

                    // Get subject based on language
                    string emailSubject =
                        lang == "en"
                            ? (template.SubjectEn ?? template.Subject ?? "")
                            : lang == "vi"
                                ? (template.Subject ?? "")
                                : (template.SubjectLo ?? template.Subject ?? "");

                    // Get content based on language
                    string emailContent =
                        lang == "en"
                            ? (template.ContentEn ?? template.Content ?? "")
                            : lang == "vi"
                                ? (template.Content ?? "")
                                : (template.ContentLo ?? template.Content ?? "");

                    // Replace placeholders
                    emailContent = emailContent
                        .Replace("{{OTP_CODE}}", otpCode)
                        .Replace("{{EXPIRE_MINUTES}}", otpExpireMinutes.ToString());

                    emailSubject = emailSubject
                        .Replace("{{OTP_CODE}}", otpCode)
                        .Replace("{{EXPIRE_MINUTES}}", otpExpireMinutes.ToString());

                    var emailMessageID = (int)
                        await Database.DbLogic.GenIdAsync(dbContext, "MESSAGE_QUEUE_SEQ");
                    var emailMessage = new MessageQueue
                    {
                        Id = emailMessageID,
                        MessageType = 1, // Email
                        Recipient = request.email,
                        Subject = emailSubject,
                        Content = emailContent,
                        Priority = true, // High priority
                        Status = 0, // Pending
                        ScheduledAt = DateTime.Now,
                        RetryCount = 0,
                        MaxRetry = 3,
                        CreatedBy = customerId,
                        CreatedDate = DateTime.Now
                    };

                    dbContext.MessageQueues.Add(emailMessage);
                    await dbContext.SaveChangesAsync();
                }

                log.Info(
                    $"ResendOtp: OTP resent for {request.email}: {otpCode} - {(isTestAccount ? "Test account, no email sent" : "Email queued")}"
                );

                return DotnetLib.Http.HttpResponse.BuildResponse(
                    log,
                    url,
                    json,
                    CommonErrorCode.Success,
                    ConfigManager.Instance.GetConfigWebValue("OTP_RESENT_SUCCESS", lang),
                    new { email = request.email, expireInSeconds = otpExpireMinutes * 60 }
                );
            }
            catch (Exception exception)
            {
                log.Error("ResendOtp Exception: ", exception);
            }
            return DotnetLib.Http.HttpResponse.BuildResponse(
                log,
                url,
                json,
                CommonErrorCode.SystemError,
                ConfigManager.Instance.GetConfigWebValue("SYSTEM_FAILURE"),
                new { }
            );
        }

        /// <summary>
        /// Verify OTP and complete login - return JWT token
        /// </summary>
        public async Task<IActionResult> VerifyOtp(HttpRequest httpRequest, VerifyOtpReq request)
        {
            var url = httpRequest.Path;
            var json = JsonConvert.SerializeObject(request);
            log.Debug("URL: " + url + " => Request: " + json);
            try
            {
                if (string.IsNullOrEmpty(request.email) || string.IsNullOrEmpty(request.otpCode))
                {
                    string lang = CommonLogic.GetLanguage(httpRequest, request.lang);
                    return DotnetLib.Http.HttpResponse.BuildResponse(
                        log,
                        url,
                        json,
                        CommonErrorCode.RequiredFieldMissing,
                        ConfigManager.Instance.GetConfigWebValue("EMAIL_OTP_REQUIRED", lang),
                        new { }
                    );
                }

                // Get language for response messages
                string responseLang = CommonLogic.GetLanguage(httpRequest, request.lang);

                // Find valid OTP
                var otpRecord = dbContext
                    .OtpVerifications.Where(
                        o =>
                            o.UserEmail == request.email
                            && o.OtpCode == request.otpCode
                            && o.IsUsed == false
                            && o.ExpiredAt > DateTime.Now
                    )
                    .OrderByDescending(o => o.CreatedDate)
                    .FirstOrDefault();

                if (otpRecord == null)
                {
                    // Check if OTP exists but expired or used
                    var anyOtp = dbContext
                        .OtpVerifications.Where(
                            o => o.UserEmail == request.email && o.OtpCode == request.otpCode
                        )
                        .FirstOrDefault();

                    if (anyOtp != null)
                    {
                        if (anyOtp.IsUsed == true)
                        {
                            return DotnetLib.Http.HttpResponse.BuildResponse(
                                log,
                                url,
                                json,
                                CommonErrorCode.OtpAlreadyUsed,
                                ConfigManager.Instance.GetConfigWebValue(
                                    "OTP_ALREADY_USED",
                                    responseLang
                                ),
                                new { }
                            );
                        }
                        if (anyOtp.ExpiredAt <= DateTime.Now)
                        {
                            return DotnetLib.Http.HttpResponse.BuildResponse(
                                log,
                                url,
                                json,
                                CommonErrorCode.OtpExpired,
                                ConfigManager.Instance.GetConfigWebValue(
                                    "OTP_EXPIRED",
                                    responseLang
                                ),
                                new { }
                            );
                        }
                    }

                    return DotnetLib.Http.HttpResponse.BuildResponse(
                        log,
                        url,
                        json,
                        CommonErrorCode.OtpInvalid,
                        ConfigManager.Instance.GetConfigWebValue("OTP_INVALID", responseLang),
                        new { }
                    );
                }

                // Mark OTP as used
                otpRecord.IsUsed = true;

                // Get customer info
                var customer = dbContext
                    .CustomerInfos.Where(c => c.Email == request.email)
                    .FirstOrDefault();

                if (customer == null)
                {
                    return DotnetLib.Http.HttpResponse.BuildResponse(
                        log,
                        url,
                        json,
                        CommonErrorCode.UserNotFound,
                        ConfigManager.Instance.GetConfigWebValue("USER_NOT_FOUND", responseLang),
                        new { }
                    );
                }

                // Update customer verification status
                customer.IsVerified = true;
                customer.LastLoginDate = DateTime.Now;
                customer.LastUpdate = DateTime.Now;

                // Generate JWT tokens
                int tokenExpireHours = 24;
                int refreshTokenExpireDays = 30;

                string accessToken = CommonLogic.GenToken(
                    configuration,
                    customer.Email ?? "",
                    customer.Id.ToString() ?? ""
                );
                string refreshToken = CommonLogic.GenRefreshToken(
                    configuration,
                    customer.Email ?? ""
                );
                var expiresAt = DateTime.Now.AddHours(tokenExpireHours);
                var refreshExpiresAt = DateTime.Now.AddDays(refreshTokenExpireDays);

                // Revoke old tokens
                var oldTokens = dbContext
                    .UserTokens.Where(t => t.CustomerId == customer.Id && t.IsRevoked == false)
                    .ToList();

                foreach (var oldToken in oldTokens)
                {
                    oldToken.IsRevoked = true;
                }

                // Save new token
                var tokenId = (int)await Database.DbLogic.GenIdAsync(dbContext, "USER_TOKEN_SEQ");
                var userToken = new UserToken
                {
                    Id = tokenId,
                    CustomerId = customer.Id,
                    AccessToken = accessToken,
                    RefreshToken = refreshToken,
                    TokenType = "Bearer",
                    DeviceInfo = httpRequest.Headers["User-Agent"].ToString(),
                    IpAddress = GetClientIpAddress(httpRequest),
                    ExpiredAt = expiresAt,
                    RefreshExpiredAt = refreshExpiresAt,
                    IsRevoked = false,
                    CreatedDate = DateTime.Now,
                    LastUsed = DateTime.Now
                };

                dbContext.UserTokens.Add(userToken);
                await dbContext.SaveChangesAsync();

                return DotnetLib.Http.HttpResponse.BuildResponse(
                    log,
                    url,
                    json,
                    CommonErrorCode.Success,
                    ConfigManager.Instance.GetConfigWebValue("LOGIN_SUCCESS", responseLang),
                    new
                    {
                        userId = customer.Id,
                        email = customer.Email ?? "",
                        fullName = $"{customer.SurName} {customer.LastName}".Trim(),
                        avatarUrl = customer.AvatarUrl,
                        msisdn = customer.PhoneNumber,
                        accessToken,
                        refreshToken,
                        expiresAt
                    }
                );
            }
            catch (Exception exception)
            {
                log.Error("Exception: ", exception);
            }
            return DotnetLib.Http.HttpResponse.BuildResponse(
                log,
                url,
                json,
                CommonErrorCode.SystemError,
                ConfigManager.Instance.GetConfigWebValue("SYSTEM_FAILURE"),
                new { }
            );
        }

        /// <summary>
        /// Generate 6-digit OTP code
        /// </summary>
        private string GenerateOtp()
        {
            using (var rng = System.Security.Cryptography.RandomNumberGenerator.Create())
            {
                var bytes = new byte[4];
                rng.GetBytes(bytes);
                var number = Math.Abs(BitConverter.ToInt32(bytes, 0)) % 1000000;
                return number.ToString("D6");
            }
        }

        /// <summary>
        /// Get client IP address
        /// </summary>
        private string GetClientIpAddress(HttpRequest httpRequest)
        {
            var ipAddress = httpRequest.Headers["X-Forwarded-For"].FirstOrDefault();
            if (string.IsNullOrEmpty(ipAddress))
            {
                ipAddress = httpRequest.HttpContext.Connection.RemoteIpAddress?.ToString();
            }
            return ipAddress ?? "Unknown";
        }

        public async Task<IActionResult> GoogleLogin(
            HttpRequest httpRequest,
            GoogleLoginReq request
        )
        {
            var url = httpRequest.Path;
            var json = JsonConvert.SerializeObject(request);
            log.Debug("URL: " + url + " => GoogleLogin Request: " + json);

            try
            {
                string lang = CommonLogic.GetLanguage(httpRequest, request?.lang);

                var clientId = configuration["Google:ClientId"];
                var redirectUri = configuration["Google:RedirectUri"];

                if (string.IsNullOrEmpty(clientId) || string.IsNullOrEmpty(redirectUri))
                {
                    log.Error("Google Auth configuration missing");
                    return DotnetLib.Http.HttpResponse.BuildResponse(
                        log,
                        url,
                        json,
                        CommonErrorCode.SystemError,
                        ConfigManager.Instance.GetConfigWebValue("GOOGLE_CONFIG_MISSING", lang),
                        new { }
                    );
                }

                var googleUrl =
                    $"https://accounts.google.com/o/oauth2/v2/auth?client_id={clientId}&redirect_uri={redirectUri}&response_type=code&scope=email%20profile&prompt=select_account";

                return DotnetLib.Http.HttpResponse.BuildResponse(
                    log,
                    url,
                    json,
                    CommonErrorCode.Success,
                    ConfigManager.Instance.GetConfigWebValue("SUCCESS", lang),
                    new { url = googleUrl }
                );
            }
            catch (Exception ex)
            {
                log.Error("GoogleLogin Exception: ", ex);
                return DotnetLib.Http.HttpResponse.BuildResponse(
                    log,
                    url,
                    json,
                    CommonErrorCode.SystemError,
                    ConfigManager.Instance.GetConfigWebValue("SYSTEM_FAILURE"),
                    new { }
                );
            }
        }

        public async Task<IActionResult> GoogleCallback(
            HttpRequest httpRequest,
            GoogleCallbackReq request
        )
        {
            var url = httpRequest.Path;
            var json = JsonConvert.SerializeObject(request);
            log.Debug("URL: " + url + " => GoogleCallback Request: " + json);

            try
            {
                // Get language for response messages
                string lang = CommonLogic.GetLanguage(httpRequest, request.lang);

                if (string.IsNullOrEmpty(request.code))
                {
                    return DotnetLib.Http.HttpResponse.BuildResponse(
                        log,
                        url,
                        json,
                        CommonErrorCode.RequiredFieldMissing,
                        ConfigManager.Instance.GetConfigWebValue("GOOGLE_CODE_REQUIRED", lang),
                        new { }
                    );
                }

                var clientId = configuration["Google:ClientId"];
                var clientSecret = configuration["Google:ClientSecret"];
                var redirectUri = !string.IsNullOrEmpty(request.redirectUri)
                    ? request.redirectUri
                    : configuration["Google:RedirectUri"];

                using (var httpClient = new HttpClient())
                {
                    // 1. Exchange code for token
                    var tokenRequestContent = new FormUrlEncodedContent(
                        new[]
                        {
                            new KeyValuePair<string, string>("code", request.code),
                            new KeyValuePair<string, string>("client_id", clientId),
                            new KeyValuePair<string, string>("client_secret", clientSecret),
                            new KeyValuePair<string, string>("redirect_uri", redirectUri),
                            new KeyValuePair<string, string>("grant_type", "authorization_code")
                        }
                    );

                    var tokenResponse = await httpClient.PostAsync(
                        "https://oauth2.googleapis.com/token",
                        tokenRequestContent
                    );
                    var tokenResponseString = await tokenResponse.Content.ReadAsStringAsync();

                    if (!tokenResponse.IsSuccessStatusCode)
                    {
                        log.Error($"Google Token Exchange Failed: {tokenResponseString}");
                        return DotnetLib.Http.HttpResponse.BuildResponse(
                            log,
                            url,
                            json,
                            CommonErrorCode.ExternalServiceError,
                            ConfigManager.Instance.GetConfigWebValue(
                                "GOOGLE_TOKEN_EXCHANGE_FAILED",
                                lang
                            ),
                            new { error = tokenResponseString }
                        );
                    }

                    var tokenData = JsonConvert.DeserializeObject<JObject>(tokenResponseString);
                    var accessToken = tokenData["access_token"]?.ToString();

                    if (string.IsNullOrEmpty(accessToken))
                    {
                        return DotnetLib.Http.HttpResponse.BuildResponse(
                            log,
                            url,
                            json,
                            CommonErrorCode.ExternalServiceError,
                            ConfigManager.Instance.GetConfigWebValue(
                                "GOOGLE_NO_ACCESS_TOKEN",
                                lang
                            ),
                            new { }
                        );
                    }

                    // 2. Get User Info
                    httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
                        "Bearer",
                        accessToken
                    );
                    var userInfoResponse = await httpClient.GetAsync(
                        "https://www.googleapis.com/oauth2/v2/userinfo"
                    );
                    var userInfoString = await userInfoResponse.Content.ReadAsStringAsync();

                    if (!userInfoResponse.IsSuccessStatusCode)
                    {
                        log.Error($"Google User Info Failed: {userInfoString}");
                        return DotnetLib.Http.HttpResponse.BuildResponse(
                            log,
                            url,
                            json,
                            CommonErrorCode.ExternalServiceError,
                            ConfigManager.Instance.GetConfigWebValue(
                                "GOOGLE_USERINFO_FAILED",
                                lang
                            ),
                            new { error = userInfoString }
                        );
                    }

                    var userInfo = JsonConvert.DeserializeObject<JObject>(userInfoString);
                    var email = userInfo["email"]?.ToString();
                    var name = userInfo["name"]?.ToString(); // Full name
                    var picture = userInfo["picture"]?.ToString();

                    if (string.IsNullOrEmpty(email))
                    {
                        return DotnetLib.Http.HttpResponse.BuildResponse(
                            log,
                            url,
                            json,
                            CommonErrorCode.ExternalServiceError,
                            ConfigManager.Instance.GetConfigWebValue("GOOGLE_NO_EMAIL", lang),
                            new { }
                        );
                    }

                    // 3. Login or Register logic
                    var customer = dbContext.CustomerInfos.FirstOrDefault(c => c.Email == email);

                    if (customer == null)
                    {
                        // Register new user
                        var newCustomerId = await Database.DbLogic.GenIdAsync(
                            dbContext,
                            "CUSTOMER_INFO_SEQ"
                        );

                        // Try to split name
                        string surname = name;
                        string lastname = "";
                        if (!string.IsNullOrEmpty(name))
                        {
                            var parts = name.Split(' ');
                            if (parts.Length > 1)
                            {
                                lastname = parts[parts.Length - 1];
                                surname = string.Join(" ", parts.Take(parts.Length - 1));
                            }
                        }
                        else
                        {
                            surname = email.Split('@')[0];
                        }

                        customer = new CustomerInfo
                        {
                            Id = newCustomerId,
                            Email = email,
                            SurName = surname,
                            LastName = lastname,
                            AvatarUrl = picture,
                            Status = true,
                            IsVerified = true, // Verified by Google
                            CreatedDate = DateTime.Now,
                            LastUpdate = DateTime.Now
                        };
                        dbContext.CustomerInfos.Add(customer);
                        await dbContext.SaveChangesAsync();
                        log.Info(
                            $"Created new customer via Google Login: ID={newCustomerId}, Email={email}"
                        );
                    }
                    else
                    {
                        // Update existing user info if needed or just log them in
                        customer.IsVerified = true;
                        if (
                            string.IsNullOrEmpty(customer.AvatarUrl)
                            && !string.IsNullOrEmpty(picture)
                        )
                        {
                            customer.AvatarUrl = picture;
                        }
                        customer.LastLoginDate = DateTime.Now;
                        customer.LastUpdate = DateTime.Now;
                        await dbContext.SaveChangesAsync();
                        log.Info(
                            $"Existing customer logged in via Google: ID={customer.Id}, Email={email}"
                        );
                    }

                    // 4. Generate JWT
                    int tokenExpireHours = 24;
                    int refreshTokenExpireDays = 30;

                    string jwtAccessToken = CommonLogic.GenToken(
                        configuration,
                        customer.Email ?? "",
                        customer.Id.ToString() ?? ""
                    );
                    string jwtRefreshToken = CommonLogic.GenRefreshToken(
                        configuration,
                        customer.Email ?? ""
                    );
                    var expiresAt = DateTime.Now.AddHours(tokenExpireHours);
                    var refreshExpiresAt = DateTime.Now.AddDays(refreshTokenExpireDays);

                    // Revoke old tokens
                    var oldTokens = dbContext
                        .UserTokens.Where(t => t.CustomerId == customer.Id && t.IsRevoked == false)
                        .ToList();

                    foreach (var oldToken in oldTokens)
                    {
                        oldToken.IsRevoked = true;
                    }

                    // Save new token
                    var tokenId = (int)
                        await Database.DbLogic.GenIdAsync(dbContext, "USER_TOKEN_SEQ");
                    var userToken = new UserToken
                    {
                        Id = tokenId,
                        CustomerId = customer.Id,
                        AccessToken = jwtAccessToken,
                        RefreshToken = jwtRefreshToken,
                        TokenType = "Bearer",
                        DeviceInfo = httpRequest.Headers["User-Agent"].ToString(),
                        IpAddress = GetClientIpAddress(httpRequest),
                        ExpiredAt = expiresAt,
                        RefreshExpiredAt = refreshExpiresAt,
                        IsRevoked = false,
                        CreatedDate = DateTime.Now,
                        LastUsed = DateTime.Now
                    };

                    dbContext.UserTokens.Add(userToken);
                    await dbContext.SaveChangesAsync();

                    return DotnetLib.Http.HttpResponse.BuildResponse(
                        log,
                        url,
                        json,
                        CommonErrorCode.Success,
                        ConfigManager.Instance.GetConfigWebValue("GOOGLE_LOGIN_SUCCESS", lang),
                        new
                        {
                            userId = customer.Id,
                            email = customer.Email ?? "",
                            fullName = $"{customer.SurName} {customer.LastName}".Trim(),
                            avatarUrl = customer.AvatarUrl,
                            msisdn = customer.PhoneNumber,
                            accessToken = jwtAccessToken,
                            refreshToken = jwtRefreshToken,
                            expiresAt
                        }
                    );
                }
            }
            catch (Exception exception)
            {
                log.Error("GoogleCallback Exception: ", exception);
                return DotnetLib.Http.HttpResponse.BuildResponse(
                    log,
                    url,
                    json,
                    CommonErrorCode.SystemError,
                    ConfigManager.Instance.GetConfigWebValue("SYSTEM_FAILURE"),
                    new { }
                );
            }
        }

        public async Task<IActionResult> PartnerLogin(HttpRequest httpRequest, PartnerLoginReq request)
        {
            var url = httpRequest.Path;
            var json = JsonConvert.SerializeObject(request);
            log.Debug("URL: " + url + " => GoogleCallback Request: " + json);

            try
            {
                byte[] keyBytes = Encoding.UTF8.GetBytes(configuration["Partner:EncryptionKey"] ?? "");

                var decrypt = DotnetLib.Secure.SecureLogic.DecryptAes(
                    request.data.Replace(" ", "+"),
                    configuration["Partner:EncryptionKey"] ?? ""
                );
                if (decrypt == null)
                {
                    return DotnetLib.Http.HttpResponse.BuildResponse(
                        log,
                        url,
                        json,
                        CommonErrorCode.Error,
                        ConfigManager.Instance.GetConfigWebValue("INVALID_PARTNER_DATA"),
                        new { }
                    );
                }

                var requestData = JsonConvert.DeserializeObject<JObject>(decrypt);
                log.Debug("Decrypted PartnerLogin data: " + JsonConvert.SerializeObject(requestData));
                string email = requestData["email"]?.ToString() ?? "";
                if (string.IsNullOrEmpty(email))
                {
                    return DotnetLib.Http.HttpResponse.BuildResponse(
                        log,
                        url,
                        json,
                        CommonErrorCode.RequiredFieldMissing,
                        ConfigManager.Instance.GetConfigWebValue("EMAIL_REQUIRED"),
                        new { }
                    );
                }

                // Get customer info
                var customer = dbContext
                    .CustomerInfos.Where(c => c.Email == email)
                    .FirstOrDefault();

                if (customer == null)
                {
                    // create a new customer
                    customer = new CustomerInfo
                    {
                        Id = await Database.DbLogic.GenIdAsync(dbContext, "CUSTOMER_INFO_SEQ"),
                        Email = email,
                        SurName = requestData["fullName"]?.ToString() ?? "",
                        LastName = requestData["fullName"]?.ToString() ?? "",
                        AvatarUrl = requestData["avatarUrl"]?.ToString() ?? "",
                        PhoneNumber = requestData["phoneNumber"]?.ToString() ?? "",
                        Status = true,
                        IsVerified = true,
                        CreatedDate = DateTime.Now,
                        LastUpdate = DateTime.Now
                    };
                    dbContext.CustomerInfos.Add(customer);
                    await dbContext.SaveChangesAsync();
                }

                // Update customer verification status
                customer.IsVerified = true;
                customer.LastLoginDate = DateTime.Now;
                customer.LastUpdate = DateTime.Now;

                // Generate JWT tokens
                int tokenExpireHours = 24;
                int refreshTokenExpireDays = 30;

                string accessToken = CommonLogic.GenToken(
                    configuration,
                    customer.Email ?? "",
                    customer.Id.ToString() ?? ""
                );
                string refreshToken = CommonLogic.GenRefreshToken(
                    configuration,
                    customer.Email ?? ""
                );
                var expiresAt = DateTime.Now.AddHours(tokenExpireHours);
                var refreshExpiresAt = DateTime.Now.AddDays(refreshTokenExpireDays);

                // Revoke old tokens
                var oldTokens = dbContext
                    .UserTokens.Where(t => t.CustomerId == customer.Id && t.IsRevoked == false)
                    .ToList();

                foreach (var oldToken in oldTokens)
                {
                    oldToken.IsRevoked = true;
                }

                // Save new token
                var tokenId = (int)await Database.DbLogic.GenIdAsync(dbContext, "USER_TOKEN_SEQ");
                var userToken = new UserToken
                {
                    Id = tokenId,
                    CustomerId = customer.Id,
                    AccessToken = accessToken,
                    RefreshToken = refreshToken,
                    TokenType = "Bearer",
                    DeviceInfo = httpRequest.Headers["User-Agent"].ToString(),
                    IpAddress = GetClientIpAddress(httpRequest),
                    ExpiredAt = expiresAt,
                    RefreshExpiredAt = refreshExpiresAt,
                    IsRevoked = false,
                    CreatedDate = DateTime.Now,
                    LastUsed = DateTime.Now
                };

                dbContext.UserTokens.Add(userToken);
                await dbContext.SaveChangesAsync();

                return DotnetLib.Http.HttpResponse.BuildResponse(
                    log,
                    url,
                    json,
                    CommonErrorCode.Success,
                    ConfigManager.Instance.GetConfigWebValue("LOGIN_SUCCESS", "en"),
                    new
                    {
                        userId = customer.Id,
                        email = customer.Email ?? "",
                        fullName = $"{customer.SurName} {customer.LastName}".Trim(),
                        avatarUrl = customer.AvatarUrl,
                        msisdn = customer.PhoneNumber,
                        accessToken,
                        refreshToken,
                        expiresAt
                    }
                );
            }
            catch (Exception exception)
            {
                log.Error("GoogleCallback Exception: ", exception);
            }
            return DotnetLib.Http.HttpResponse.BuildResponse(
                log,
                url,
                json,
                CommonErrorCode.SystemError,
                ConfigManager.Instance.GetConfigWebValue("SYSTEM_FAILURE"),
                new { }
            );
        }
    }
}
