package com.honey.honey.service;

import com.honey.honey.dto.*;
import com.honey.honey.model.*;
import com.honey.honey.repository.*;
import com.honey.honey.util.IpUtils;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import jakarta.persistence.EntityManager;
import jakarta.persistence.Query;
import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.Root;
import jakarta.persistence.criteria.Subquery;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.Instant;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor
public class AdminUserService {

    private static final long TICKETS_MULTIPLIER = 1_000_000L;

    private static final BigDecimal TICKETS_TO_USD = new BigDecimal("0.001"); // 1000 tickets = 1 USD

    private final UserARepository userARepository;
    private final UserBRepository userBRepository;
    private final UserDRepository userDRepository;
    private final TransactionRepository transactionRepository;
    private final PaymentRepository paymentRepository;
    private final PayoutRepository payoutRepository;
    private final UserTaskClaimRepository userTaskClaimRepository;
    private final TaskRepository taskRepository;
    private final EntityManager entityManager;

    public Page<AdminUserDto> getUsers(
            Pageable pageable,
            String search,
            Integer banned,
            String countryCode,
            String languageCode,
            Integer dateRegFrom,
            Integer dateRegTo,
            Long balanceAMin,
            Long balanceAMax,
            Long balanceBMin,
            Long balanceBMax,
            Integer referralCountMin,
            Integer referralCountMax,
            Integer referrerId,
            Integer referralLevel,
            String ipFilter,
            Boolean botActive,
            Integer depositCountMin,
            String sortBy,
            String sortDir,
            boolean excludeMasters) {

        List<Integer> masterIds = excludeMasters ? userDRepository.findMasterUserIds() : List.of();

        // Build specification for filtering UserA fields
        Specification<UserA> spec = (root, query, cb) -> {
            List<Predicate> predicates = new ArrayList<>();

            if (!masterIds.isEmpty()) {
                predicates.add(cb.not(root.get("id").in(masterIds)));
            }

            // IP filter (exact match; stored as varbinary(16))
            if (ipFilter != null && !ipFilter.trim().isEmpty()) {
                byte[] ipBytes = IpUtils.ipToBytes(ipFilter.trim());
                if (ipBytes != null) {
                    predicates.add(cb.equal(root.get("ip"), ipBytes));
                }
            }

            // Search filter (ID, Telegram ID, or name)
            if (search != null && !search.trim().isEmpty()) {
                String searchPattern = "%" + search.trim() + "%";
                List<Predicate> searchPredicates = new ArrayList<>();
                
                // Try to parse as integer (for ID search)
                Integer searchId = parseInteger(search.trim());
                if (searchId != null) {
                    searchPredicates.add(cb.equal(root.get("id"), searchId));
                }
                
                // Try to parse as long (for Telegram ID search)
                Long searchTelegramId = parseLong(search.trim());
                if (searchTelegramId != null) {
                    searchPredicates.add(cb.equal(root.get("telegramId"), searchTelegramId));
                }
                
                // Name search (telegramName or screenName)
                searchPredicates.add(cb.or(
                    cb.like(cb.lower(root.get("telegramName")), searchPattern.toLowerCase()),
                    cb.like(cb.lower(root.get("screenName")), searchPattern.toLowerCase())
                ));
                
                predicates.add(cb.or(searchPredicates.toArray(new Predicate[0])));
            }

            // Ban status filter
            if (banned != null) {
                predicates.add(cb.equal(root.get("banned"), banned));
            }

            // Country code filter
            if (countryCode != null && !countryCode.trim().isEmpty()) {
                predicates.add(cb.equal(root.get("countryCode"), countryCode.trim()));
            }

            // Language code filter
            if (languageCode != null && !languageCode.trim().isEmpty()) {
                predicates.add(cb.equal(root.get("languageCode"), languageCode.trim()));
            }

            // Registration date range filter
            if (dateRegFrom != null) {
                predicates.add(cb.greaterThanOrEqualTo(root.get("dateReg"), dateRegFrom));
            }
            if (dateRegTo != null) {
                // Include the end date (<= instead of <)
                // Convert to end of day timestamp (23:59:59)
                int endOfDayTimestamp = dateRegTo + 86399; // Add 86399 seconds (23:59:59)
                predicates.add(cb.lessThanOrEqualTo(root.get("dateReg"), endOfDayTimestamp));
            }

            // Balance A / Balance B filters via subqueries
            if (balanceAMin != null || balanceAMax != null || balanceBMin != null || balanceBMax != null) {
                Subquery<Integer> subB = query.subquery(Integer.class);
                Root<UserB> br = subB.from(UserB.class);
                subB.select(br.get("id"));
                List<Predicate> subPreds = new ArrayList<>();
                if (balanceAMin != null) subPreds.add(cb.greaterThanOrEqualTo(br.get("balanceA"), balanceAMin));
                if (balanceAMax != null) subPreds.add(cb.lessThanOrEqualTo(br.get("balanceA"), balanceAMax));
                if (balanceBMin != null) subPreds.add(cb.greaterThanOrEqualTo(br.get("balanceB"), balanceBMin));
                if (balanceBMax != null) subPreds.add(cb.lessThanOrEqualTo(br.get("balanceB"), balanceBMax));
                subB.where(cb.and(subPreds.toArray(new Predicate[0])));
                predicates.add(cb.in(root.get("id")).value(subB));
            }
            if (referralCountMin != null || referralCountMax != null) {
                Subquery<Integer> subD = query.subquery(Integer.class);
                Root<UserD> dr = subD.from(UserD.class);
                subD.select(dr.get("id"));
                var refSum = cb.sum(cb.sum(cb.sum(cb.sum(dr.get("referals1"), dr.get("referals2")),
                        dr.get("referals3")), dr.get("referals4")), dr.get("referals5"));
                List<Predicate> subPreds = new ArrayList<>();
                if (referralCountMin != null) subPreds.add(cb.greaterThanOrEqualTo(refSum.as(Long.class), cb.literal(referralCountMin.longValue())));
                if (referralCountMax != null) subPreds.add(cb.lessThanOrEqualTo(refSum.as(Long.class), cb.literal(referralCountMax.longValue())));
                subD.where(cb.and(subPreds.toArray(new Predicate[0])));
                predicates.add(cb.in(root.get("id")).value(subD));
            }

            if (referrerId != null && referrerId > 0 && referralLevel != null && referralLevel >= 1 && referralLevel <= 5) {
                Subquery<Integer> subRef = query.subquery(Integer.class);
                Root<UserD> refDr = subRef.from(UserD.class);
                subRef.select(refDr.get("id"));
                Predicate refererPred = switch (referralLevel) {
                    case 1 -> cb.equal(refDr.get("refererId1"), referrerId);
                    case 2 -> cb.equal(refDr.get("refererId2"), referrerId);
                    case 3 -> cb.equal(refDr.get("refererId3"), referrerId);
                    case 4 -> cb.equal(refDr.get("refererId4"), referrerId);
                    case 5 -> cb.equal(refDr.get("refererId5"), referrerId);
                    default -> cb.disjunction();
                };
                subRef.where(refererPred);
                predicates.add(cb.in(root.get("id")).value(subRef));
            }

            if (botActive != null) {
                predicates.add(cb.equal(root.get("botActive"), botActive));
            }

            if (depositCountMin != null && depositCountMin > 0) {
                Subquery<Integer> subDep = query.subquery(Integer.class);
                Root<UserB> depRoot = subDep.from(UserB.class);
                subDep.select(depRoot.get("id"));
                subDep.where(cb.greaterThanOrEqualTo(depRoot.get("depositCount"), depositCountMin));
                predicates.add(cb.in(root.get("id")).value(subDep));
            }

            return cb.and(predicates.toArray(new Predicate[0]));
        };

        Set<String> sortRequiresJoin = Set.of("balanceA", "balanceB", "depositTotal", "withdrawTotal", "referralCount", "profit");
        boolean useJoinSort = sortBy != null && sortRequiresJoin.contains(sortBy);
        List<UserA> userList;
        long totalElements;

        if (useJoinSort) {
            List<Integer> orderedIds = getOrderedUserIdsForAdminList(
                    search, banned, countryCode, languageCode,
                    dateRegFrom, dateRegTo, balanceAMin, balanceAMax, balanceBMin, balanceBMax,
                    referralCountMin, referralCountMax,
                    referrerId, referralLevel, ipFilter,
                    botActive, depositCountMin,
                    sortBy, sortDir != null ? sortDir : "desc",
                    pageable.getPageSize(), (int) pageable.getOffset(),
                    masterIds);
            totalElements = userARepository.count(spec);
            if (orderedIds.isEmpty()) {
                userList = List.of();
            } else {
                List<UserA> unsorted = userARepository.findAllById(orderedIds);
                userList = unsorted.stream()
                        .sorted(Comparator.comparingInt(u -> orderedIds.indexOf(u.getId())))
                        .collect(Collectors.toList());
            }
        } else {
            Page<UserA> userPage = userARepository.findAll(spec, pageable);
            userList = userPage.getContent();
            totalElements = userPage.getTotalElements();
        }

        List<Integer> userIds = userList.stream().map(UserA::getId).collect(Collectors.toList());

        Map<Integer, UserB> userBMap = userBRepository.findAllById(userIds).stream()
                .collect(Collectors.toMap(UserB::getId, ub -> ub));

        Map<Integer, UserD> userDMap = userDRepository.findAllById(userIds).stream()
                .collect(Collectors.toMap(UserD::getId, ud -> ud));

        // Map to DTOs (filtering is done in DB via specification subqueries)
        List<AdminUserDto> pageContent = userList.stream()
                .map(userA -> {
                    UserB userB = userBMap.getOrDefault(userA.getId(),
                            UserB.builder()
                                    .id(userA.getId())
                                    .balanceA(0L)
                                    .balanceB(0L)
                                    .depositTotal(0L)
                                    .depositCount(0)
                                    .withdrawTotal(0L)
                                    .withdrawCount(0)
                                    .build());

                    UserD userD = userDMap.getOrDefault(userA.getId(),
                            UserD.builder()
                                    .id(userA.getId())
                                    .referals1(0)
                                    .referals2(0)
                                    .referals3(0)
                                    .referals4(0)
                                    .referals5(0)
                                    .fromReferals1(0L)
                                    .fromReferals2(0L)
                                    .fromReferals3(0L)
                                    .fromReferals4(0L)
                                    .fromReferals5(0L)
                                    .build());

                    int totalReferrals = userD.getReferals1() + userD.getReferals2() +
                            userD.getReferals3() + userD.getReferals4() + userD.getReferals5();
                    long totalCommissions = userD.getFromReferals1() + userD.getFromReferals2() +
                            userD.getFromReferals3() + userD.getFromReferals4() + userD.getFromReferals5();
                    long profit = userB.getDepositTotal() - userB.getWithdrawTotal();
                    BigDecimal depositTotalUsd = ticketsToUsd(userB.getDepositTotal());
                    BigDecimal withdrawTotalUsd = ticketsToUsd(userB.getWithdrawTotal());
                    BigDecimal profitUsd = ticketsToUsd(profit);

                    return AdminUserDto.builder()
                            .id(userA.getId())
                            .screenName(userA.getScreenName())
                            .telegramId(userA.getTelegramId())
                            .telegramName(userA.getTelegramName())
                            .balanceA(userB.getBalanceA())
                            .balanceB(userB.getBalanceB())
                            .depositTotal(userB.getDepositTotal())
                            .depositCount(userB.getDepositCount())
                            .withdrawTotal(userB.getWithdrawTotal())
                            .withdrawCount(userB.getWithdrawCount())
                            .dateReg(userA.getDateReg())
                            .dateLogin(userA.getDateLogin())
                            .banned(userA.getBanned())
                            .countryCode(userA.getCountryCode())
                            .languageCode(userA.getLanguageCode())
                            .referralCount(totalReferrals)
                            .totalCommissionsEarned(totalCommissions)
                            .profit(profit)
                            .depositTotalUsd(depositTotalUsd)
                            .withdrawTotalUsd(withdrawTotalUsd)
                            .profitUsd(profitUsd)
                            .build();
                })
                .collect(Collectors.toList());

        return new PageImpl<>(pageContent, pageable, totalElements);
    }

