package com.yc.ship.module.trade.service.report.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.converters.longconverter.LongStringConverter;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import com.yc.ship.framework.common.pojo.PageResult;
import com.yc.ship.framework.excel.core.util.ExcelUtils;
import com.yc.ship.module.resource.api.ship.dto.RoomModelFloorNumDTO;
import com.yc.ship.module.resource.dal.mysql.room.ResourceRoomMapper;
import com.yc.ship.module.trade.controller.admin.report.vo.*;
import com.yc.ship.module.trade.dal.dataobject.order.TradeVisitorDO;
import com.yc.ship.module.trade.dal.mysql.order.TradeVisitorMapper;
import com.yc.ship.module.trade.dal.mysql.report.VoyageStockBoardMapper;
import com.yc.ship.module.trade.service.report.VoyageStockBoardService;
import com.yc.ship.module.trade.utils.IdCardProvinceUtil;
import com.yc.ship.module.trade.utils.excel.VoyageStockBoardExportStyleHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 航次库存看板 Service 实现类
 */
@Service
@Slf4j
public class VoyageStockBoardServiceImpl implements VoyageStockBoardService {

    @Resource
    private VoyageStockBoardMapper voyageStockBoardMapper;

    @Resource
    private ResourceRoomMapper resourceRoomMapper;


    @Override
    public PageResult<VoyageStockBoardRespVO> getVoyageStockBoardPage(VoyageStockBoardReqVO pageReqVO) {
        Long voyageId = pageReqVO.getVoyageId();

        // 1. 查询航次汇总
        VoyageStockBoardRespVO respVO = voyageStockBoardMapper.selectVoyageStockSummary(voyageId);
        if (respVO == null) {
            respVO = new VoyageStockBoardRespVO();
        }

        // 2. 查询明细
        List<VoyageStockBoardItemVO> detailList = voyageStockBoardMapper.selectVoyageStockBoardDetail(voyageId);
        if (CollUtil.isEmpty(detailList)) {
            respVO.setDetailList(new ArrayList<>());
            return new PageResult<>(Collections.singletonList(respVO), 1L);
        }

        // 3. 查询同比环比
        fillYoyAndMom(detailList, voyageId);

        // 4. 计算派生字段
        calculateDerivedFields(detailList);

        // 5. 按房型拼接名称
        formatRoomModelName(detailList);

        respVO.setDetailList(detailList);
        return new PageResult<>(Collections.singletonList(respVO), 1L);
    }

    @Override
    public void exportVoyageStockBoardExcel(VoyageStockBoardReqVO pageReqVO, HttpServletResponse response) throws IOException {
        Long voyageId = pageReqVO.getVoyageId();

        // 1. 查询数据
        VoyageStockBoardRespVO respVO = voyageStockBoardMapper.selectVoyageStockSummary(voyageId);
        if (respVO == null) {
            respVO = new VoyageStockBoardRespVO();
        }

        List<VoyageStockBoardItemVO> detailList = voyageStockBoardMapper.selectVoyageStockBoardDetail(voyageId);
        if (CollUtil.isEmpty(detailList)) {
            ExcelUtils.exportEmpty(response, "航次库存看板");
            return;
        }

        fillYoyAndMom(detailList, voyageId);
        calculateDerivedFields(detailList);
        formatRoomModelName(detailList);
        respVO.setDetailList(detailList);

        // 2. 构建多级表头
        List<List<String>> head = buildExportHeaders();

        // 3. 转换数据
        List<List<Object>> exportData = transformExportData(respVO);

        response.addHeader("Content-Disposition",
                "attachment;filename=" + URLEncoder.encode("航次库存看板.xlsx", StandardCharsets.UTF_8.name()));
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8");
        // 4. 写出（表头从第5行开始，前4行是汇总数据）
        VoyageStockBoardExportStyleHandler styleHandler = new VoyageStockBoardExportStyleHandler(respVO);
        EasyExcel.write(response.getOutputStream())
                .head(head)
                .relativeHeadRowIndex(4)
                .autoCloseStream(false)
                .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
                .registerWriteHandler(styleHandler)
                .registerConverter(new LongStringConverter())
                .sheet("航次库存看板")
                .doWrite(exportData);


    }

