using Common.Constant;
using Common.Logic;
using Database.Database;
using log4net;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Newtonsoft.Json;
using SicboSub.Api.DTO;
using SicboSub.Api.Singleton;

namespace SicboSub.Api.Business
{
    /// <summary>
    /// Implementation cho Exchange Business Logic với OTP verification
    /// </summary>
    public class ExchangeBusinessImpl : IExchangeBusiness
    {
        private static readonly ILog log = LogManager.GetLogger(typeof(ExchangeBusinessImpl));

        private readonly ModelContext dbContext;
        private readonly IConfiguration configuration;

        // OTP Configuration
        private const int OTP_LENGTH = 6;
        private const int OTP_EXPIRE_MINUTES = 1;
        private const int MAX_ATTEMPT_PER_OTP = 3;
        private const int MAX_OTP_PER_DAY = 10;
        private const int RESEND_COOLDOWN_SECONDS = 60;

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

        /// <summary>
        /// Yêu cầu OTP để thực hiện Exchange
        /// Flow: Validate -> Check pending OTP -> Check rate limit -> Create OTP -> Send SMS via MT table
        /// </summary>
        public async Task<IActionResult> RequestOtp(HttpRequest httpRequest, ExchangeRequestOtpReq request)
        {
            var url = httpRequest.Path;
            var json = JsonConvert.SerializeObject(request);
            log.Debug("URL: " + url + " => Request: " + json);

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

                // 1. Validate token
                string token = httpRequest.Headers["Authorization"].ToString();
                if (string.IsNullOrEmpty(token))
                {
                    return DotnetLib.Http.HttpResponse.BuildResponse(
                        log, url, json,
                        CommonErrorCode.UnauthorizedAccess,
                        ConfigManager.Instance.GetConfigWebValue("UNAUTHORIZED", lang),
                        new { }
                    );
                }

                // 2. Validate request
                if (string.IsNullOrEmpty(request.msisdn) || request.configId <= 0)
                {
                    return DotnetLib.Http.HttpResponse.BuildResponse(
                        log, url, json,
                        CommonErrorCode.InvalidParameter,
                        ConfigManager.Instance.GetConfigWebValue("INVALID_PARAM", lang),
                        new { }
                    );
                }

                // 3. Check user exists
                var user = await dbContext.AccountUsers.AsNoTracking()
                    .FirstOrDefaultAsync(u => u.Msisdn == request.msisdn);

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

                // 4. Get Exchange Config
                var exchangeConfig = await dbContext.ExchangeConfigs.AsNoTracking()
                    .FirstOrDefaultAsync(e => e.Id == request.configId && e.IsActive == 1);

                if (exchangeConfig == null)
                {
                    return DotnetLib.Http.HttpResponse.BuildResponse(
                        log, url, json,
                        CommonErrorCode.PackageNotFound,
                        ConfigManager.Instance.GetConfigWebValue("EXCHANGE_CONFIG_NOT_FOUND", lang),
                        new { }
                    );
                }

                // 5. Check user has enough coins
                if (user.WinCoin < exchangeConfig.CoinCost)
                {
                    return DotnetLib.Http.HttpResponse.BuildResponse(
                        log, url, json,
                        CommonErrorCode.InsufficientBalance,
                        ConfigManager.Instance.GetConfigWebValue("INSUFFICIENT_COIN", lang),
                        new { currentBalance = user.WinCoin, required = exchangeConfig.CoinCost }
                    );
                }

                // 6. Check pending OTP (chưa hết hạn)
                var pendingOtp = await dbContext.ExchangeOtps.AsNoTracking()
                    .FirstOrDefaultAsync(o => o.Msisdn == request.msisdn 
                        && o.Status == ExchangeOtpStatus.Pending 
                        && o.ExpiredTime > DateTime.Now);

                if (pendingOtp != null)
                {
                    var remainingSeconds = (int)(pendingOtp.ExpiredTime!.Value - DateTime.Now).TotalSeconds;
                    return DotnetLib.Http.HttpResponse.BuildResponse(
                        log, url, json,
                        CommonErrorCode.OtpTooManyRequests,
                        ConfigManager.Instance.GetConfigWebValue("OTP_PENDING_EXISTS", lang),
                        new { expiresInSeconds = remainingSeconds }
                    );
                }

                // 7. Check rate limit (max OTP per day)
                var today = DateTime.Today;
                var todayOtpCount = await dbContext.ExchangeOtps.AsNoTracking()
                    .CountAsync(o => o.Msisdn == request.msisdn && o.CreatedTime >= today);

                if (todayOtpCount >= MAX_OTP_PER_DAY)
                {
                    return DotnetLib.Http.HttpResponse.BuildResponse(
                        log, url, json,
                        CommonErrorCode.RateLimitExceeded,
                        ConfigManager.Instance.GetConfigWebValue("OTP_RATE_LIMIT_EXCEEDED", lang),
                        new { maxPerDay = MAX_OTP_PER_DAY }
                    );
                }

                // Get dynamic limits from config
                string limitUserStr = ConfigManager.Instance.GetConfigWebValue("EXCHANGE_USER_DAILY_LIMIT") ?? "100";
                string limitSystemStr = ConfigManager.Instance.GetConfigWebValue("EXCHANGE_SYSTEM_DAILY_LIMIT") ?? "1000";
                
                decimal limitUser = decimal.TryParse(limitUserStr, out decimal parsedLimitUser) ? parsedLimitUser : 100;
                decimal limitSystem = decimal.TryParse(limitSystemStr, out decimal parsedLimitSystem) ? parsedLimitSystem : 1000;

                // CHECK 1: Max withdrawal < limitUser HTG/day per user
                var totalWithdrawnTodayUser = await dbContext.ExchangeHistories.AsNoTracking()
                    .Where(h => h.Msisdn == request.msisdn && h.ExchangeTime >= today && h.Status >= 0)
                    .SumAsync(h => h.MoneyAdded ?? 0);

                if (totalWithdrawnTodayUser + (exchangeConfig.MoneyReceived ?? 0) > limitUser)
                {
                    return DotnetLib.Http.HttpResponse.BuildResponse(
                        log, url, json,
                        CommonErrorCode.LimitExceeded, 
                        ConfigManager.Instance.GetConfigWebValue("EXCHANGE_DAILY_LIMIT_EXCEEDED", lang),
                        new { currentDailyTotal = totalWithdrawnTodayUser, limit = limitUser }
                    );
                }

                // CHECK 2: Max withdrawal < limitSystem HTG/day for ALL users (System Limit)
                var totalWithdrawnTodaySystem = await dbContext.ExchangeHistories.AsNoTracking()
                    .Where(h => h.ExchangeTime >= today && h.Status >= 0)
                    .SumAsync(h => h.MoneyAdded ?? 0);

                if (totalWithdrawnTodaySystem + (exchangeConfig.MoneyReceived ?? 0) > limitSystem)
                {
                    log.Warn($"System Daily Exchange Limit Reached: {totalWithdrawnTodaySystem} + {exchangeConfig.MoneyReceived} > {limitSystem}");
                    return DotnetLib.Http.HttpResponse.BuildResponse(
                        log, url, json,
                        CommonErrorCode.LimitExceeded,
                        ConfigManager.Instance.GetConfigWebValue("EXCHANGE_SYSTEM_LIMIT_EXCEEDED", lang), // Add this key to config or generic msg
                        new { limit = limitSystem }
                    );
                }

                // 8. Expire any old pending OTPs
                var oldPendingOtps = await dbContext.ExchangeOtps
                    .Where(o => o.Msisdn == request.msisdn && o.Status == ExchangeOtpStatus.Pending)
                    .ToListAsync();

                foreach (var oldOtp in oldPendingOtps)
                {
                    oldOtp.Status = ExchangeOtpStatus.Expired;
                }

                // 9. Generate new OTP
                string otpCode = GenerateOtpCode();
                //string otpCode = "111111";
                var now = DateTime.Now;
                var expiredTime = now.AddMinutes(OTP_EXPIRE_MINUTES);

                // Get next sequence value
                var otpId = await GetNextSequenceValue("EXCHANGE_OTP_SEQ");

                var newOtp = new ExchangeOtp
                {
                    Id = otpId,
                    Msisdn = request.msisdn,
                    OtpCode = otpCode,
                    ConfigId = request.configId,
                    CoinAmount = exchangeConfig.CoinCost,
                    MoneyAmount = exchangeConfig.MoneyReceived,
                    CreatedTime = now,
                    ExpiredTime = expiredTime,
                    Status = ExchangeOtpStatus.Pending,
                    AttemptCount = 0,
                    IpAddress = GetClientIpAddress(httpRequest)
                };

                dbContext.ExchangeOtps.Add(newOtp);

                // 10. Insert vào bảng MT để gửi SMS
                var mtId = await GetNextSequenceValue("MT_SEQ");
                var smsMessage = GetOtpSmsMessage(otpCode, OTP_EXPIRE_MINUTES);

                var mt = new Mt
                {
                    MtId = mtId,
                    MoHisId = (int)mtId,
                    Msisdn = request.msisdn,
                    Message = smsMessage,
                    ReceiveTime = now,
                    RetryNum = 0,
                    Channel = CommonConstant.Channel
                };
                dbContext.Mts.Add(mt);

                await dbContext.SaveChangesAsync();

                log.Info($"OTP created successfully: msisdn={request.msisdn}, otpId={otpId}");

                return DotnetLib.Http.HttpResponse.BuildResponse(
                    log, url, json,
                    CommonErrorCode.Success,
                    ConfigManager.Instance.GetConfigWebValue("OTP_SENT_SUCCESS", lang),
                    new ExchangeRequestOtpRes
                    {
                        otpId = otpId,
                        expiresInSeconds = OTP_EXPIRE_MINUTES * 60,
                        coinAmount = exchangeConfig.CoinCost ?? 0,
                        moneyAmount = exchangeConfig.MoneyReceived ?? 0
                    }
                );
            }
            catch (Exception ex)
            {
                log.Error("Exception: ", ex);
            }

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