    /**
     * Returns ordered user ids for admin list when sorting by UserB/UserD columns (balanceA, depositTotal, etc.).
     */
    @SuppressWarnings("unchecked")
    private List<Integer> getOrderedUserIdsForAdminList(
            String search,
            Integer banned,
            String countryCode,
            String languageCode,
            Integer dateRegFrom,
            Integer dateRegTo,
            Long balanceAMin,
            Long balanceAMax,
            Long balanceBMin,
            Long balanceBMax,
            Integer referralCountMin,
            Integer referralCountMax,
            Integer referrerId,
            Integer referralLevel,
            String ipFilter,
            Boolean botActive,
            Integer depositCountMin,
            String sortBy,
            String sortDir,
            int limit,
            int offset,
            List<Integer> excludeUserIds) {
        StringBuilder sql = new StringBuilder(
                "SELECT a.id FROM db_users_a a " +
                "INNER JOIN db_users_b b ON a.id = b.id " +
                "INNER JOIN db_users_d d ON a.id = d.id WHERE 1=1");
        List<Object> params = new ArrayList<>();
        int paramIndex = 1;

        if (excludeUserIds != null && !excludeUserIds.isEmpty()) {
            sql.append(" AND a.id NOT IN (");
            for (int i = 0; i < excludeUserIds.size(); i++) {
                sql.append(i == 0 ? "?" : ",?");
            }
            sql.append(")");
            params.addAll(excludeUserIds);
            paramIndex += excludeUserIds.size();
        }

        if (search != null && !search.trim().isEmpty()) {
            String searchPattern = "%" + search.trim() + "%";
            Integer searchId = parseInteger(search.trim());
            Long searchTgId = parseLong(search.trim());
            if (searchId != null) {
                sql.append(" AND a.id = ?");
                params.add(searchId);
                paramIndex++;
            } else if (searchTgId != null) {
                sql.append(" AND a.telegram_id = ?");
                params.add(searchTgId);
                paramIndex++;
            } else {
                sql.append(" AND (a.screen_name LIKE ? OR a.telegram_name LIKE ?)");
                params.add(searchPattern);
                params.add(searchPattern);
                paramIndex += 2;
            }
        }
        if (banned != null) {
            sql.append(" AND a.banned = ?");
            params.add(banned);
            paramIndex++;
        }
        if (countryCode != null && !countryCode.trim().isEmpty()) {
            sql.append(" AND a.country_code = ?");
            params.add(countryCode.trim());
            paramIndex++;
        }
        if (languageCode != null && !languageCode.trim().isEmpty()) {
            sql.append(" AND a.language_code = ?");
            params.add(languageCode.trim());
            paramIndex++;
        }
        if (dateRegFrom != null) {
            sql.append(" AND a.date_reg >= ?");
            params.add(dateRegFrom);
            paramIndex++;
        }
        if (dateRegTo != null) {
            sql.append(" AND a.date_reg <= ?");
            params.add(dateRegTo);
            paramIndex++;
        }
        if (balanceAMin != null) {
            sql.append(" AND b.balance_a >= ?");
            params.add(balanceAMin);
            paramIndex++;
        }
        if (balanceAMax != null) {
            sql.append(" AND b.balance_a <= ?");
            params.add(balanceAMax);
            paramIndex++;
        }
        if (balanceBMin != null) {
            sql.append(" AND b.balance_b >= ?");
            params.add(balanceBMin);
            paramIndex++;
        }
        if (balanceBMax != null) {
            sql.append(" AND b.balance_b <= ?");
            params.add(balanceBMax);
            paramIndex++;
        }
        if (referralCountMin != null || referralCountMax != null) {
            sql.append(" AND (d.referals_1 + d.referals_2 + d.referals_3 + d.referals_4 + d.referals_5)");
            if (referralCountMin != null && referralCountMax != null) {
                sql.append(" BETWEEN ? AND ?");
                params.add(referralCountMin.longValue());
                params.add(referralCountMax.longValue());
                paramIndex += 2;
            } else if (referralCountMin != null) {
                sql.append(" >= ?");
                params.add(referralCountMin.longValue());
                paramIndex++;
            } else {
                sql.append(" <= ?");
                params.add(referralCountMax.longValue());
                paramIndex++;
            }
        }
        if (referrerId != null && referrerId > 0 && referralLevel != null && referralLevel >= 1 && referralLevel <= 5) {
            sql.append(" AND d.referer_id_").append(referralLevel).append(" = ?");
            params.add(referrerId);
            paramIndex++;
        }
        if (ipFilter != null && !ipFilter.trim().isEmpty()) {
            byte[] ipBytes = IpUtils.ipToBytes(ipFilter.trim());
            if (ipBytes != null) {
                sql.append(" AND a.ip = ?");
                params.add(ipBytes);
                paramIndex++;
            }
        }
        if (botActive != null) {
            sql.append(" AND a.bot_active = ?");
            params.add(Boolean.TRUE.equals(botActive) ? 1 : 0);
            paramIndex++;
        }
        if (depositCountMin != null && depositCountMin > 0) {
            sql.append(" AND b.deposit_count >= ?");
            params.add(depositCountMin);
            paramIndex++;
        }

        String orderColumn = switch (sortBy != null ? sortBy : "") {
            case "balanceA" -> "b.balance_a";
            case "balanceB" -> "b.balance_b";
            case "depositTotal" -> "b.deposit_total";
            case "withdrawTotal" -> "b.withdraw_total";
            case "referralCount" -> "(d.referals_1 + d.referals_2 + d.referals_3 + d.referals_4 + d.referals_5)";
            case "profit" -> "(b.deposit_total - b.withdraw_total)";
            default -> "a.id";
        };
        String direction = "asc".equalsIgnoreCase(sortDir) ? " ASC" : " DESC";
        sql.append(" ORDER BY ").append(orderColumn).append(direction);
        sql.append(" LIMIT ? OFFSET ?");
        params.add(limit);
        params.add(offset);

        Query query = entityManager.createNativeQuery(sql.toString());
        for (int i = 0; i < params.size(); i++) {
            query.setParameter(i + 1, params.get(i));
        }
        List<?> rows = query.getResultList();
        List<Integer> ids = new ArrayList<>();
        for (Object row : rows) {
            if (row instanceof Number n) {
                ids.add(n.intValue());
            }
        }
        return ids;
    }