    @Override
    public List<AllVoyageStockBoardTableHeadRespVO> getAllVoyageStockBoardTableHead(AllVoyageStockBoardReqVO reqVO) {
        List<RoomModelFloorNumDTO> roomModelFloorNumList = resourceRoomMapper.selectRoomModelFloorToNum(reqVO.getShipId());
        if (CollUtil.isEmpty(roomModelFloorNumList)) {
            return Collections.emptyList();
        }
        List<AllVoyageStockBoardTableHeadRespVO> result = new ArrayList<>();


        // 记录已处理的房型ID
        Set<Long> processedRoomModelIds = new HashSet<>();

        // 按数据库返回顺序处理
        for (RoomModelFloorNumDTO item : roomModelFloorNumList) {
            Long roomModelId = item.getRoomModelId();

            // 如果已经处理过，跳过
            if (processedRoomModelIds.contains(roomModelId)) {
                continue;
            }
            String roomModelName = item.getRoomModelName();
            // 判断是否是家庭套房或家庭套房PRO
            if (roomModelName != null && roomModelName.contains("家庭套房")) {
                // 如果是家庭套房，合并处理
                AllVoyageStockBoardTableHeadRespVO familyVO = new AllVoyageStockBoardTableHeadRespVO();
                familyVO.setKey("family");
                familyVO.setLabel("家庭套房");

                // 筛选出所有家庭套房相关的楼层数据（包括家庭套房和家庭套房PRO等）
                List<RoomModelFloorNumDTO> familyFloors = roomModelFloorNumList.stream()
                        .filter(r -> r.getRoomModelName() != null && r.getRoomModelName().contains("家庭套房"))
                        .collect(Collectors.toList());

                // 计算总房间数
                int sumRooms = familyFloors.stream()
                        .mapToInt(r -> r.getNum() != null ? r.getNum() : 0)
                        .sum();
                familyVO.setSumRooms(sumRooms);

                // 构建楼层列表
                List<AllVoyageStockBoardTableHeadRespVO.FloorInfo> floorInfoList = new ArrayList<>();
                for (RoomModelFloorNumDTO floorItem : familyFloors) {
                    AllVoyageStockBoardTableHeadRespVO.FloorInfo floorInfo = new AllVoyageStockBoardTableHeadRespVO.FloorInfo();
                    // 根据房型名称生成key：家庭套房 -> family, 家庭套房PRO -> family_pro
                    String floorKey = floorItem.getRoomModelName().contains("PRO") ? "family_pro" : "family";
                    floorInfo.setKey(floorKey + "_" + floorItem.getFloor() + "f");
                    floorInfo.setName(floorItem.getRoomModelName());
                    floorInfo.setFloors(floorItem.getFloor());
                    floorInfo.setTotalRooms(floorItem.getNum());
                    floorInfoList.add(floorInfo);
                }

                familyVO.setFloors(floorInfoList);
                result.add(familyVO);

                // 标记所有家庭套房相关的房型都已处理
                familyFloors.forEach(f -> processedRoomModelIds.add(f.getRoomModelId()));
            } else {
                // 普通房型处理
                AllVoyageStockBoardTableHeadRespVO tableHeadVO = new AllVoyageStockBoardTableHeadRespVO();

                // 筛选出该房型的所有楼层
                List<RoomModelFloorNumDTO> roomFloors = roomModelFloorNumList.stream()
                        .filter(r -> roomModelId.equals(r.getRoomModelId()))
                        .collect(Collectors.toList());

                RoomModelFloorNumDTO firstItem = roomFloors.get(0);
                tableHeadVO.setKey("room_" + firstItem.getRoomModelId());
                tableHeadVO.setLabel(firstItem.getShortName());

                int sumRooms = roomFloors.stream()
                        .mapToInt(r -> r.getNum() != null ? r.getNum() : 0)
                        .sum();
                tableHeadVO.setSumRooms(sumRooms);

                List<AllVoyageStockBoardTableHeadRespVO.FloorInfo> floorInfoList = new ArrayList<>();
                for (RoomModelFloorNumDTO floorItem : roomFloors) {
                    AllVoyageStockBoardTableHeadRespVO.FloorInfo floorInfo = new AllVoyageStockBoardTableHeadRespVO.FloorInfo();
                    floorInfo.setKey("room_" + floorItem.getRoomModelId() + "_" + floorItem.getFloor() + "f");
                    floorInfo.setName(floorItem.getRoomModelName());
                    floorInfo.setFloors(floorItem.getFloor());
                    floorInfo.setTotalRooms(floorItem.getNum());
                    floorInfoList.add(floorInfo);
                }

                tableHeadVO.setFloors(floorInfoList);
                result.add(tableHeadVO);

                processedRoomModelIds.add(roomModelId);
            }
        }

        return result;
    }

