package com.honey.honey.service;

import com.honey.honey.dto.CreateCryptoWithdrawalRequest;
import com.honey.honey.dto.CreatePayoutRequest;
import com.honey.honey.dto.PayoutHistoryEntryDto;
import com.honey.honey.dto.PayoutResponse;
import com.honey.honey.dto.WithdrawalApiResponse;
import com.honey.honey.model.Payout;
import com.honey.honey.model.UserB;
import com.honey.honey.repository.PayoutRepository;
import com.honey.honey.repository.UserBRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.client.HttpStatusCodeException;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import org.springframework.data.domain.PageRequest;

/**
 * Service for handling payout operations.
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class PayoutService {

    private final PayoutRepository payoutRepository;
    private final UserBRepository userBRepository;
    private final TransactionService transactionService;
    private final LocalizationService localizationService;
    private final CryptoWithdrawalService cryptoWithdrawalService;
    private final TaskService taskService;
    private final FeatureSwitchService featureSwitchService;

    // Gift type to stars amount mapping
    private static final Map<Payout.GiftType, Integer> GIFT_TO_STARS = new HashMap<>();
    
    // Gift type to total (tickets in bigint format) mapping
    private static final Map<Payout.GiftType, Long> GIFT_TO_TOTAL = new HashMap<>();
    
    // Conversion rate for STARS payout: 1 Star = 12 Tickets, so in bigint: 1 Star = 12,000,000
    private static final long STARS_TO_TOTAL_MULTIPLIER = 12_000_000L;

    // Allowed stars amounts for STARS payout (user must pick one of these)
    private static final Set<Integer> ALLOWED_STARS_AMOUNTS = Set.of(
            50, 75, 100, 150, 250, 350, 500, 750, 2500, 10000, 25000, 35000
    );
    
    static {
        GIFT_TO_STARS.put(Payout.GiftType.HEART, 15);
        GIFT_TO_STARS.put(Payout.GiftType.BEAR, 15);
        GIFT_TO_STARS.put(Payout.GiftType.GIFTBOX, 25);
        GIFT_TO_STARS.put(Payout.GiftType.FLOWER, 25);
        GIFT_TO_STARS.put(Payout.GiftType.CAKE, 50);
        GIFT_TO_STARS.put(Payout.GiftType.BOUQUET, 50);
        GIFT_TO_STARS.put(Payout.GiftType.ROCKET, 50);
        GIFT_TO_STARS.put(Payout.GiftType.CHAMPAGNE, 50);
        GIFT_TO_STARS.put(Payout.GiftType.CUP, 100);
        GIFT_TO_STARS.put(Payout.GiftType.RING, 100);
        GIFT_TO_STARS.put(Payout.GiftType.DIAMOND, 100);
        
        // Gift type to total mapping (tickets in bigint format: tickets * 1,000,000)
        // All values multiplied by 10 (e.g., 18 → 180)
        GIFT_TO_TOTAL.put(Payout.GiftType.HEART, 180_000_000L);      // 180 tickets
        GIFT_TO_TOTAL.put(Payout.GiftType.BEAR, 180_000_000L);        // 180 tickets
        GIFT_TO_TOTAL.put(Payout.GiftType.GIFTBOX, 280_000_000L);    // 280 tickets
        GIFT_TO_TOTAL.put(Payout.GiftType.FLOWER, 280_000_000L);     // 280 tickets
        GIFT_TO_TOTAL.put(Payout.GiftType.CAKE, 550_000_000L);       // 550 tickets
        GIFT_TO_TOTAL.put(Payout.GiftType.BOUQUET, 550_000_000L);   // 550 tickets
        GIFT_TO_TOTAL.put(Payout.GiftType.ROCKET, 550_000_000L);    // 550 tickets
        GIFT_TO_TOTAL.put(Payout.GiftType.CHAMPAGNE, 550_000_000L); // 550 tickets
        GIFT_TO_TOTAL.put(Payout.GiftType.CUP, 1_100_000_000L);       // 1100 tickets
        GIFT_TO_TOTAL.put(Payout.GiftType.RING, 1_100_000_000L);     // 1100 tickets
        GIFT_TO_TOTAL.put(Payout.GiftType.DIAMOND, 1_100_000_000L);   // 1100 tickets
    }

    /**
     * Validates and creates a payout request.
     * 
     * @param userId User ID
     * @param request Payout request data
     * @return Created payout
     * @throws IllegalArgumentException if validation fails
     */
    @Transactional
    public Payout createPayout(Integer userId, CreatePayoutRequest request) {
        // Require at least one deposit before allowing withdrawal
        UserB userB = userBRepository.findById(userId)
                .orElseThrow(() -> new IllegalStateException(localizationService.getMessage("user.error.balanceNotFound")));
        if (Boolean.TRUE.equals(userB.getWithdrawalsDisabled())) {
            throw new IllegalArgumentException(localizationService.getMessage("payout.error.withdrawalsRestrictedForAccount"));
        }
        if (userB.getDepositTotal() == null || userB.getDepositTotal() == 0) {
            throw new IllegalArgumentException(localizationService.getMessage("payout.error.depositRequiredToWithdraw"));
        }

        // Validate username pattern
        validateUsername(request.getUsername());
        
        // Validate and normalize quantity
        Integer quantity = request.getQuantity();
        if (quantity == null) {
            quantity = 1; // Default to 1 if not provided
        }
        validateQuantity(quantity, request.getType());
        
        // For STARS type, quantity must always be 1
        if ("STARS".equalsIgnoreCase(request.getType()) && quantity != 1) {
            throw new IllegalArgumentException(localizationService.getMessage("payout.error.quantityMustBeOne"));
        }
        
        // Validate and process based on type
        Payout payout;
        if ("STARS".equalsIgnoreCase(request.getType())) {
            // Validate that total matches stars amount * conversion rate BEFORE creating payout
            validateStarsTotal(request.getStarsAmount(), request.getTotal(), quantity);
            payout = createStarsPayout(userId, request, quantity);
        } else if ("GIFT".equalsIgnoreCase(request.getType())) {
            // Validate that total matches the gift type's expected value * quantity BEFORE creating payout
            validateGiftTotal(request.getGiftName(), request.getTotal(), quantity);
            payout = createGiftPayout(userId, request, quantity);
        } else {
            throw new IllegalArgumentException(localizationService.getMessage("payout.error.invalidPayoutType"));
        }

        // Validate tickets amount and user balance
        validateTicketsAmount(userId, payout.getTotal());

        // Deduct balance and reduce total_win_after_deposit
        deductBalance(userId, payout.getTotal());
        
        // Save payout
        payout = payoutRepository.save(payout);
        
        log.info("Payout created: id={}, userId={}, type={}, total={}", 
                payout.getId(), userId, payout.getType(), payout.getTotal());
        
        return payout;
    }

    /**
     * Creates a CRYPTO withdrawal: calls external crypto API, then on success creates payout and deducts balance.
     * Uses in-memory lock so only one withdrawal per user at a time. Keeps deposit total and maxWinAfterDeposit validation.
     */
    @Transactional
    public Payout createCryptoPayout(Integer userId, CreateCryptoWithdrawalRequest request) {
        UserB userB = userBRepository.findById(userId)
                .orElseThrow(() -> new IllegalStateException(localizationService.getMessage("user.error.balanceNotFound")));
        if (Boolean.TRUE.equals(userB.getWithdrawalsDisabled())) {
            throw new IllegalArgumentException(localizationService.getMessage("payout.error.withdrawalsRestrictedForAccount"));
        }
        if (userB.getDepositTotal() == null || userB.getDepositTotal() == 0) {
            throw new IllegalArgumentException(localizationService.getMessage("payout.error.depositRequiredToWithdraw"));
        }

        Long total = request.getTotal();
        validateTicketsAmount(userId, total);
        validateCryptoWithdrawalMaxTwoDecimals(total);

        if (payoutRepository.existsByUserIdAndStatus(userId, Payout.PayoutStatus.PROCESSING)) {
            throw new IllegalArgumentException(localizationService.getMessage("payout.error.withdrawalInProgress"));
        }

        if (!cryptoWithdrawalService.tryAcquireWithdrawal(userId)) {
            throw new IllegalArgumentException(localizationService.getMessage("payout.error.withdrawalInProgress"));
        }

        try {
            // Lock UserB row before calling external API so concurrent requests (e.g. from another instance) block
            // and cannot double-spend. We hold the lock until the transaction commits.
            userB = userBRepository.findByIdForUpdate(userId)
                    .orElseThrow(() -> new IllegalStateException(localizationService.getMessage("user.error.balanceNotFound")));
            if (userB.getBalanceA() < total) {
                throw new IllegalArgumentException(localizationService.getMessage("payout.error.insufficientBalanceDetailed",
                        String.valueOf(userB.getBalanceA() / 1_000_000.0), String.valueOf(total / 1_000_000.0)));
            }

            double amountUsd = total / 1_000_000_000.0;
            boolean noWithdrawalsYet = (userB.getWithdrawCount() != null ? userB.getWithdrawCount() : 0) == 0;
            boolean manualPay = featureSwitchService.isManualPayForAllPayoutsEnabled()
                    || (noWithdrawalsYet && taskService.hasCompletedReferral50Or100(userId));
            WithdrawalApiResponse response;
            try {
                response = cryptoWithdrawalService.postWithdrawal(userId, request.getPid(), request.getWallet().trim(), amountUsd, manualPay);
            } catch (HttpStatusCodeException e) {
                log.warn("Crypto withdrawal API HTTP error: userId={}, status={}, body={}", userId, e.getStatusCode(), e.getResponseBodyAsString());
                throw new IllegalStateException(localizationService.getMessage("withdraw.error.tryLater"));
            }

            if (response == null) {
                log.warn("Crypto withdrawal API returned null body: userId={}", userId);
                throw new IllegalStateException(localizationService.getMessage("withdraw.error.tryLater"));
            }
            if (response.getResult() != null && response.getResult().getError() != null) {
                WithdrawalApiResponse.ResultError err = response.getResult().getError();
                int code = err.getErrorCode() != null ? err.getErrorCode() : -1;
                if (code == 1) {
                    throw new IllegalArgumentException(localizationService.getMessage("withdraw.error.walletInvalidFormat"));
                }
                log.warn("Crypto withdrawal API business error: userId={}, error_code={}, error_text={}", userId, code, err.getErrorText());
                throw new IllegalStateException(localizationService.getMessage("withdraw.error.tryLater"));
            }
            if (response.getResult() == null || response.getResult().getPayment() == null) {
                log.warn("Crypto withdrawal API success but no payment in result: userId={}", userId);
                throw new IllegalStateException(localizationService.getMessage("withdraw.error.tryLater"));
            }

            WithdrawalApiResponse.Payment payment = response.getResult().getPayment();
            if (payment.getPaymentId() == null) {
                log.warn("Crypto withdrawal API returned payment with null payment_id: userId={}", userId);
                throw new IllegalStateException(localizationService.getMessage("withdraw.error.tryLater"));
            }
            BigDecimal usdAmount = BigDecimal.valueOf(total / 1_000_000_000.0).setScale(2, RoundingMode.DOWN);

            Payout payout = Payout.builder()
                    .userId(userId)
                    .username("")
                    .wallet(request.getWallet().trim())
                    .type(Payout.PayoutType.CRYPTO)
                    .giftName(null)
                    .cryptoName(payment.getTicker() != null ? payment.getTicker() : "")
                    .total(total)
                    .starsAmount(0)
                    .usdAmount(usdAmount)
                    .amountCoins(payment.getAmountCoins())
                    .commissionCoins(payment.getComissionCoins())
                    .amountToSend(payment.getAmountToSend())
                    .paymentId(payment.getPaymentId())
                    .quantity(1)
                    .status(Payout.PayoutStatus.PROCESSING)
                    .build();

            applyDeductToUserB(userB, userId, total);
            payout = payoutRepository.save(payout);
            log.debug("Crypto payout created: id={}, userId={}, pid={}, total={}", payout.getId(), userId, request.getPid(), total);
            return payout;
        } finally {
            cryptoWithdrawalService.releaseWithdrawal(userId);
        }
    }

    /**
     * Marks a payout as COMPLETED: sets status, resolvedAt, updates user withdrawTotal and withdrawCount.
     * Allowed only when payout status is PROCESSING or WAITING (e.g. from admin or cron sync).
     */
    @Transactional
    public void markPayoutCompleted(Long payoutId) {
        Payout payout = payoutRepository.findById(payoutId)
                .orElseThrow(() -> new IllegalArgumentException(localizationService.getMessage("payout.error.notFound", String.valueOf(payoutId))));
        if (payout.getStatus() != Payout.PayoutStatus.PROCESSING && payout.getStatus() != Payout.PayoutStatus.WAITING) {
            throw new IllegalArgumentException(localizationService.getMessage("payout.error.onlyProcessingCanComplete", payout.getStatus().name()));
        }
        Instant now = Instant.now();
        payout.setStatus(Payout.PayoutStatus.COMPLETED);
        payout.setResolvedAt(now);
        payout.setUpdatedAt(now);
        payoutRepository.save(payout);

        UserB userB = userBRepository.findById(payout.getUserId())
                .orElseThrow(() -> new IllegalStateException(localizationService.getMessage("user.error.balanceNotFound")));
        userB.setWithdrawTotal(userB.getWithdrawTotal() + payout.getTotal());
        userB.setWithdrawCount(userB.getWithdrawCount() + 1);
        userBRepository.save(userB);
        log.info("Payout completed: id={}, userId={}", payoutId, payout.getUserId());
    }

    /**
     * Marks a payout as CANCELLED: sets status, resolvedAt, refunds balance, creates cancellation transaction.
     * Allowed only when payout status is PROCESSING or WAITING (e.g. from admin or cron sync).
     */
    @Transactional
    public void markPayoutCancelled(Long payoutId) {
        Payout payout = payoutRepository.findById(payoutId)
                .orElseThrow(() -> new IllegalArgumentException(localizationService.getMessage("payout.error.notFound", String.valueOf(payoutId))));
        if (payout.getStatus() != Payout.PayoutStatus.PROCESSING && payout.getStatus() != Payout.PayoutStatus.WAITING) {
            throw new IllegalArgumentException(localizationService.getMessage("payout.error.onlyProcessingCanCancel", payout.getStatus().name()));
        }
        Instant now = Instant.now();
        payout.setStatus(Payout.PayoutStatus.CANCELLED);
        payout.setResolvedAt(now);
        payout.setUpdatedAt(now);
        payoutRepository.save(payout);

        UserB userB = userBRepository.findById(payout.getUserId())
                .orElseThrow(() -> new IllegalStateException(localizationService.getMessage("user.error.balanceNotFound")));
        userB.setBalanceA(userB.getBalanceA() + payout.getTotal());
        userBRepository.save(userB);

        transactionService.createCancellationOfWithdrawalTransaction(payout.getUserId(), payout.getTotal(), now);
        log.info("Payout cancelled: id={}, userId={}", payoutId, payout.getUserId());
    }

    /**
     * Creates a STARS type payout.
     * Stars amount must be one of the allowed values; total is calculated as stars * 12 (in bigint).
     */
    private Payout createStarsPayout(Integer userId, CreatePayoutRequest request, Integer quantity) {
        Integer starsAmount = request.getStarsAmount();
        if (starsAmount == null || starsAmount < 0) {
            throw new IllegalArgumentException(localizationService.getMessage("payout.error.starsAmountNonNegative"));
        }
        if (!ALLOWED_STARS_AMOUNTS.contains(starsAmount)) {
            throw new IllegalArgumentException(localizationService.getMessage("payout.error.starsAmountNotAllowed"));
        }

        // Calculate expected total from stars amount (quantity is always 1 for STARS)
        long expectedTotal = (long) starsAmount * STARS_TO_TOTAL_MULTIPLIER;
        
        return Payout.builder()
                .userId(userId)
                .username(request.getUsername())
                .type(Payout.PayoutType.STARS)
                .giftName(null)
                .total(expectedTotal) // Use calculated total, not from request
                .starsAmount(request.getStarsAmount())
                .quantity(1) // Always 1 for STARS
                .status(Payout.PayoutStatus.PROCESSING)
                .build();
    }
    
    /**
     * Validates that stars amount is allowed and total matches stars * 12 (in bigint).
     * Quantity is always 1 for STARS type.
     */
    private void validateStarsTotal(Integer starsAmount, Long total, Integer quantity) {
        if (starsAmount == null || total == null) {
            throw new IllegalArgumentException(localizationService.getMessage("payout.error.starsAmountAndTotalRequired"));
        }
        if (!ALLOWED_STARS_AMOUNTS.contains(starsAmount)) {
            throw new IllegalArgumentException(localizationService.getMessage("payout.error.starsAmountNotAllowed"));
        }
        if (quantity == null || quantity != 1) {
            throw new IllegalArgumentException(localizationService.getMessage("payout.error.quantityMustBeOne"));
        }

        long expectedTotal = (long) starsAmount * STARS_TO_TOTAL_MULTIPLIER;
        if (!total.equals(expectedTotal)) {
            throw new IllegalArgumentException(
                    localizationService.getMessage("payout.error.totalMustEqualStars",
                            String.valueOf(expectedTotal), String.valueOf(total)));
        }
    }

    /**
     * Creates a GIFT type payout.
     */
    private Payout createGiftPayout(Integer userId, CreatePayoutRequest request, Integer quantity) {
        // Validate gift name
        if (request.getGiftName() == null || request.getGiftName().isEmpty()) {
            throw new IllegalArgumentException(localizationService.getMessage("payout.error.giftNameRequired"));
        }
        
        Payout.GiftType giftType;
        try {
            giftType = Payout.GiftType.valueOf(request.getGiftName().toUpperCase());
        } catch (IllegalArgumentException e) {
            throw new IllegalArgumentException(localizationService.getMessage("payout.error.invalidGiftName", request.getGiftName()));
        }
        
        // Calculate stars amount from gift type
        Integer starsAmount = GIFT_TO_STARS.get(giftType);
        if (starsAmount == null) {
            throw new IllegalArgumentException(localizationService.getMessage("payout.error.starsAmountNotDefined", giftType.name()));
        }
        
        // Get expected total for this gift type (single gift)
        Long singleGiftTotal = GIFT_TO_TOTAL.get(giftType);
        if (singleGiftTotal == null) {
            throw new IllegalArgumentException(localizationService.getMessage("payout.error.totalNotDefined", giftType.name()));
        }
        
        // Calculate total with quantity
        long expectedTotal = singleGiftTotal * quantity;
        
        return Payout.builder()
                .userId(userId)
                .username(request.getUsername())
                .type(Payout.PayoutType.GIFT)
                .giftName(giftType)
                .total(expectedTotal) // Use calculated total (single gift * quantity)
                .starsAmount(starsAmount * quantity) // Stars amount also multiplied by quantity
                .quantity(quantity)
                .status(Payout.PayoutStatus.PROCESSING)
                .build();
    }
    
    /**
     * Validates that the total matches the expected value for the gift type * quantity.
     */
    private void validateGiftTotal(String giftName, Long total, Integer quantity) {
        if (giftName == null || giftName.isEmpty() || total == null || quantity == null) {
            throw new IllegalArgumentException(localizationService.getMessage("payout.error.giftNameTotalQuantityRequired"));
        }
        
        Payout.GiftType giftType;
        try {
            giftType = Payout.GiftType.valueOf(giftName.toUpperCase());
        } catch (IllegalArgumentException e) {
            throw new IllegalArgumentException(localizationService.getMessage("payout.error.invalidGiftName", giftName));
        }
        
        Long singleGiftTotal = GIFT_TO_TOTAL.get(giftType);
        if (singleGiftTotal == null) {
            throw new IllegalArgumentException(localizationService.getMessage("payout.error.totalNotDefined", giftType.name()));
        }
        
        long expectedTotal = singleGiftTotal * quantity;
        
        if (!total.equals(expectedTotal)) {
            throw new IllegalArgumentException(
                    localizationService.getMessage("payout.error.totalForGiftMismatch", 
                            giftType.name(), String.valueOf(quantity), String.valueOf(expectedTotal), String.valueOf(total)));
        }
    }
    
    /**
     * Validates quantity is between 1 and 100.
     */
    private void validateQuantity(Integer quantity, String type) {
        if (quantity == null) {
            throw new IllegalArgumentException(localizationService.getMessage("payout.error.quantityRequired"));
        }
        
        if (quantity < 1 || quantity > 100) {
            throw new IllegalArgumentException(localizationService.getMessage("payout.error.quantityRange"));
        }
    }

    /**
     * Validates username pattern.
     */
    private void validateUsername(String username) {
        if (username == null || username.isEmpty()) {
            throw new IllegalArgumentException(localizationService.getMessage("payout.error.usernameRequired"));
        }
        
        // Username should start with @ followed by at least 1 English letter
        if (!username.matches("^@[a-zA-Z].*")) {
            throw new IllegalArgumentException(localizationService.getMessage("payout.error.usernamePattern"));
        }
    }

    /** Units per 0.01 tickets when 1 ticket = 1_000_000. Ensures withdrawal amount has at most 2 decimal places. */
    private static final long CRYPTO_WITHDRAWAL_MIN_STEP = 10_000L;

    /**
     * Validates that crypto withdrawal total has at most 2 decimal places (e.g. 125.25 allowed, 125.125 not).
     * Total is in units where 1 ticket = 1_000_000, so 0.01 tickets = 10_000.
     */
    private void validateCryptoWithdrawalMaxTwoDecimals(Long total) {
        if (total != null && total % CRYPTO_WITHDRAWAL_MIN_STEP != 0) {
            throw new IllegalArgumentException(localizationService.getMessage("payout.error.withdrawalAmountMaxTwoDecimals"));
        }
    }

    /**
     * Validates tickets amount and user balance.
     */
    private void validateTicketsAmount(Integer userId, Long total) {
        if (total == null || total < 0) {
            throw new IllegalArgumentException(localizationService.getMessage("payout.error.ticketsAmountNonNegative"));
        }
        
        Optional<UserB> userBOpt = userBRepository.findById(userId);
        if (userBOpt.isEmpty()) {
            throw new IllegalStateException(localizationService.getMessage("user.error.balanceNotFound"));
        }
        
        UserB userB = userBOpt.get();
        if (userB.getBalanceA() < total) {
            throw new IllegalArgumentException(localizationService.getMessage("payout.error.insufficientBalanceDetailed", 
                    String.valueOf(userB.getBalanceA() / 1_000_000.0), String.valueOf(total / 1_000_000.0)));
        }
    }

    /**
     * Deducts balance from user's balance_a. Uses pessimistic lock (SELECT FOR UPDATE) on the UserB row
     * so that concurrent withdrawal requests for the same user are serialized and cannot double-spend.
     */
    private void deductBalance(Integer userId, Long total) {
        UserB userB = userBRepository.findByIdForUpdate(userId)
                .orElseThrow(() -> new IllegalStateException(localizationService.getMessage("user.error.balanceNotFound")));
        applyDeductToUserB(userB, userId, total);
    }

    /**
     * Applies balance deduction to an already-loaded (and locked) UserB.
     * Caller must hold a pessimistic lock on the UserB row (e.g. from findByIdForUpdate).
     */
    private void applyDeductToUserB(UserB userB, Integer userId, Long total) {
        if (userB.getBalanceA() < total) {
            throw new IllegalStateException(localizationService.getMessage("payout.error.insufficientBalance"));
        }
        userB.setBalanceA(userB.getBalanceA() - total);
        userBRepository.save(userB);

        try {
            transactionService.createWithdrawalTransaction(userId, total);
        } catch (Exception e) {
            log.error("Error creating withdrawal transaction: userId={}, amount={}", userId, total, e);
        }
        log.info("Balance deducted for payout: userId={}, amount={}, newBalance={}", userId, total, userB.getBalanceA());
    }

    /**
     * Converts Payout entity to PayoutResponse DTO.
     */
    public PayoutResponse toResponse(Payout payout) {
        return PayoutResponse.builder()
                .id(payout.getId())
                .username(payout.getUsername())
                .type(payout.getType().name())
                .giftName(payout.getGiftName() != null ? payout.getGiftName().name() : null)
                .total(payout.getTotal())
                .starsAmount(payout.getStarsAmount())
                .quantity(payout.getQuantity())
                .status(payout.getStatus().name())
                .createdAt(payout.getCreatedAt() != null ? payout.getCreatedAt().toEpochMilli() : null)
                .resolvedAt(payout.getResolvedAt() != null ? payout.getResolvedAt().toEpochMilli() : null)
                .build();
    }

    /**
     * Gets the last 20 payouts for the current user as history entries.
     * Uses index for optimal performance.
     * 
     * @param userId User ID
     * @param timezone Optional timezone (e.g., "Europe/London"). If null, uses UTC.
     * @param languageCode User's language code for localization (e.g., "EN", "RU")
     */
    public List<PayoutHistoryEntryDto> getUserPayoutHistory(Integer userId, String timezone, String languageCode) {
        List<Payout> payouts = payoutRepository.findLastPayoutsByUserId(
                userId,
                PageRequest.of(0, 20)
        );
        
        // Determine timezone to use
        ZoneId zoneId;
        try {
            zoneId = (timezone != null && !timezone.trim().isEmpty()) 
                    ? ZoneId.of(timezone) 
                    : ZoneId.of("UTC");
        } catch (Exception e) {
            // Invalid timezone, fallback to UTC
            zoneId = ZoneId.of("UTC");
        }
        
        // Get localized "at" word
        String atWord = localizationService.getMessage("dateTime.at", languageCode);
        if (atWord == null || atWord.isEmpty()) {
            atWord = "at"; // Fallback to English
        }
        
        // Create formatter with localized "at" word
        DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("dd.MM '" + atWord + "' HH:mm")
                .withZone(zoneId);
        
        return payouts.stream()
                .map(payout -> {
                    // Format date
                    String date = dateFormatter.format(payout.getCreatedAt());
                    
                    // Status
                    String status = payout.getStatus().name();
                    
                    return PayoutHistoryEntryDto.builder()
                            .amount(payout.getTotal()) // Return raw bigint value
                            .date(date)
                            .status(status)
                            .build();
                })
                .collect(Collectors.toList());
    }
}