    private Integer parseInteger(String str) {
        try {
            return Integer.parseInt(str);
        } catch (NumberFormatException e) {
            return null;
        }
    }

    private Long parseLong(String str) {
        try {
            return Long.parseLong(str);
        } catch (NumberFormatException e) {
            return null;
        }
    }

    public AdminUserDetailDto getUserDetail(Integer userId, boolean excludeMasters) {
        if (excludeMasters) {
            Optional<UserD> userDOpt = userDRepository.findById(userId);
            if (userDOpt.isPresent()) {
                UserD d = userDOpt.get();
                if (d.getMasterId() > 0 && d.getId().equals(d.getMasterId())) {
                    return null;
                }
            }
        }
        Optional<UserA> userAOpt = userARepository.findById(userId);
        if (userAOpt.isEmpty()) {
            return null;
        }

        UserA userA = userAOpt.get();
        UserB userB = userBRepository.findById(userId).orElse(
                UserB.builder()
                        .id(userId)
                        .balanceA(0L)
                        .balanceB(0L)
                        .depositTotal(0L)
                        .depositCount(0)
                        .withdrawTotal(0L)
                        .withdrawCount(0)
                        .withdrawalsDisabled(false)
                        .build());

        UserD userD = userDRepository.findById(userId).orElse(
                UserD.builder()
                        .id(userId)
                        .referals1(0)
                        .referals2(0)
                        .referals3(0)
                        .referals4(0)
                        .referals5(0)
                        .fromReferals1(0L)
                        .fromReferals2(0L)
                        .fromReferals3(0L)
                        .fromReferals4(0L)
                        .fromReferals5(0L)
                        .toReferer1(0L)
                        .toReferer2(0L)
                        .toReferer3(0L)
                        .toReferer4(0L)
                        .toReferer5(0L)
                        .masterId(0)
                        .build());

        int totalReferrals = userD.getReferals1() + userD.getReferals2() +
                userD.getReferals3() + userD.getReferals4() + userD.getReferals5();
        long totalCommissions = userD.getFromReferals1() + userD.getFromReferals2() +
                userD.getFromReferals3() + userD.getFromReferals4() + userD.getFromReferals5();

        // Build referral levels
        List<ReferralLevelDto> referralLevels = new ArrayList<>();
        for (int level = 1; level <= 5; level++) {
            int refererId = switch (level) {
                case 1 -> userD.getRefererId1();
                case 2 -> userD.getRefererId2();
                case 3 -> userD.getRefererId3();
                case 4 -> userD.getRefererId4();
                case 5 -> userD.getRefererId5();
                default -> 0;
            };
            int referralCount = switch (level) {
                case 1 -> userD.getReferals1();
                case 2 -> userD.getReferals2();
                case 3 -> userD.getReferals3();
                case 4 -> userD.getReferals4();
                case 5 -> userD.getReferals5();
                default -> 0;
            };
            long commissionsEarned = switch (level) {
                case 1 -> userD.getFromReferals1();
                case 2 -> userD.getFromReferals2();
                case 3 -> userD.getFromReferals3();
                case 4 -> userD.getFromReferals4();
                case 5 -> userD.getFromReferals5();
                default -> 0L;
            };
            long commissionsPaid = switch (level) {
                case 1 -> userD.getToReferer1();
                case 2 -> userD.getToReferer2();
                case 3 -> userD.getToReferer3();
                case 4 -> userD.getToReferer4();
                case 5 -> userD.getToReferer5();
                default -> 0L;
            };

            referralLevels.add(ReferralLevelDto.builder()
                    .level(level)
                    .refererId(refererId > 0 ? refererId : null)
                    .referralCount(referralCount)
                    .commissionsEarned(commissionsEarned)
                    .commissionsPaid(commissionsPaid)
                    .commissionsEarnedUsd(ticketsToUsd(commissionsEarned))
                    .commissionsPaidUsd(ticketsToUsd(commissionsPaid))
                    .build());
        }

        BigDecimal depositTotalUsd = ticketsToUsd(userB.getDepositTotal());
        BigDecimal withdrawTotalUsd = ticketsToUsd(userB.getWithdrawTotal());
        BigDecimal totalCommissionsEarnedUsd = ticketsToUsd(totalCommissions);

        return AdminUserDetailDto.builder()
                .id(userA.getId())
                .screenName(userA.getScreenName())
                .telegramId(userA.getTelegramId())
                .telegramName(userA.getTelegramName())
                .isPremium(userA.getIsPremium())
                .languageCode(userA.getLanguageCode())
                .countryCode(userA.getCountryCode())
                .deviceCode(userA.getDeviceCode())
                .avatarUrl(userA.getAvatarUrl())
                .dateReg(userA.getDateReg())
                .dateLogin(userA.getDateLogin())
                .banned(userA.getBanned())
                .ipAddress(IpUtils.bytesToIp(userA.getIp()))
                .balanceA(userB.getBalanceA())
                .balanceB(userB.getBalanceB())
                .depositTotal(userB.getDepositTotal())
                .depositCount(userB.getDepositCount())
                .withdrawTotal(userB.getWithdrawTotal())
                .withdrawCount(userB.getWithdrawCount())
                .depositTotalUsd(depositTotalUsd)
                .withdrawTotalUsd(withdrawTotalUsd)
                .withdrawalsDisabled(Boolean.TRUE.equals(userB.getWithdrawalsDisabled()))
                .referralCount(totalReferrals)
                .totalCommissionsEarned(totalCommissions)
                .totalCommissionsEarnedUsd(totalCommissionsEarnedUsd)
                .masterId(userD.getMasterId() > 0 ? userD.getMasterId() : null)
                .referralLevels(referralLevels)
                .build();
    }