    @Override
    public PageResult<AllVoyageStockBoardRespVO> getAllVoyageStockBoardPage(AllVoyageStockBoardReqVO reqVO) {
        return null;
    }

    /**
     * 填充同比环比数据
     */
    private void fillYoyAndMom(List<VoyageStockBoardItemVO> detailList, Long voyageId) {
        Long yoyVoyageId = voyageStockBoardMapper.selectYoyVoyageId(voyageId);
        Long momVoyageId = voyageStockBoardMapper.selectMomVoyageId(voyageId);

        Map<String, BigDecimal> yoyMap = new HashMap<>();
        if (yoyVoyageId != null) {
            List<VoyageStockBoardItemVO> yoyList = voyageStockBoardMapper.selectBookNumByVoyageId(yoyVoyageId);
            yoyMap = yoyList.stream()
                    .filter(item -> item.getRoomModelId() != null)
                    .collect(Collectors.toMap(
                            item -> item.getRoomModelId() + "_" + item.getFloor(),
                            VoyageStockBoardItemVO::getBookedNum,
                            (v1, v2) -> v1));
        }

        Map<String, BigDecimal> momMap = new HashMap<>();
        if (momVoyageId != null) {
            List<VoyageStockBoardItemVO> momList = voyageStockBoardMapper.selectBookNumByVoyageId(momVoyageId);
            momMap = momList.stream()
                    .filter(item -> item.getRoomModelId() != null)
                    .collect(Collectors.toMap(
                            item -> item.getRoomModelId() + "_" + item.getFloor(),
                            VoyageStockBoardItemVO::getBookedNum,
                            (v1, v2) -> v1));
        }

        for (VoyageStockBoardItemVO item : detailList) {
            String key = item.getRoomModelId() + "_" + item.getFloor();
            BigDecimal currentBookNum = safeDecimal(item.getBookedNum());

            BigDecimal yoyBookNum = yoyMap.get(key);
            item.setYoy(calcYoyPercent(currentBookNum, yoyBookNum));

            BigDecimal momBookNum = momMap.get(key);
            item.setMom(calcYoyPercent(currentBookNum, momBookNum));
        }
    }

    /**
     * 计算派生字段
     */
    private void calculateDerivedFields(List<VoyageStockBoardItemVO> detailList) {
        for (VoyageStockBoardItemVO item : detailList) {
            BigDecimal shelfedNum = safeDecimal(item.getShelfedNum());
            BigDecimal totalNum = safeDecimal(item.getTotalNum());
            BigDecimal depositLock = safeDecimal(item.getDepositLock());
            BigDecimal nameListLock = safeDecimal(item.getNameListLock());
            BigDecimal leaveRoom = safeDecimal(item.getLeaveRoom());
            BigDecimal cutTotal = safeDecimal(item.getCutTotal());
            // BigDecimal cutLock = safeDecimal(item.getCutLock());
            BigDecimal bookedNum = safeDecimal(item.getBookedNum());

            // 锁位总房间 = 定金锁位 + 名单锁位
            BigDecimal lockTotal = depositLock.add(nameListLock);

            // 切位剩余房间数 = 切位总房间 - 其中已锁位的房间
            BigDecimal cutRemain = cutTotal.subtract(depositLock).subtract(nameListLock);
            item.setCutRemain(cutRemain.max(BigDecimal.ZERO));

            // 剩余可售 = 已上架房间数 - 留位房间数 - 锁位房间数
            BigDecimal remainingSell = shelfedNum.subtract(leaveRoom).subtract(lockTotal);
            item.setRemainingSell(remainingSell);

            // 剩余共享 = 已上架房间数 - 留位房间数 - 锁位房间数 - 切位剩余房间数
            BigDecimal remainingShare = remainingSell.subtract(item.getCutRemain());
            item.setRemainingShare(remainingShare);

            // 已订间数/总数
            String bookedRatio = bookedNum.stripTrailingZeros().toPlainString() + "/" + totalNum.stripTrailingZeros().toPlainString();
            item.setBookedRatio(bookedRatio);
        }
    }

