package com.rj.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.rj.entity.LbDailyUserTrade;
import com.rj.entity.LbDailyUserTradeReport;
import com.rj.entity.LbDepartmentUser;
import com.rj.mapper.LbDailyUserTradeMapper;
import com.rj.mapper.LbDailyUserTradeReportMapper;
import com.rj.service.ILbDailyUserTradeReportService;
import com.rj.service.ILbDepartmentUserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;

/**
 * <p>
 * 当天用户交易报表 服务实现类
 * </p>
 */
@Service
public class LbDailyUserTradeReportServiceImpl extends ServiceImpl<LbDailyUserTradeReportMapper, LbDailyUserTradeReport>
        implements ILbDailyUserTradeReportService {

    private static final Logger log = LoggerFactory.getLogger(LbDailyUserTradeReportServiceImpl.class);
    private static final String DATA_TYPE_REPORT_SUM = "report_sum";
    private static final String DATA_TYPE_REPORT_DETAIL = "report_detail";
    private static final BigDecimal CROSS_DAY_DIFF_TOLERANCE = new BigDecimal("3");

    @Autowired
    private ILbDepartmentUserService lbDepartmentUserService;
    @Autowired
    private LbDailyUserTradeMapper lbDailyUserTradeMapper;

    @Override
    public Page<LbDailyUserTrade> pageDailyUserTradeByUserIdAndDateRange(
            LocalDate startDate, LocalDate endDate, String userId, Integer current, Integer size) {
        LambdaQueryWrapper<LbDailyUserTrade> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(LbDailyUserTrade::getUserId, userId.trim())
                .ge(LbDailyUserTrade::getReportDate, startDate)
                .le(LbDailyUserTrade::getReportDate, endDate)
                .orderByDesc(LbDailyUserTrade::getReportDate)
                .orderByDesc(LbDailyUserTrade::getCreatedAt);
        return lbDailyUserTradeMapper.selectPage(new Page<>(current, size), queryWrapper);
    }

    @Override
    public Page<LbDailyUserTrade> pageDailyUserTradeByParentIdOfUserAndDateRange(
            LocalDate startDate, LocalDate endDate, String userId, Integer current, Integer size) {
        LambdaQueryWrapper<LbDepartmentUser> departmentUserQueryWrapper = new LambdaQueryWrapper<>();
        departmentUserQueryWrapper.eq(LbDepartmentUser::getUserId, userId.trim())
                .orderByDesc(LbDepartmentUser::getUpdateTime)
                .orderByDesc(LbDepartmentUser::getCreateTime)
                .last("limit 1");
        LbDepartmentUser departmentUser = lbDepartmentUserService.getOne(departmentUserQueryWrapper, false);
        if (departmentUser == null || departmentUser.getParentId() == null || departmentUser.getParentId().trim().isEmpty()) {
            throw new IllegalArgumentException("未查询到该用户对应的parentId");
        }

        String parentId = departmentUser.getParentId().trim();
        LambdaQueryWrapper<LbDailyUserTrade> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(LbDailyUserTrade::getUserId, parentId)
                .ge(LbDailyUserTrade::getReportDate, startDate)
                .le(LbDailyUserTrade::getReportDate, endDate)
                .orderByDesc(LbDailyUserTrade::getReportDate)
                .orderByDesc(LbDailyUserTrade::getCreatedAt);
        return lbDailyUserTradeMapper.selectPage(new Page<>(current, size), queryWrapper);
    }

    @Override
    public Map<String, Object> calculateReportSumByDateAndTenant(LocalDate reportDate, String tenantId) {
        Map<String, Object> result = new HashMap<>();
        LocalDateTime start = reportDate.atStartOfDay();
        LocalDateTime end = reportDate.plusDays(1).atStartOfDay();

        LambdaQueryWrapper<LbDailyUserTradeReport> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(LbDailyUserTradeReport::getTenantId, tenantId)
                .ge(LbDailyUserTradeReport::getReportDate, start)
                .lt(LbDailyUserTradeReport::getReportDate, end)
                .eq(LbDailyUserTradeReport::getDataType, DATA_TYPE_REPORT_DETAIL);

        BigDecimal yestodayBuyAmt = BigDecimal.ZERO;
        BigDecimal dailySellAmt = BigDecimal.ZERO;
        BigDecimal dailyBuyAmt = BigDecimal.ZERO;
        BigDecimal serviceAmt = BigDecimal.ZERO;
        BigDecimal diffAmt = BigDecimal.ZERO;
        BigDecimal dikouAmt = BigDecimal.ZERO;

        for (LbDailyUserTradeReport item : list(queryWrapper)) {
            yestodayBuyAmt = yestodayBuyAmt.add(defaultZero(item.getYestodayBuyAmt()));
            dailySellAmt = dailySellAmt.add(defaultZero(item.getDailySellAmt()));
            dailyBuyAmt = dailyBuyAmt.add(defaultZero(item.getDailyBuyAmt()));
            serviceAmt = serviceAmt.add(defaultZero(item.getServiceAmt()));
            diffAmt = diffAmt.add(defaultZero(item.getDiffAmt()));
            dikouAmt = dikouAmt.add(defaultZero(item.getDikouAmt()));
        }

        LbDailyUserTradeReport summary = new LbDailyUserTradeReport();
        summary.setId(UUID.randomUUID().toString());

        summary.setTenantId(tenantId);
        summary.setDataType(DATA_TYPE_REPORT_SUM);
        summary.setReportDate(start);
        summary.setYestodayBuyAmt(yestodayBuyAmt);
        summary.setDailySellAmt(dailySellAmt);
        summary.setDailyBuyAmt(dailyBuyAmt);
        summary.setServiceAmt(serviceAmt);
        summary.setDiffAmt(diffAmt);
        summary.setDikouAmt(dikouAmt);
        summary.setActualReceiptsPayments(diffAmt.subtract(serviceAmt));
        summary.setDescContent("系统汇总记录");
        summary.setCreatedAt(LocalDateTime.now());
        summary.setUpdatedAt(LocalDateTime.now());

        // 同日重复汇总时先清理历史汇总，避免 report_sum 产生多条导致读取口径不稳定。
        LambdaQueryWrapper<LbDailyUserTradeReport> sumDeleteWrapper = new LambdaQueryWrapper<>();
        sumDeleteWrapper.eq(LbDailyUserTradeReport::getTenantId, tenantId)
                .ge(LbDailyUserTradeReport::getReportDate, start)
                .lt(LbDailyUserTradeReport::getReportDate, end)
                .eq(LbDailyUserTradeReport::getDataType, DATA_TYPE_REPORT_SUM);
        remove(sumDeleteWrapper);

        boolean success = save(summary);
        Map<String, Object> checkInfo = buildCrossDayCheckInfo(reportDate, tenantId, start, end, yestodayBuyAmt);
        if (success && refreshSummaryFromDetail(summary, tenantId, start, end)) {
            checkInfo.put("summaryRefreshed", true);
        }

        result.put("success", success);
        if (success) {
            result.put("message", "汇总成功");
            result.put("data", summary);
            result.put("checkInfo", checkInfo);
            return result;
        }
        result.put("message", "汇总失败");
        return result;
    }

    private Map<String, Object> buildCrossDayCheckInfo(
            LocalDate reportDate,
            String tenantId,
            LocalDateTime todayStart,
            LocalDateTime todayEnd,
            BigDecimal todayYestodayBuyAmt) {
        Map<String, Object> checkInfo = new LinkedHashMap<>();
        LocalDate yesterdayDate = reportDate.minusDays(1);
        LocalDateTime yesterdayStart = yesterdayDate.atStartOfDay();
        LocalDateTime yesterdayEnd = reportDate.atStartOfDay();
        BigDecimal tolerance = CROSS_DAY_DIFF_TOLERANCE;
        log.info("cross-day check started, tenantId={}, reportDate={}, yesterdayRange=[{},{}), todayRange=[{},{})",
                tenantId, reportDate, yesterdayStart, yesterdayEnd, todayStart, todayEnd);

        LbDailyUserTradeReport yesterdaySum = queryYesterdaySummary(tenantId, yesterdayStart, yesterdayEnd);
        if (yesterdaySum == null) {
            log.warn("cross-day check skipped: yesterday summary not found, tenantId={}, reportDate={}", tenantId, reportDate);
            checkInfo.put("yesterdaySumFound", false);
            checkInfo.put("needDetailCheck", false);
            checkInfo.put("detailCheckPassed", false);
            return checkInfo;
        }

        BigDecimal yesterdaySumBuyTotal = defaultZero(yesterdaySum.getDailyBuyAmt());
        BigDecimal sumDiff = yesterdaySumBuyTotal.subtract(defaultZero(todayYestodayBuyAmt)).abs();
        checkInfo.put("yesterdaySumFound", true);
        checkInfo.put("yesterdaySumBuyTotal", yesterdaySumBuyTotal);
        checkInfo.put("todayYestodayBuyAmt", todayYestodayBuyAmt);
        checkInfo.put("sumDiff", sumDiff);
        checkInfo.put("threshold", tolerance);
        log.info("cross-day summary compared, tenantId={}, reportDate={}, yesterdaySumBuyTotal={}, todayYestodayBuyAmt={}, diff={}, threshold={}",
                tenantId, reportDate, yesterdaySumBuyTotal, todayYestodayBuyAmt, sumDiff, tolerance);

        if (sumDiff.compareTo(tolerance) <= 0) {
            log.info("cross-day summary within threshold, skip detail check, tenantId={}, reportDate={}", tenantId, reportDate);
            checkInfo.put("needDetailCheck", false);
            checkInfo.put("detailCheckPassed", true);
            return checkInfo;
        }

        log.warn("cross-day summary exceeded threshold, start detail repair/check, tenantId={}, reportDate={}, diff={}",
                tenantId, reportDate, sumDiff);
        List<LbDailyUserTradeReport> yesterdayDetailList = queryDetailRows(tenantId, yesterdayStart, yesterdayEnd);
        List<LbDailyUserTradeReport> todayDetailList = queryDetailRows(tenantId, todayStart, todayEnd);
        log.info("detail rows loaded, tenantId={}, yesterdayCount={}, todayCount={}",
                tenantId, yesterdayDetailList.size(), todayDetailList.size());

        Map<String, BigDecimal> yesterdayAmtByUserId = buildAmountByUserId(yesterdayDetailList, true);
        DetailRepairStat repairStat = repairTodayDetailByYesterday(yesterdayDetailList, todayDetailList, tenantId, todayStart);
        checkInfo.put("autoRepaired", repairStat.repaired);
        checkInfo.put("updatedTodayRows", repairStat.updatedCount);
        checkInfo.put("insertedTodayRows", repairStat.insertedCount);
        log.info("detail repair finished, tenantId={}, repaired={}, updatedRows={}, insertedRows={}",
                tenantId, repairStat.repaired, repairStat.updatedCount, repairStat.insertedCount);

        // 修正后重新读取今天明细并复核结果。
        List<LbDailyUserTradeReport> repairedTodayDetailList = queryDetailRows(tenantId, todayStart, todayEnd);
        Map<String, BigDecimal> repairedTodayAmtByUserId = buildAmountByUserId(repairedTodayDetailList, false);
        fillDetailCheckResult(checkInfo, yesterdayAmtByUserId, repairedTodayAmtByUserId, yesterdayDetailList.size(), repairedTodayDetailList.size());

        @SuppressWarnings("unchecked")
        List<Map<String, Object>> mismatchUsers = (List<Map<String, Object>>) checkInfo.get("mismatchUsers");
        @SuppressWarnings("unchecked")
        List<String> missingInTodayUsers = (List<String>) checkInfo.get("missingInTodayUsers");
        @SuppressWarnings("unchecked")
        List<String> extraInTodayUsers = (List<String>) checkInfo.get("extraInTodayUsers");

        if (!mismatchUsers.isEmpty() || !missingInTodayUsers.isEmpty() || !extraInTodayUsers.isEmpty()) {
            log.warn("trade report detail check failed, tenantId={}, reportDate={}, sumDiff={}, mismatchCount={}, missingCount={}, extraCount={}",
                    tenantId, reportDate, sumDiff, mismatchUsers.size(), missingInTodayUsers.size(), extraInTodayUsers.size());
        } else {
            log.info("trade report detail check passed after repair, tenantId={}, reportDate={}, sumDiff={}",
                    tenantId, reportDate, sumDiff);
        }
        return checkInfo;
    }

    private LbDailyUserTradeReport queryYesterdaySummary(String tenantId, LocalDateTime yesterdayStart, LocalDateTime yesterdayEnd) {
        LambdaQueryWrapper<LbDailyUserTradeReport> query = new LambdaQueryWrapper<>();
        query.eq(LbDailyUserTradeReport::getTenantId, tenantId)
                .ge(LbDailyUserTradeReport::getReportDate, yesterdayStart)
                .lt(LbDailyUserTradeReport::getReportDate, yesterdayEnd)
                .eq(LbDailyUserTradeReport::getDataType, DATA_TYPE_REPORT_SUM)
                .orderByDesc(LbDailyUserTradeReport::getUpdatedAt)
                .orderByDesc(LbDailyUserTradeReport::getCreatedAt)
                .last("limit 1");
        List<LbDailyUserTradeReport> rows = list(query);
        return rows.isEmpty() ? null : rows.get(0);
    }

    private boolean refreshSummaryFromDetail(
            LbDailyUserTradeReport summary,
            String tenantId,
            LocalDateTime start,
            LocalDateTime end) {
        TradeSummaryAmount latest = aggregateDetailAmounts(tenantId, start, end);
        boolean changed = isSummaryChanged(summary, latest);
        if (!changed) {
            return false;
        }
        summary.setYestodayBuyAmt(latest.yestodayBuyAmt);
        summary.setDailySellAmt(latest.dailySellAmt);
        summary.setDailyBuyAmt(latest.dailyBuyAmt);
        summary.setServiceAmt(latest.serviceAmt);
        summary.setDiffAmt(latest.diffAmt);
        summary.setDikouAmt(latest.dikouAmt);
        summary.setActualReceiptsPayments(latest.diffAmt.subtract(latest.serviceAmt));
        summary.setUpdatedAt(LocalDateTime.now());
        updateById(summary);
        return true;
    }

    private TradeSummaryAmount aggregateDetailAmounts(String tenantId, LocalDateTime start, LocalDateTime end) {
        LambdaQueryWrapper<LbDailyUserTradeReport> detailQuery = new LambdaQueryWrapper<>();
        detailQuery.eq(LbDailyUserTradeReport::getTenantId, tenantId)
                .ge(LbDailyUserTradeReport::getReportDate, start)
                .lt(LbDailyUserTradeReport::getReportDate, end)
                .eq(LbDailyUserTradeReport::getDataType, DATA_TYPE_REPORT_DETAIL);

        TradeSummaryAmount amount = new TradeSummaryAmount();
        for (LbDailyUserTradeReport row : list(detailQuery)) {
            amount.yestodayBuyAmt = amount.yestodayBuyAmt.add(defaultZero(row.getYestodayBuyAmt()));
            amount.dailySellAmt = amount.dailySellAmt.add(defaultZero(row.getDailySellAmt()));
            amount.dailyBuyAmt = amount.dailyBuyAmt.add(defaultZero(row.getDailyBuyAmt()));
            amount.serviceAmt = amount.serviceAmt.add(defaultZero(row.getServiceAmt()));
            amount.diffAmt = amount.diffAmt.add(defaultZero(row.getDiffAmt()));
            amount.dikouAmt = amount.dikouAmt.add(defaultZero(row.getDikouAmt()));
        }
        return amount;
    }

    private boolean isSummaryChanged(LbDailyUserTradeReport summary, TradeSummaryAmount latest) {
        return defaultZero(summary.getYestodayBuyAmt()).compareTo(latest.yestodayBuyAmt) != 0
                || defaultZero(summary.getDailySellAmt()).compareTo(latest.dailySellAmt) != 0
                || defaultZero(summary.getDailyBuyAmt()).compareTo(latest.dailyBuyAmt) != 0
                || defaultZero(summary.getServiceAmt()).compareTo(latest.serviceAmt) != 0
                || defaultZero(summary.getDiffAmt()).compareTo(latest.diffAmt) != 0
                || defaultZero(summary.getDikouAmt()).compareTo(latest.dikouAmt) != 0;
    }

    private List<LbDailyUserTradeReport> queryDetailRows(String tenantId, LocalDateTime start, LocalDateTime end) {
        LambdaQueryWrapper<LbDailyUserTradeReport> query = new LambdaQueryWrapper<>();
        query.eq(LbDailyUserTradeReport::getTenantId, tenantId)
                .ge(LbDailyUserTradeReport::getReportDate, start)
                .lt(LbDailyUserTradeReport::getReportDate, end)
                .eq(LbDailyUserTradeReport::getDataType, DATA_TYPE_REPORT_DETAIL);
        return list(query);
    }

    private Map<String, BigDecimal> buildAmountByUserId(List<LbDailyUserTradeReport> detailRows, boolean useDailyBuyAmt) {
        Map<String, BigDecimal> amountByUserId = new HashMap<>();
        for (LbDailyUserTradeReport row : detailRows) {
            if (row.getUserId() == null || row.getUserId().trim().isEmpty()) {
                continue;
            }
            String userId = row.getUserId().trim();
            BigDecimal amount = useDailyBuyAmt ? defaultZero(row.getDailyBuyAmt()) : defaultZero(row.getYestodayBuyAmt());
            amountByUserId.merge(userId, amount, BigDecimal::add);
        }
        return amountByUserId;
    }

    private DetailRepairStat repairTodayDetailByYesterday(
            List<LbDailyUserTradeReport> yesterdayDetailList,
            List<LbDailyUserTradeReport> todayDetailList,
            String tenantId,
            LocalDateTime todayStart) {
        // Step1: 先把“昨天每个用户应有金额”聚合好，作为今天修正目标。
        Map<String, BigDecimal> yesterdayAmtByUserId = new HashMap<>();
        Map<String, LbDailyUserTradeReport> yesterdaySampleByUserId = new HashMap<>();
        for (LbDailyUserTradeReport row : yesterdayDetailList) {
            if (row.getUserId() == null || row.getUserId().trim().isEmpty()) {
                continue;
            }
            String userId = row.getUserId().trim();
            yesterdayAmtByUserId.merge(userId, defaultZero(row.getDailyBuyAmt()), BigDecimal::add);
            yesterdaySampleByUserId.putIfAbsent(userId, row);
        }

        // Step2: 对今天明细建立 userId -> rows 映射，便于 O(1) 找到待修正记录。
        Map<String, List<LbDailyUserTradeReport>> todayRowsByUserId = new HashMap<>();
        for (LbDailyUserTradeReport row : todayDetailList) {
            if (row.getUserId() == null || row.getUserId().trim().isEmpty()) {
                continue;
            }
            String userId = row.getUserId().trim();
            todayRowsByUserId.computeIfAbsent(userId, k -> new ArrayList<>()).add(row);
        }

        List<LbDailyUserTradeReport> rowsToUpdate = new ArrayList<>();
        List<LbDailyUserTradeReport> rowsToInsert = new ArrayList<>();
        LocalDateTime now = LocalDateTime.now();
        int duplicateRowZeroedCount = 0;

        // Step3: 以“昨天聚合金额”为唯一标准回写今天数据。
        for (Map.Entry<String, BigDecimal> entry : yesterdayAmtByUserId.entrySet()) {
            String userId = entry.getKey();
            BigDecimal expectedAmt = defaultZero(entry.getValue());
            List<LbDailyUserTradeReport> todayRows = todayRowsByUserId.get(userId);

            if (todayRows == null || todayRows.isEmpty()) {
                LbDailyUserTradeReport source = yesterdaySampleByUserId.get(userId);
                LbDailyUserTradeReport inserted = new LbDailyUserTradeReport();
                inserted.setId(UUID.randomUUID().toString());
                inserted.setTenantId(tenantId);
                inserted.setUserId(userId);
                inserted.setNickname(source == null ? null : source.getNickname());
                inserted.setDataType(DATA_TYPE_REPORT_DETAIL);
                inserted.setReportDate(todayStart);
                inserted.setYestodayBuyAmt(expectedAmt);
                inserted.setDailySellAmt(BigDecimal.ZERO);
                inserted.setDailyBuyAmt(BigDecimal.ZERO);
                inserted.setServiceAmt(BigDecimal.ZERO);
                inserted.setDiffAmt(BigDecimal.ZERO);
                inserted.setDikouAmt(BigDecimal.ZERO);
                inserted.setActualReceiptsPayments(BigDecimal.ZERO);
                inserted.setDescContent("系统按昨日数据自动修正");
                inserted.setCreatedAt(now);
                inserted.setUpdatedAt(now);
                rowsToInsert.add(inserted);
                log.info("detail repair insert row, tenantId={}, userId={}, expectedAmt={}", tenantId, userId, expectedAmt);
                continue;
            }

            LbDailyUserTradeReport first = todayRows.get(0);
            if (defaultZero(first.getYestodayBuyAmt()).compareTo(expectedAmt) != 0) {
                first.setYestodayBuyAmt(expectedAmt);
                first.setUpdatedAt(now);
                rowsToUpdate.add(first);
                log.info("detail repair update row, tenantId={}, userId={}, rowId={}, expectedAmt={}",
                        tenantId, userId, first.getId(), expectedAmt);
            }
            for (int i = 1; i < todayRows.size(); i++) {
                LbDailyUserTradeReport extra = todayRows.get(i);
                if (defaultZero(extra.getYestodayBuyAmt()).compareTo(BigDecimal.ZERO) != 0) {
                    extra.setYestodayBuyAmt(BigDecimal.ZERO);
                    extra.setUpdatedAt(now);
                    rowsToUpdate.add(extra);
                    duplicateRowZeroedCount++;
                }
            }
        }

        boolean repaired = false;
        if (!rowsToUpdate.isEmpty()) {
            saveOrUpdateBatch(rowsToUpdate);
            repaired = true;
        }
        if (!rowsToInsert.isEmpty()) {
            saveBatch(rowsToInsert);
            repaired = true;
        }
        log.info("detail repair summary, tenantId={}, targetUserCount={}, updatedRows={}, insertedRows={}, duplicateRowsZeroed={}",
                tenantId, yesterdayAmtByUserId.size(), rowsToUpdate.size(), rowsToInsert.size(), duplicateRowZeroedCount);
        return new DetailRepairStat(repaired, rowsToUpdate.size(), rowsToInsert.size());
    }

    private void fillDetailCheckResult(
            Map<String, Object> checkInfo,
            Map<String, BigDecimal> yesterdayAmtByUserId,
            Map<String, BigDecimal> todayAmtByUserId,
            int yesterdayDetailCount,
            int todayDetailCount) {
        List<Map<String, Object>> mismatchUsers = new ArrayList<>();
        List<String> missingInTodayUsers = new ArrayList<>();
        List<String> extraInTodayUsers = new ArrayList<>();
        Set<String> allUserIds = new HashSet<>(yesterdayAmtByUserId.keySet());
        allUserIds.addAll(todayAmtByUserId.keySet());

        for (String userId : allUserIds) {
            BigDecimal yesterdayUserAmt = yesterdayAmtByUserId.get(userId);
            BigDecimal todayUserAmt = todayAmtByUserId.get(userId);

            if (yesterdayUserAmt == null && todayUserAmt != null) {
                extraInTodayUsers.add(userId);
                continue;
            }
            if (yesterdayUserAmt != null && todayUserAmt == null) {
                missingInTodayUsers.add(userId);
                continue;
            }
            BigDecimal userDiff = defaultZero(yesterdayUserAmt).subtract(defaultZero(todayUserAmt)).abs();
            if (userDiff.compareTo(BigDecimal.ZERO) > 0) {
                Map<String, Object> mismatch = new LinkedHashMap<>();
                mismatch.put("userId", userId);
                mismatch.put("yesterdayAmt", yesterdayUserAmt);
                mismatch.put("todayAmt", todayUserAmt);
                mismatch.put("diff", userDiff);
                mismatchUsers.add(mismatch);
            }
        }

        checkInfo.put("needDetailCheck", true);
        checkInfo.put("yesterdayDetailCount", yesterdayDetailCount);
        checkInfo.put("todayDetailCount", todayDetailCount);
        checkInfo.put("mismatchUsers", mismatchUsers);
        checkInfo.put("missingInTodayUsers", missingInTodayUsers);
        checkInfo.put("extraInTodayUsers", extraInTodayUsers);
        checkInfo.put("detailCheckPassed", mismatchUsers.isEmpty() && missingInTodayUsers.isEmpty() && extraInTodayUsers.isEmpty());
    }

    private static final class DetailRepairStat {
        final boolean repaired;
        final int updatedCount;
        final int insertedCount;

        DetailRepairStat(boolean repaired, int updatedCount, int insertedCount) {
            this.repaired = repaired;
            this.updatedCount = updatedCount;
            this.insertedCount = insertedCount;
        }
    }

    private static final class TradeSummaryAmount {
        BigDecimal yestodayBuyAmt = BigDecimal.ZERO;
        BigDecimal dailySellAmt = BigDecimal.ZERO;
        BigDecimal dailyBuyAmt = BigDecimal.ZERO;
        BigDecimal serviceAmt = BigDecimal.ZERO;
        BigDecimal diffAmt = BigDecimal.ZERO;
        BigDecimal dikouAmt = BigDecimal.ZERO;
    }

    private static BigDecimal defaultZero(BigDecimal value) {
        return value == null ? BigDecimal.ZERO : value;
    }

    @Override
    /**
     * 按日期范围统计“本人 + 递归团队”的交易金额。
     *
     * 设计说明：
     * 1) 先用交易报表圈定“本次需要返回”的用户集合（scopeUserIds），并聚合每个用户 own 金额；
     * 2) 再用部门关系构建 parentUserId -> childrenUserIds 的树；
     * 3) 对 scope 中每个用户递归汇总 team 金额（不含本人）；
     * 4) 返回 own/team/total 三套字段，便于前端直接展示。
     *
     * 注意：
     * - 交易报表必须带 tenant 过滤，否则会混入其他租户用户导致层级结果异常；
     * - lb_department_user.parent_id 在历史数据中可能是“父user_id”或“父记录id(UUID)”，此处做兼容解析。
     */
    public Map<String, Object> listUserTradeAmountWithTeamByDateRangeAndTenant(
            LocalDate startDate, LocalDate endDate, String tenantId) {
        Map<String, Object> result = new HashMap<>();
        if (tenantId == null || tenantId.trim().isEmpty()) {
            result.put("success", false);
            result.put("message", "tenantId不能为空");
            return result;
        }
        if (startDate == null || endDate == null) {
            result.put("success", false);
            result.put("message", "开始日期和结束日期不能为空");
            return result;
        }
        if (endDate.isBefore(startDate)) {
            result.put("success", false);
            result.put("message", "结束日期不能早于开始日期");
            return result;
        }

        String tenantIdTrim = tenantId.trim();
        LocalDateTime rangeStart = startDate.atStartOfDay();
        LocalDateTime rangeEnd = endDate.atTime(23, 59, 59);

        // 1) 先查 lb_daily_user_trade_report：
        //    - 仅取当前租户 + 指定日期范围 + report_detail；
        //    - 用于圈定返回范围（scopeUserIds）；
        //    - 同时把本人四类金额先聚合好（ownAmtByUserId）。
        LambdaQueryWrapper<LbDailyUserTradeReport> tradeQw = new LambdaQueryWrapper<>();
        tradeQw.ge(LbDailyUserTradeReport::getReportDate, rangeStart)
                .le(LbDailyUserTradeReport::getReportDate, rangeEnd)
                .eq(LbDailyUserTradeReport::getTenantId, tenantIdTrim)
                .eq(LbDailyUserTradeReport::getDataType, DATA_TYPE_REPORT_DETAIL );

        // userId -> 姓名，优先用报表昵称，后续再用部门姓名补齐。
        Map<String, String> userIdToName = new HashMap<>();
        // userId -> 本人金额汇总（own）。
        Map<String, TradeAmtBundle> ownAmtByUserId = new HashMap<>();
        List<LbDailyUserTradeReport> list1 = list(tradeQw);

        for (LbDailyUserTradeReport r : list1) {
            if (r.getUserId() == null || r.getUserId().trim().isEmpty()) {
                continue;
            }
            String uid = r.getUserId().trim();
            ownAmtByUserId
                    .computeIfAbsent(uid, k -> TradeAmtBundle.zeros())
                    .addFromReportRow(r);
            if (r.getNickname() != null && !r.getNickname().trim().isEmpty()) {
                userIdToName.putIfAbsent(uid, r.getNickname().trim());
            }
        }

        if (ownAmtByUserId.isEmpty()) {
            result.put("success", true);
            result.put("message", "查询成功");
            result.put("data", List.of());
            result.put("total", 0);
            result.put("startDate", startDate);
            result.put("endDate", endDate);
            result.put("tenantId", tenantIdTrim);
            return result;
        }

        // 最终只返回这些用户（报表范围内“有交易数据”的用户）。
        Set<String> scopeUserIds = new HashSet<>(ownAmtByUserId.keySet());

        // 2) 再查 lb_department_user，构建上下级关系：
        //    目标结构：childrenByParentUserId[parentUserId] = [childUserId...]
        //    兼容 parent_id 两种格式：
        //    - 直接存父 user_id；
        //    - 存父记录 id(UUID)，需先反查成父 user_id。
        Map<String, List<String>> childrenByParentUserId = new HashMap<>();
        // childUserId -> parentUserId，用于后续把平铺数据组装成树。
        Map<String, String> parentByUserId = new HashMap<>();
        List<LbDepartmentUser> deptUsers = lbDepartmentUserService.list(
                new LambdaQueryWrapper<LbDepartmentUser>()
                        .eq(LbDepartmentUser::getTenantId, tenantIdTrim));
        if (deptUsers != null) {
            // 预处理索引：
            // - userIdByRecordId: 记录id(UUID) -> userId
            // - allUserIds: 全部 userId（用于判断 parent_id 是否已是 userId）
            Map<String, String> userIdByRecordId = new HashMap<>();
            Set<String> allUserIds = new HashSet<>();
            for (LbDepartmentUser row : deptUsers) {
                String rowId = row.getId() == null ? null : row.getId().trim();
                String uid = row.getUserId() == null ? null : row.getUserId().trim();
                if (uid == null || uid.isEmpty()) {
                    continue;
                }
                allUserIds.add(uid);
                if (rowId != null && !rowId.isEmpty()) {
                    userIdByRecordId.put(rowId, uid);
                }
            }
            for (LbDepartmentUser row : deptUsers) {
                if (row.getUserId() == null || row.getUserId().trim().isEmpty()) {
                    continue;
                }
                String uid = row.getUserId().trim();
                if (row.getName() != null && !row.getName().trim().isEmpty()) {
                    userIdToName.putIfAbsent(uid, row.getName().trim());
                }
                // parentRef 可能是“父userId”或“父记录id”，不要直接假设其一定是 userId。
                String parentRef = row.getParentId() == null ? null : row.getParentId().trim();
                if (parentRef != null && !parentRef.isEmpty()) {
                    String parentUserId = null;
                    String resolvedBy = null;
                    // case A：parent_id 本身就是父 userId
                    if (allUserIds.contains(parentRef)) {
                        parentUserId = parentRef;
                        resolvedBy = "parent_user_id";
                    // case B：parent_id 是父记录id(UUID)，反查映射到父 userId
                    } else if (userIdByRecordId.containsKey(parentRef)) {
                        parentUserId = userIdByRecordId.get(parentRef);
                        resolvedBy = "parent_record_id";
                    }
                    // 两种都解析失败：说明关系数据不完整/不一致，记录日志便于排查。
                    if (parentUserId == null || parentUserId.isEmpty()) {
                        log.warn("dept relation parent unresolved, tenantId={}, userId={}, name={}, parentId={}",
                                tenantIdTrim, uid, row.getName(), parentRef);
                        continue;
                    }
                    List<String> ch = childrenByParentUserId.computeIfAbsent(parentUserId, k -> new ArrayList<>());
                    if (!ch.contains(uid)) {
                        ch.add(uid);
                    }
                    // 若同一个子节点出现多次，保留第一次解析到的父节点，避免树结构抖动。
                    parentByUserId.putIfAbsent(uid, parentUserId);
                    // 针对已知问题用户打定位日志，便于验证 parent_id 解析是否正确。
                    if ("98155".equals(uid) || "杨渺渺".equals(row.getName())) {
                        log.info("dept relation resolved for focus user, tenantId={}, userId={}, name={}, parentId={}, parentUserId={}, resolvedBy={}",
                                tenantIdTrim, uid, row.getName(), parentRef, parentUserId, resolvedBy);
                    }
                }
            }
            for (List<String> list : childrenByParentUserId.values()) {
                list.sort(String::compareTo);
            }
        }

        // 3) 对 scope 用户计算递归 team 金额：
        //    - team = 所有下级 own + 下级的 team（不含本人）；
        //    - 使用 memo 缓存避免重复递归。
        Map<String, TradeAmtBundle> teamAmtMemo = new HashMap<>();
        for (String userId : scopeUserIds) {
            teamAmtRecursive(userId, childrenByParentUserId, ownAmtByUserId, teamAmtMemo, new HashSet<>());
        }

        // 4) 先组装平铺结果，保持 own/team/total 三套字段并按 totalDiffAmt 倒序。
        List<Map<String, Object>> rows = new ArrayList<>();
        for (String userId : scopeUserIds) {
            TradeAmtBundle own = ownAmt(userId, ownAmtByUserId);
            TradeAmtBundle team = teamAmt(userId, teamAmtMemo);
            TradeAmtBundle total = own.addCopy(team);
            Map<String, Object> row = new LinkedHashMap<>();
            row.put("userId", userId);
            row.put("name", userIdToName.getOrDefault(userId, ""));
            row.put("ownYestodayBuyAmt", own.yestodayBuyAmt);
            row.put("teamYestodayBuyAmt", team.yestodayBuyAmt);
            row.put("totalYestodayBuyAmt", total.yestodayBuyAmt);
            row.put("ownDailySellAmt", own.dailySellAmt);
            row.put("teamDailySellAmt", team.dailySellAmt);
            row.put("totalDailySellAmt", total.dailySellAmt);
            row.put("ownDailyBuyAmt", own.dailyBuyAmt);
            row.put("teamDailyBuyAmt", team.dailyBuyAmt);
            row.put("totalDailyBuyAmt", total.dailyBuyAmt);
            row.put("ownDiffAmt", own.diffAmt);
            row.put("teamDiffAmt", team.diffAmt);
            row.put("totalDiffAmt", total.diffAmt);
            // children 用于前端树形展示。
            row.put("children", new ArrayList<Map<String, Object>>());
            rows.add(row);
        }
        rows.sort(Comparator.comparing((Map<String, Object> m) -> (BigDecimal) m.get("totalDiffAmt")).reversed());

        // 5) 将平铺数据转为树形结构：
        //    - 仅挂载 scope（报表内有交易数据）节点，避免返回无交易的空节点；
        //    - 若父节点不在 scope，则当前节点作为根节点返回。
        Map<String, Map<String, Object>> rowByUserId = new HashMap<>();
        for (Map<String, Object> row : rows) {
            rowByUserId.put((String) row.get("userId"), row);
        }
        List<Map<String, Object>> treeRows = new ArrayList<>();
        for (Map<String, Object> row : rows) {
            String userId = (String) row.get("userId");
            String parentUserId = parentByUserId.get(userId);
            Map<String, Object> parentRow = parentUserId == null ? null : rowByUserId.get(parentUserId);
            if (parentRow == null) {
                treeRows.add(row);
                continue;
            }
            @SuppressWarnings("unchecked")
            List<Map<String, Object>> children = (List<Map<String, Object>>) parentRow.get("children");
            children.add(row);
        }
        sortTreeByTotalDiffDesc(treeRows);

        result.put("success", true);
        result.put("message", "查询成功");
        // data 改为树形，flatData 保留平铺，便于前端平滑改造。
        result.put("data", treeRows);
        result.put("flatData", rows);
        result.put("total", rows.size());
        result.put("startDate", startDate);
        result.put("endDate", endDate);
        result.put("tenantId", tenantIdTrim);
        return result;
    }

    private static TradeAmtBundle ownAmt(String userId, Map<String, TradeAmtBundle> ownAmtByUserId) {
        TradeAmtBundle b = ownAmtByUserId.get(userId);
        return b != null ? b : TradeAmtBundle.zeros();
    }

    private static TradeAmtBundle teamAmt(String userId, Map<String, TradeAmtBundle> teamAmtMemo) {
        TradeAmtBundle b = teamAmtMemo.get(userId);
        return b != null ? b : TradeAmtBundle.zeros();
    }

    /**
     * 递归按 totalDiffAmt 倒序排序树节点，确保每层展示稳定。
     */
    private static void sortTreeByTotalDiffDesc(List<Map<String, Object>> nodes) {
        nodes.sort(Comparator.comparing((Map<String, Object> m) -> (BigDecimal) m.get("totalDiffAmt")).reversed());
        for (Map<String, Object> node : nodes) {
            @SuppressWarnings("unchecked")
            List<Map<String, Object>> children = (List<Map<String, Object>>) node.get("children");
            if (children != null && !children.isEmpty()) {
                sortTreeByTotalDiffDesc(children);
            }
        }
    }

    /**
     * 递归计算某个用户的 team 金额（不含本人）。
     *
     * 规则：
     * - sum = Σ(每个child的own + child的team)；
     * - memo 命中即直接返回，避免重复计算；
     * - stack 用于防环（脏数据导致 A->B->A 时返回 0，避免死循环）。
     */
    private TradeAmtBundle teamAmtRecursive(
            String parentUserId,
            Map<String, List<String>> childrenByParentUserId,
            Map<String, TradeAmtBundle> ownAmtByUserId,
            Map<String, TradeAmtBundle> memo,
            Set<String> stack) {
        // 动态规划缓存：同一节点只算一次。
        TradeAmtBundle cached = memo.get(parentUserId);
        if (cached != null) {
            return cached;
        }
        // 防止环形关系导致无限递归。
        if (!stack.add(parentUserId)) {
            return TradeAmtBundle.zeros();
        }
        try {
            TradeAmtBundle sum = TradeAmtBundle.zeros();
            List<String> children = childrenByParentUserId.get(parentUserId);
            if (children != null) {
                for (String childUserId : children) {
                    sum.addInPlace(ownAmt(childUserId, ownAmtByUserId));
                    sum.addInPlace(teamAmtRecursive(childUserId, childrenByParentUserId, ownAmtByUserId, memo, stack));
                }
            }
            memo.put(parentUserId, sum);
            return sum;
        } finally {
            stack.remove(parentUserId);
        }
    }

    /**
     * 日期范围内按用户汇总的四类金额（与报表字段对应）。
     * 该对象既可表示 own，也可表示 team 或 total。
     */
    private static final class TradeAmtBundle {
        BigDecimal yestodayBuyAmt = BigDecimal.ZERO;
        BigDecimal dailySellAmt = BigDecimal.ZERO;
        BigDecimal dailyBuyAmt = BigDecimal.ZERO;
        BigDecimal diffAmt = BigDecimal.ZERO;

        static TradeAmtBundle zeros() {
            return new TradeAmtBundle();
        }

        void addFromReportRow(LbDailyUserTradeReport r) {
            yestodayBuyAmt = yestodayBuyAmt.add(defaultZero(r.getYestodayBuyAmt()));
            dailySellAmt = dailySellAmt.add(defaultZero(r.getDailySellAmt()));
            dailyBuyAmt = dailyBuyAmt.add(defaultZero(r.getDailyBuyAmt()));
            diffAmt = diffAmt.add(defaultZero(r.getDiffAmt()));
        }

        void addInPlace(TradeAmtBundle o) {
            if (o == null) {
                return;
            }
            yestodayBuyAmt = yestodayBuyAmt.add(defaultZero(o.yestodayBuyAmt));
            dailySellAmt = dailySellAmt.add(defaultZero(o.dailySellAmt));
            dailyBuyAmt = dailyBuyAmt.add(defaultZero(o.dailyBuyAmt));
            diffAmt = diffAmt.add(defaultZero(o.diffAmt));
        }

        TradeAmtBundle addCopy(TradeAmtBundle o) {
            TradeAmtBundle n = zeros();
            n.addInPlace(this);
            n.addInPlace(o != null ? o : zeros());
            return n;
        }
    }
}