        /// <summary>
        /// Xác thực OTP và tạo yêu cầu Exchange (Pending)
        /// Flow: Validate OTP -> Check attempts -> Verify code -> Deduct coins -> Save to EXCHANGE_HISTORY (status=0)
        /// Trình tự động sẽ quét bảng này để gọi API cộng thưởng và cập nhật status
        /// </summary>
        public async Task<IActionResult> VerifyOtp(HttpRequest httpRequest, ExchangeVerifyOtpReq request)
        {
            var url = httpRequest.Path;
            var json = JsonConvert.SerializeObject(request);
            log.Debug("URL: " + url + " => Request: " + json);

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

                // 1. Validate token
                string token = httpRequest.Headers["Authorization"].ToString();
                if (string.IsNullOrEmpty(token))
                {
                    return DotnetLib.Http.HttpResponse.BuildResponse(
                        log, url, json,
                        CommonErrorCode.UnauthorizedAccess,
                        ConfigManager.Instance.GetConfigWebValue("UNAUTHORIZED", lang),
                        new { }
                    );
                }

                // 2. Validate request
                if (string.IsNullOrEmpty(request.msisdn) || string.IsNullOrEmpty(request.otpCode))
                {
                    return DotnetLib.Http.HttpResponse.BuildResponse(
                        log, url, json,
                        CommonErrorCode.InvalidParameter,
                        ConfigManager.Instance.GetConfigWebValue("INVALID_PARAM", lang),
                        new { }
                    );
                }

                // 3. Find pending OTP
                var otp = await dbContext.ExchangeOtps
                    .FirstOrDefaultAsync(o => o.Msisdn == request.msisdn 
                        && o.Status == ExchangeOtpStatus.Pending 
                        && o.ExpiredTime > DateTime.Now);

                if (otp == null)
                {
                    return DotnetLib.Http.HttpResponse.BuildResponse(
                        log, url, json,
                        CommonErrorCode.OtpExpired,
                        ConfigManager.Instance.GetConfigWebValue("OTP_NOT_FOUND_OR_EXPIRED", lang),
                        new { }
                    );
                }

                // 4. Check max attempts
                if (otp.AttemptCount >= MAX_ATTEMPT_PER_OTP)
                {
                    otp.Status = ExchangeOtpStatus.Failed;
                    await dbContext.SaveChangesAsync();

                    return DotnetLib.Http.HttpResponse.BuildResponse(
                        log, url, json,
                        CommonErrorCode.OtpMaxAttemptsExceeded,
                        ConfigManager.Instance.GetConfigWebValue("OTP_MAX_ATTEMPTS_EXCEEDED", lang),
                        new { }
                    );
                }

                // 5. Verify OTP code
                if (otp.OtpCode != request.otpCode)
                {
                    otp.AttemptCount = (byte?)((otp.AttemptCount ?? 0) + 1);

                    // Lock if max attempts reached
                    if (otp.AttemptCount >= MAX_ATTEMPT_PER_OTP)
                    {
                        otp.Status = ExchangeOtpStatus.Failed;
                    }

                    await dbContext.SaveChangesAsync();

                    int attemptsRemaining = MAX_ATTEMPT_PER_OTP - (otp.AttemptCount ?? 0);

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

                // 6. OTP is valid - Mark as verified
                otp.Status = ExchangeOtpStatus.Verified;
                otp.VerifiedTime = DateTime.Now;

                // 7. Get user and check balance
                var user = await dbContext.AccountUsers
                    .FirstOrDefaultAsync(u => u.Msisdn == request.msisdn);

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

                // Double check balance
                if (user.WinCoin < otp.CoinAmount)
                {
                    return DotnetLib.Http.HttpResponse.BuildResponse(
                        log, url, json,
                        CommonErrorCode.InsufficientBalance,
                        ConfigManager.Instance.GetConfigWebValue("INSUFFICIENT_COIN", lang),
                        new { currentBalance = user.WinCoin, required = otp.CoinAmount }
                    );
                }

                // 8. Deduct coins from user
                user.WinCoin -= otp.CoinAmount ?? 0;

                // 9. Create Exchange History record với STATUS = 0 (Pending)
                // Trình tự động sẽ quét bảng này để gọi API cộng thưởng
                var exchangeId = await GetNextSequenceValue("EXCHANGE_HISTORY_SEQ");
                
                var exchangeHistory = new ExchangeHistory
                {
                    Id = exchangeId,
                    Msisdn = request.msisdn,
                    ConfigId = otp.ConfigId,
                    CoinDeducted = otp.CoinAmount,
                    MoneyAdded = otp.MoneyAmount,
                    ExchangeTime = DateTime.Now,
                    Status = 0  // PENDING - Chờ trình tự động xử lý cộng thưởng
                };

                dbContext.ExchangeHistories.Add(exchangeHistory);

                await dbContext.SaveChangesAsync();

                log.Info($"Exchange request created: msisdn={request.msisdn}, exchangeId={exchangeId}, coinDeducted={otp.CoinAmount}, moneyToAdd={otp.MoneyAmount}, status=PENDING");

                return DotnetLib.Http.HttpResponse.BuildResponse(
                    log, url, json,
                    CommonErrorCode.Success,
                    ConfigManager.Instance.GetConfigWebValue("EXCHANGE_REQUEST_SUCCESS", lang),
                    new ExchangeVerifyOtpRes
                    {
                        exchangeId = exchangeId,
                        coinDeducted = otp.CoinAmount ?? 0,
                        moneyAdded = otp.MoneyAmount ?? 0,
                        newBalance = user.WinCoin
                    }
                );
            }
            catch (Exception ex)
            {
                log.Error("Exception: ", ex);
            }

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

        /// <summary>
        /// Gửi lại OTP (resend)
        /// Flow: Check cooldown -> Find last pending OTP -> Create new OTP -> Send SMS
        /// </summary>
        public async Task<IActionResult> ResendOtp(HttpRequest httpRequest, ExchangeResendOtpReq request)
        {
            var url = httpRequest.Path;
            var json = JsonConvert.SerializeObject(request);
            log.Debug("URL: " + url + " => Request: " + json);

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

                // 1. Validate token
                string token = httpRequest.Headers["Authorization"].ToString();
                if (string.IsNullOrEmpty(token))
                {
                    return DotnetLib.Http.HttpResponse.BuildResponse(
                        log, url, json,
                        CommonErrorCode.UnauthorizedAccess,
                        ConfigManager.Instance.GetConfigWebValue("UNAUTHORIZED", lang),
                        new { }
                    );
                }

                // 2. Validate request
                if (string.IsNullOrEmpty(request.msisdn))
                {
                    return DotnetLib.Http.HttpResponse.BuildResponse(
                        log, url, json,
                        CommonErrorCode.InvalidParameter,
                        ConfigManager.Instance.GetConfigWebValue("INVALID_PARAM", lang),
                        new { }
                    );
                }

                // 3. Find last OTP (pending or recently expired)
                var lastOtp = await dbContext.ExchangeOtps.AsNoTracking()
                    .Where(o => o.Msisdn == request.msisdn)
                    .OrderByDescending(o => o.CreatedTime)
                    .FirstOrDefaultAsync();

                if (lastOtp == null)
                {
                    return DotnetLib.Http.HttpResponse.BuildResponse(
                        log, url, json,
                        CommonErrorCode.OtpNotRequested,
                        ConfigManager.Instance.GetConfigWebValue("OTP_NOT_REQUESTED", lang),
                        new { }
                    );
                }

                // 4. Check cooldown (60 seconds between resends)
                var secondsSinceLastOtp = (DateTime.Now - lastOtp.CreatedTime).TotalSeconds;
                if (secondsSinceLastOtp < RESEND_COOLDOWN_SECONDS)
                {
                    int waitSeconds = RESEND_COOLDOWN_SECONDS - (int)secondsSinceLastOtp;
                    return DotnetLib.Http.HttpResponse.BuildResponse(
                        log, url, json,
                        CommonErrorCode.RateLimitExceeded,
                        ConfigManager.Instance.GetConfigWebValue("OTP_RESEND_COOLDOWN", lang),
                        new { waitSeconds = waitSeconds }
                    );
                }

                // 5. Check rate limit
                var today = DateTime.Today;
                var todayOtpCount = await dbContext.ExchangeOtps.AsNoTracking()
                    .CountAsync(o => o.Msisdn == request.msisdn && o.CreatedTime >= today);

                if (todayOtpCount >= MAX_OTP_PER_DAY)
                {
                    return DotnetLib.Http.HttpResponse.BuildResponse(
                        log, url, json,
                        CommonErrorCode.RateLimitExceeded,
                        ConfigManager.Instance.GetConfigWebValue("OTP_RATE_LIMIT_EXCEEDED", lang),
                        new { maxPerDay = MAX_OTP_PER_DAY }
                    );
                }

                // 6. Expire old pending OTPs
                var oldPendingOtps = await dbContext.ExchangeOtps
                    .Where(o => o.Msisdn == request.msisdn && o.Status == ExchangeOtpStatus.Pending)
                    .ToListAsync();

                foreach (var oldOtp in oldPendingOtps)
                {
                    oldOtp.Status = ExchangeOtpStatus.Expired;
                }

                // 7. Generate new OTP with same config
                string otpCode = GenerateOtpCode();
                var now = DateTime.Now;
                var expiredTime = now.AddMinutes(OTP_EXPIRE_MINUTES);

                var otpId = await GetNextSequenceValue("EXCHANGE_OTP_SEQ");

                var newOtp = new ExchangeOtp
                {
                    Id = otpId,
                    Msisdn = request.msisdn,
                    OtpCode = otpCode,
                    ConfigId = lastOtp.ConfigId,
                    CoinAmount = lastOtp.CoinAmount,
                    MoneyAmount = lastOtp.MoneyAmount,
                    CreatedTime = now,
                    ExpiredTime = expiredTime,
                    Status = ExchangeOtpStatus.Pending,
                    AttemptCount = 0,
                    IpAddress = GetClientIpAddress(httpRequest)
                };

                dbContext.ExchangeOtps.Add(newOtp);

                // 8. Insert vào bảng MT để gửi SMS
                var mtId = await GetNextSequenceValue("MT_SEQ");
                var smsMessage = GetOtpSmsMessage(otpCode, OTP_EXPIRE_MINUTES);

                var mt = new Mt
                {
                    MtId = mtId,
                    MoHisId = null,
                    Msisdn = request.msisdn,
                    Message = smsMessage,
                    ReceiveTime = now,
                    RetryNum = 0,
                    Channel = "API"
                };

                dbContext.Mts.Add(mt);

                await dbContext.SaveChangesAsync();

                log.Info($"OTP resent successfully: msisdn={request.msisdn}, otpId={otpId}");

                return DotnetLib.Http.HttpResponse.BuildResponse(
                    log, url, json,
                    CommonErrorCode.Success,
                    ConfigManager.Instance.GetConfigWebValue("OTP_RESENT_SUCCESS", lang),
                    new ExchangeRequestOtpRes
                    {
                        otpId = otpId,
                        expiresInSeconds = OTP_EXPIRE_MINUTES * 60,
                        coinAmount = lastOtp.CoinAmount ?? 0,
                        moneyAmount = lastOtp.MoneyAmount ?? 0
                    }
                );
            }
            catch (Exception ex)
            {
                log.Error("Exception: ", ex);
            }

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

        /// <summary>
        /// Lấy lịch sử rút đổi thưởng
        /// </summary>
        public async Task<IActionResult> GetHistory(HttpRequest httpRequest, ExchangeHistoryReq request)
        {
            var url = httpRequest.Path;
            var json = JsonConvert.SerializeObject(request);
            log.Debug("URL: " + url + " => Request: " + json);

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

                // 1. Validate token
                string token = httpRequest.Headers["Authorization"].ToString();
                if (string.IsNullOrEmpty(token))
                {
                    return DotnetLib.Http.HttpResponse.BuildResponse(
                        log, url, json,
                        CommonErrorCode.UnauthorizedAccess,
                        ConfigManager.Instance.GetConfigWebValue("UNAUTHORIZED", lang),
                        new { }
                    );
                }

                // 2. Validate request
                if (string.IsNullOrEmpty(request.msisdn))
                {
                    return DotnetLib.Http.HttpResponse.BuildResponse(
                        log, url, json,
                        CommonErrorCode.InvalidParameter,
                        ConfigManager.Instance.GetConfigWebValue("INVALID_PARAM", lang),
                        new { }
                    );
                }

                // 3. Build query
                var query = dbContext.ExchangeHistories.AsNoTracking()
                    .Where(x => x.Msisdn == request.msisdn);

                // 4. Filter by date
                if (!string.IsNullOrEmpty(request.fromDate))
                {
                    if (DateTime.TryParseExact(request.fromDate, "dd/MM/yyyy HH:mm:ss", System.Globalization.CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.None, out DateTime fromDate))
                    {
                        query = query.Where(x => x.ExchangeTime >= fromDate);
                    }
                }

                if (!string.IsNullOrEmpty(request.toDate))
                {
                    if (DateTime.TryParseExact(request.toDate, "dd/MM/yyyy HH:mm:ss", System.Globalization.CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.None, out DateTime toDate))
                    {
                        query = query.Where(x => x.ExchangeTime <= toDate);
                    }
                }

                // 5. Sort & Paging
                query = query.OrderByDescending(x => x.ExchangeTime);

                var total = await query.CountAsync();
                
                int pageIdx = request.pageNumber > 0 ? request.pageNumber : 0;
                int pageSize = request.pageSize > 0 ? request.pageSize : 20;

                var data = await query.Skip(pageIdx * pageSize).Take(pageSize).ToListAsync();

                // 6. Map to DTO
                var result = data.Select(x => new ExchangeHistoryRes
                {
                    id = x.Id ?? 0,
                    msisdn = x.Msisdn ?? "",
                    coinDeducted = x.CoinDeducted ?? 0,
                    moneyAdded = x.MoneyAdded ?? 0,
                    exchangeTime = x.ExchangeTime ?? DateTime.MinValue,
                    status = x.Status ?? 0,
                    // Map status name can be done here or FE
                    statusName = GetStatusName(x.Status, lang)
                }).ToList();

                return DotnetLib.Http.HttpResponse.BuildResponse(
                    log, url, json,
                    CommonErrorCode.Success,
                    "Success",
                    result
                );
            }
            catch (Exception ex)
            {
                log.Error("Exception: ", ex);
            }

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

        private string GetStatusName(byte? status, string lang)
        {
            // Simple mapping, can be improved with config
            return status switch
            {
                0 => ConfigManager.Instance.GetConfigWebValue("EXCHANGE_STATUS_PENDING", lang) == "" ? "Pending" : ConfigManager.Instance.GetConfigWebValue("EXCHANGE_STATUS_PENDING", lang),
                1 => ConfigManager.Instance.GetConfigWebValue("EXCHANGE_STATUS_SUCCESS", lang) == "" ? "Success" : ConfigManager.Instance.GetConfigWebValue("EXCHANGE_STATUS_SUCCESS", lang),
                2 => ConfigManager.Instance.GetConfigWebValue("EXCHANGE_STATUS_FAILED", lang) == "" ? "Failed" : ConfigManager.Instance.GetConfigWebValue("EXCHANGE_STATUS_FAILED", lang),
                _ => "Unknown"
            };
        }

        #region Private Helper Methods

        /// <summary>
        /// Generate random 6-digit OTP code
        /// </summary>
        private string GenerateOtpCode()
        {
            var random = new Random();
            int otp = random.Next(0, 999999);
            return otp.ToString().PadLeft(OTP_LENGTH, '0');
        }

        /// <summary>
        /// Get next value from Oracle sequence
        /// </summary>
        private async Task<decimal> GetNextSequenceValue(string sequenceName)
        {
            var connection = dbContext.Database.GetDbConnection();
            if (connection.State != System.Data.ConnectionState.Open)
            {
                await connection.OpenAsync();
            }

            using var command = connection.CreateCommand();
            command.CommandText = $"SELECT {sequenceName}.NEXTVAL FROM DUAL";
            var result = await command.ExecuteScalarAsync();

            return Convert.ToDecimal(result);
        }

        /// <summary>
        /// Get client IP address from request
        /// </summary>
        private string? GetClientIpAddress(HttpRequest httpRequest)
        {
            // Check for forwarded IP (if behind proxy/load balancer)
            var forwardedFor = httpRequest.Headers["X-Forwarded-For"].FirstOrDefault();
            if (!string.IsNullOrEmpty(forwardedFor))
            {
                return forwardedFor.Split(',').FirstOrDefault()?.Trim();
            }

            return httpRequest.HttpContext.Connection.RemoteIpAddress?.ToString();
        }

        /// <summary>
        /// Generate SMS message for OTP
        /// </summary>
        private string GetOtpSmsMessage(string otpCode, int expireMinutes)
        {
            // Lấy template từ config hoặc dùng mặc định
            var template = ConfigManager.Instance.GetConfig("OTP_SMS_TEMPLATE") 
                ?? "Ma xac thuc rut thuong cua ban la: {OTP}. Ma co hieu luc trong {EXPIRE} phut.";

            return template
                .Replace("{OTP}", otpCode)
                .Replace("{EXPIRE}", expireMinutes.ToString());
        }

        #endregion
    }
}