    /**
     * 按房型拼接汇总名称
     */
    private void formatRoomModelName(List<VoyageStockBoardItemVO> detailList) {
        Map<Long, List<VoyageStockBoardItemVO>> groupByRoomModel = detailList.stream()
                .collect(Collectors.groupingBy(VoyageStockBoardItemVO::getRoomModelId));

        for (List<VoyageStockBoardItemVO> items : groupByRoomModel.values()) {
            BigDecimal totalBooked = items.stream()
                    .map(VoyageStockBoardItemVO::getBookedNum)
                    .filter(Objects::nonNull)
                    .reduce(BigDecimal.ZERO, BigDecimal::add);
            BigDecimal totalShelfed = items.stream()
                    .map(VoyageStockBoardItemVO::getTotalNum)
                    .filter(Objects::nonNull)
                    .reduce(BigDecimal.ZERO, BigDecimal::add);

            String suffix = "(" + totalBooked.stripTrailingZeros().toPlainString()
                    + "/" + totalShelfed.stripTrailingZeros().toPlainString() + ")";

            VoyageStockBoardItemVO first = items.get(0);
            first.setRoomModelName(first.getRoomModelName() + " " + suffix);
        }
    }

    /**
     * 计算同比/环比百分比
     */
    private String calcYoyPercent(BigDecimal current, BigDecimal last) {
        if (current == null || last == null || last.compareTo(BigDecimal.ZERO) == 0) {
            return "-";
        }
        BigDecimal diff = current.subtract(last);
        BigDecimal percent = diff.multiply(BigDecimal.valueOf(100))
                .divide(last, 1, RoundingMode.HALF_UP);
        if (percent.compareTo(BigDecimal.ZERO) > 0) {
            return "↑ +" + percent.stripTrailingZeros().toPlainString() + "%";
        } else if (percent.compareTo(BigDecimal.ZERO) < 0) {
            return "↓ " + percent.stripTrailingZeros().toPlainString() + "%";
        } else {
            return "— 0%";
        }
    }

    private BigDecimal safeDecimal(BigDecimal val) {
        return val != null ? val : BigDecimal.ZERO;
    }

    /**
     * 构建导出多级表头
     */
    private List<List<String>> buildExportHeaders() {
        List<List<String>> head = new ArrayList<>();
        head.add(new ArrayList<>(Collections.singletonList("房型")));
        head.add(new ArrayList<>(Collections.singletonList("楼层")));
        head.add(new ArrayList<>(Collections.singletonList("已订间数/总数")));
        head.add(new ArrayList<>(Arrays.asList("锁位房间", "定金锁位")));
        head.add(new ArrayList<>(Arrays.asList("锁位房间", "名单锁位")));
        head.add(new ArrayList<>(Collections.singletonList("留位房间")));
        head.add(new ArrayList<>(Arrays.asList("切位房间", "切位总房间")));
        head.add(new ArrayList<>(Arrays.asList("切位房间", "切位剩余房间数")));
        head.add(new ArrayList<>(Collections.singletonList("剩余可售")));
        head.add(new ArrayList<>(Collections.singletonList("剩余共享")));
        head.add(new ArrayList<>(Collections.singletonList("同比")));
        head.add(new ArrayList<>(Collections.singletonList("环比")));
        return head;
    }