    private static BigDecimal ticketsToUsd(long ticketsBigint) {
        if (ticketsBigint == 0) return BigDecimal.ZERO;
        return BigDecimal.valueOf(ticketsBigint).divide(BigDecimal.valueOf(1_000_000L), 6, RoundingMode.HALF_UP).multiply(TICKETS_TO_USD).setScale(2, RoundingMode.HALF_UP);
    }

    public Page<AdminTransactionDto> getUserTransactions(Integer userId, Pageable pageable) {
        Page<Transaction> transactions = transactionRepository.findByUserIdOrderByCreatedAtDesc(userId, pageable);
        return transactions.map(t -> AdminTransactionDto.builder()
                .id(t.getId())
                .amount(t.getAmount())
                .type(t.getType().name())
                .taskId(t.getTaskId())
                .createdAt(t.getCreatedAt())
                .build());
    }

    /** Deposits (payments) for user detail. */
    public Page<UserDepositDto> getUserPayments(Integer userId, Pageable pageable) {
        Page<Payment> page = paymentRepository.findByUserId(userId, pageable);
        return page.map(p -> UserDepositDto.builder()
                .id(p.getId())
                .usdAmount(p.getUsdAmount())
                .status(p.getStatus().name())
                .orderId(p.getOrderId())
                .createdAt(p.getCreatedAt())
                .completedAt(p.getCompletedAt())
                .build());
    }