    /**
     * 转换导出数据
     */
    private List<List<Object>> transformExportData(VoyageStockBoardRespVO respVO) {
        List<List<Object>> result = new ArrayList<>();
        List<VoyageStockBoardItemVO> detailList = respVO.getDetailList();
        if (CollUtil.isEmpty(detailList)) {
            return result;
        }

        for (VoyageStockBoardItemVO item : detailList) {
            List<Object> rowData = new ArrayList<>();
            rowData.add(item.getRoomModelName() != null ? item.getRoomModelName() : "");
            rowData.add(item.getFloor() != null ? item.getFloor() : "");
            rowData.add(item.getBookedRatio() != null ? item.getBookedRatio() : "");
            rowData.add(formatDecimal(item.getDepositLock()));
            rowData.add(formatDecimal(item.getNameListLock()));
            rowData.add(formatDecimal(item.getLeaveRoom()));
            rowData.add(formatDecimal(item.getCutTotal()));
            rowData.add(formatDecimal(item.getCutRemain()));
            rowData.add(formatDecimal(item.getRemainingSell()));
            rowData.add(formatDecimal(item.getRemainingShare()));
            rowData.add(item.getYoy() != null ? item.getYoy() : "");
            rowData.add(item.getMom() != null ? item.getMom() : "");
            result.add(rowData);
        }
        return result;
    }

    private String formatDecimal(BigDecimal val) {
        if (val == null) return "0";
        return val.stripTrailingZeros().toPlainString();
    }

    // ==================== 客源地分析看板 ====================

    @Override
    public VisitorSourceDashboardRespVO getVisitorSourceDashboard(VisitorSourceDashboardReqVO reqVO) {
        // 1. 查询游客列表
        List<TradeVisitorDO> visitorList = voyageStockBoardMapper.selectVisitorListForSourceDashboard(
                reqVO.getShipId(), reqVO.getVoyageIds());
        if (CollUtil.isEmpty(visitorList)) {
            return buildEmptyResp();
        }

        // 2. 分类统计
        return analyzeVisitorSource(visitorList);
    }

    @Override
    public void exportVisitorSourceDashboardExcel(VisitorSourceDashboardReqVO reqVO, HttpServletResponse response) throws IOException {
        VisitorSourceDashboardRespVO data = getVisitorSourceDashboard(reqVO);

        List<VisitorSourceExportVO> domesticExportList = buildDomesticExportData(data);
        List<VisitorSourceInternationalExportVO> internationalExportList = buildInternationalExportData(data);

        if (CollUtil.isEmpty(domesticExportList) && CollUtil.isEmpty(internationalExportList)) {
            ExcelUtils.exportEmpty(response, "客源地分析看板");
            return;
        }

        response.addHeader("Content-Disposition",
                "attachment;filename=" + URLEncoder.encode("客源地分析看板.xlsx", StandardCharsets.UTF_8.name()));
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8");

        // 使用 EasyExcel 导出多 sheet
        ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream()).build();

        // Sheet1: 国内市场客源地结构示例
        if (CollUtil.isNotEmpty(domesticExportList)) {
            WriteSheet sheet1 = EasyExcel.writerSheet(0, "国内市场客源地结构示例")
                    .head(VisitorSourceExportVO.class)
                    .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
                    .build();
            excelWriter.write(domesticExportList, sheet1);
        }

        // Sheet2: 国际市场客源地结构示例
        if (CollUtil.isNotEmpty(internationalExportList)) {
            WriteSheet sheet2 = EasyExcel.writerSheet(1, "国际市场客源地结构示例")
                    .head(VisitorSourceInternationalExportVO.class)
                    .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
                    .build();
            excelWriter.write(internationalExportList, sheet2);
        }

        excelWriter.finish();
    }

    /**
     * 分析客源地数据
     */
    private VisitorSourceDashboardRespVO analyzeVisitorSource(List<TradeVisitorDO> visitorList) {
        VisitorSourceDashboardRespVO respVO = new VisitorSourceDashboardRespVO();

        // 国内省份统计
        Map<String, Integer> provinceCountMap = new HashMap<>();
        // 国内城市统计（全国）
        Map<String, Integer> cityCountMap = new HashMap<>();
        // 省份-城市统计
        Map<String, Map<String, Integer>> provinceCityCountMap = new HashMap<>();
        // 国际国家统计
        Map<String, Integer> countryCountMap = new HashMap<>();

        int domesticTotal = 0;
        int internationalTotal = 0;

        for (TradeVisitorDO visitor : visitorList) {
            String nationalityName = visitor.getNationalityName();
            if (nationalityName == null) {
                nationalityName = "";
            }

            // 尝试解析国内省份
            String province = IdCardProvinceUtil.getProvinceName(
                    visitor.getCredentialType(), visitor.getCredentialNo(), nationalityName);

            if (StrUtil.isNotBlank(province)) {
                // 国内游客
                domesticTotal++;
                provinceCountMap.merge(province, 1, Integer::sum);

                // 解析城市
                String city = IdCardProvinceUtil.getCityName(visitor.getCredentialType(), visitor.getCredentialNo());
                if (StrUtil.isBlank(city)) {
                    city = province; // 城市解析不到，用省份代替
                }
                cityCountMap.merge(city, 1, Integer::sum);
                provinceCityCountMap.computeIfAbsent(province, k -> new HashMap<>())
                        .merge(city, 1, Integer::sum);
            } else {
                // 国际游客
                String country = StrUtil.isNotBlank(nationalityName) ? nationalityName : "其他";
                internationalTotal++;
                countryCountMap.merge(country, 1, Integer::sum);
            }
        }

        // 构建国内客源地列表（按人数降序）
        int finalDomesticTotal = domesticTotal;
        List<VisitorSourceDashboardRespVO.VisitorSourceItemVO> domesticList = provinceCountMap.entrySet().stream()
                .sorted(Map.Entry.<String, Integer>comparingByValue().reversed())
                .map(entry -> {
                    VisitorSourceDashboardRespVO.VisitorSourceItemVO item = new VisitorSourceDashboardRespVO.VisitorSourceItemVO();
                    item.setName(entry.getKey());
                    item.setCount(entry.getValue());
                    item.setRatio(calcRatio(entry.getValue(), finalDomesticTotal));
                    return item;
                })
                .collect(Collectors.toList());

        // 构建国际客源地列表（按人数降序）
        int finalInternationalTotal = internationalTotal;
        List<VisitorSourceDashboardRespVO.VisitorSourceItemVO> internationalList = countryCountMap.entrySet().stream()
                .sorted(Map.Entry.<String, Integer>comparingByValue().reversed())
                .map(entry -> {
                    VisitorSourceDashboardRespVO.VisitorSourceItemVO item = new VisitorSourceDashboardRespVO.VisitorSourceItemVO();
                    item.setName(entry.getKey());
                    item.setCount(entry.getValue());
                    item.setRatio(calcRatio(entry.getValue(), finalInternationalTotal));
                    return item;
                })
                .collect(Collectors.toList());

        // 省份来源排行
        List<String> provinceRankList = domesticList.stream()
                .map(VisitorSourceDashboardRespVO.VisitorSourceItemVO::getName)
                .collect(Collectors.toList());

        // 全国城市来源排行
        List<String> nationalCityRankList = cityCountMap.entrySet().stream()
                .sorted(Map.Entry.<String, Integer>comparingByValue().reversed())
                .map(Map.Entry::getKey)
                .collect(Collectors.toList());

        // 省份-城市排行映射
        Map<String, List<String>> provinceCityRankMap = new HashMap<>();
        for (Map.Entry<String, Map<String, Integer>> entry : provinceCityCountMap.entrySet()) {
            List<String> cityRank = entry.getValue().entrySet().stream()
                    .sorted(Map.Entry.<String, Integer>comparingByValue().reversed())
                    .map(Map.Entry::getKey)
                    .collect(Collectors.toList());
            provinceCityRankMap.put(entry.getKey(), cityRank);
        }

        respVO.setDomesticSourceList(domesticList);
        respVO.setInternationalSourceList(internationalList);
        respVO.setProvinceRankList(provinceRankList);
        respVO.setNationalCityRankList(nationalCityRankList);
        respVO.setProvinceCityRankMap(provinceCityRankMap);
        respVO.setDomesticTotal(domesticTotal);
        respVO.setInternationalTotal(internationalTotal);

        return respVO;
    }

    /**
     * 计算占比
     */
    private String calcRatio(int part, int total) {
        if (total == 0) {
            return "0.00%";
        }
        BigDecimal ratio = BigDecimal.valueOf(part)
                .multiply(BigDecimal.valueOf(100))
                .divide(BigDecimal.valueOf(total), 2, RoundingMode.HALF_UP);
        return ratio.stripTrailingZeros().toPlainString() + "%";
    }

    /**
     * 构建空响应
     */
    private VisitorSourceDashboardRespVO buildEmptyResp() {
        VisitorSourceDashboardRespVO respVO = new VisitorSourceDashboardRespVO();
        respVO.setDomesticSourceList(Collections.emptyList());
        respVO.setInternationalSourceList(Collections.emptyList());
        respVO.setProvinceRankList(Collections.emptyList());
        respVO.setNationalCityRankList(Collections.emptyList());
        respVO.setProvinceCityRankMap(Collections.emptyMap());
        respVO.setDomesticTotal(0);
        respVO.setInternationalTotal(0);
        return respVO;
    }

    /**
     * 构建国内导出数据
     */
    private List<VisitorSourceExportVO> buildDomesticExportData(VisitorSourceDashboardRespVO data) {
        List<VisitorSourceExportVO> list = new ArrayList<>();
        List<VisitorSourceDashboardRespVO.VisitorSourceItemVO> domesticList = data.getDomesticSourceList();
        List<String> provinceRankList = data.getProvinceRankList();
        List<String> nationalCityRankList = data.getNationalCityRankList();
        Map<String, List<String>> provinceCityRankMap = data.getProvinceCityRankMap();

        if (CollUtil.isEmpty(domesticList)) {
            return list;
        }

        int maxRows = Math.max(domesticList.size(),
                Math.max(provinceRankList.size(), nationalCityRankList.size()));

        for (int i = 0; i < maxRows; i++) {
            VisitorSourceExportVO vo = new VisitorSourceExportVO();

            // 省份、人数、占比
            if (i < domesticList.size()) {
                VisitorSourceDashboardRespVO.VisitorSourceItemVO item = domesticList.get(i);
                vo.setProvince(item.getName());
                vo.setCount(item.getCount());
                vo.setRatio(item.getRatio());
            }

            // 省份来源排行
            if (i < provinceRankList.size()) {
                vo.setProvinceRank(provinceRankList.get(i));
            }

            // 该省份城市来源排行
            if (i < domesticList.size()) {
                String province = domesticList.get(i).getName();
                List<String> cityList = provinceCityRankMap.get(province);
                if (CollUtil.isNotEmpty(cityList)) {
                    // 取前3个城市
                    StringBuilder cityRankSb = new StringBuilder();
                    for (int j = 0; j < Math.min(cityList.size(), 3); j++) {
                        if (j > 0) {
                            cityRankSb.append(" ");
                        }
                        cityRankSb.append(j + 1).append(".").append(cityList.get(j));
                    }
                    vo.setCityRank(cityRankSb.toString());
                }
            }

            // 全国城市来源排行
            if (i < nationalCityRankList.size()) {
                vo.setNationalCityRank((i + 1) + nationalCityRankList.get(i));
            }

            list.add(vo);
        }

        return list;
    }

    /**
     * 构建国际导出数据
     */
    private List<VisitorSourceInternationalExportVO> buildInternationalExportData(VisitorSourceDashboardRespVO data) {
        List<VisitorSourceInternationalExportVO> list = new ArrayList<>();
        List<VisitorSourceDashboardRespVO.VisitorSourceItemVO> internationalList = data.getInternationalSourceList();

        if (CollUtil.isEmpty(internationalList)) {
            return list;
        }

        for (VisitorSourceDashboardRespVO.VisitorSourceItemVO item : internationalList) {
            VisitorSourceInternationalExportVO vo = new VisitorSourceInternationalExportVO();
            vo.setCountry(item.getName());
            vo.setCount(item.getCount());
            vo.setRatio(item.getRatio());
            list.add(vo);
        }

        return list;
    }
}