    /** Withdrawals (CRYPTO payouts only) for user detail. */
    public Page<UserWithdrawalDto> getUserPayouts(Integer userId, Pageable pageable) {
        Page<Payout> page = payoutRepository.findByUserIdAndType(userId, Payout.PayoutType.CRYPTO, pageable);
        return page.map(p -> UserWithdrawalDto.builder()
                .id(p.getId())
                .usdAmount(p.getUsdAmount())
                .cryptoName(p.getCryptoName())
                .amountToSend(p.getAmountToSend())
                .txhash(p.getTxhash())
                .status(p.getStatus().name())
                .paymentId(p.getPaymentId())
                .createdAt(p.getCreatedAt())
                .resolvedAt(p.getResolvedAt())
                .build());
    }

    public Map<String, Object> getUserTasks(Integer userId) {
        List<UserTaskClaim> claims = userTaskClaimRepository.findByUserId(userId);
        List<Task> allTasks = taskRepository.findAll();
        
        Map<Integer, Task> taskMap = allTasks.stream()
                .collect(Collectors.toMap(Task::getId, t -> t));

        List<AdminTaskClaimDto> completedTasks = claims.stream()
                .map(claim -> {
                    Task task = taskMap.get(claim.getTaskId());
                    // Convert LocalDateTime to Instant (assuming database stores in UTC)
                    Instant claimedAtInstant = claim.getClaimedAt() != null
                            ? claim.getClaimedAt().atZone(ZoneId.of("UTC")).toInstant()
                            : null;
                    return AdminTaskClaimDto.builder()
                            .id(claim.getId())
                            .taskId(claim.getTaskId())
                            .taskTitle(task != null ? task.getTitle() : "Unknown Task")
                            .taskType(task != null ? task.getType() : "unknown")
                            .claimedAt(claimedAtInstant)
                            .build();
                })
                .collect(Collectors.toList());

        List<Map<String, Object>> availableTasks = allTasks.stream()
                .filter(task -> !claims.stream().anyMatch(c -> c.getTaskId().equals(task.getId())))
                .map(task -> Map.<String, Object>of(
                        "id", task.getId(),
                        "title", task.getTitle(),
                        "type", task.getType(),
                        "requirement", task.getRequirement(),
                        "rewardAmount", task.getRewardAmount(),
                        "rewardType", task.getRewardType(),
                        "description", task.getDescription() != null ? task.getDescription() : ""
                ))
                .collect(Collectors.toList());

        return Map.of(
                "completed", completedTasks,
                "available", availableTasks
        );
    }

    @Transactional
    public void setBanned(Integer userId, boolean banned) {
        UserA user = userARepository.findById(userId)
                .orElseThrow(() -> new IllegalArgumentException("User not found with ID: " + userId));
        user.setBanned(banned ? 1 : 0);
        userARepository.save(user);
    }

    @Transactional
    public void setWithdrawalsEnabled(Integer userId, boolean enabled) {
        UserB userB = userBRepository.findById(userId)
                .orElseThrow(() -> new IllegalArgumentException("User not found with ID: " + userId));
        userB.setWithdrawalsDisabled(!enabled);
        userBRepository.save(userB);
    }

    @Transactional
    public BalanceAdjustmentResponse adjustBalance(Integer userId, BalanceAdjustmentRequest request) {
        // Verify user exists
        Optional<UserA> userAOpt = userARepository.findById(userId);
        if (userAOpt.isEmpty()) {
            throw new IllegalArgumentException("User not found with ID: " + userId);
        }

        // Get or create UserB
        UserB userB = userBRepository.findById(userId).orElse(
                UserB.builder()
                        .id(userId)
                        .balanceA(0L)
                        .balanceB(0L)
                        .depositTotal(0L)
                        .depositCount(0)
                        .withdrawTotal(0L)
                        .withdrawCount(0)
                        .build());

        // Store previous balances
        Long previousBalanceA = userB.getBalanceA();
        Long previousBalanceB = userB.getBalanceB();

        // Calculate new balance
        Long adjustmentAmount = request.getAmount();
        if (request.getOperation() == BalanceAdjustmentRequest.OperationType.SUBTRACT) {
            adjustmentAmount = -adjustmentAmount;
        }

        Long newBalanceA = userB.getBalanceA();
        Long newBalanceB = userB.getBalanceB();

        if (request.getBalanceType() == BalanceAdjustmentRequest.BalanceType.A) {
            newBalanceA = userB.getBalanceA() + adjustmentAmount;
            if (newBalanceA < 0) {
                throw new IllegalArgumentException("Balance A cannot be negative. Current: " + 
                        (userB.getBalanceA() / 1_000_000.0) + ", Attempted adjustment: " + 
                        (adjustmentAmount / 1_000_000.0));
            }
            userB.setBalanceA(newBalanceA);
        } else {
            newBalanceB = userB.getBalanceB() + adjustmentAmount;
            if (newBalanceB < 0) {
                throw new IllegalArgumentException("Balance B cannot be negative. Current: " + 
                        (userB.getBalanceB() / 1_000_000.0) + ", Attempted adjustment: " + 
                        (adjustmentAmount / 1_000_000.0));
            }
            userB.setBalanceB(newBalanceB);
        }

        // Save updated balance
        userBRepository.save(userB);

        // Create transaction record for audit
        Transaction transaction = Transaction.builder()
                .userId(userId)
                .amount(adjustmentAmount)
                .type(adjustmentAmount > 0 ? Transaction.TransactionType.DEPOSIT : Transaction.TransactionType.WITHDRAWAL)
                .createdAt(java.time.Instant.now())
                .build();
        transactionRepository.save(transaction);

        return BalanceAdjustmentResponse.builder()
                .newBalanceA(userB.getBalanceA())
                .newBalanceB(userB.getBalanceB())
                .previousBalanceA(previousBalanceA)
                .previousBalanceB(previousBalanceB)
                .adjustmentAmount(adjustmentAmount)
                .message("Balance adjusted successfully")
                .build();
    }
}

