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

import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.yc.ship.framework.common.pojo.PageResult;
import com.yc.ship.framework.common.pojo.PageParam;
import com.yc.ship.framework.excel.core.util.ExcelUtils;
import com.yc.ship.module.trade.controller.admin.report.vo.BankStatementReportPageReqVO;
import com.yc.ship.module.trade.controller.admin.report.vo.BankStatementReportRespVO;
import com.yc.ship.module.trade.dal.dataobject.bill.BillDO;
import com.yc.ship.module.trade.dal.dataobject.order.TradeOrderPayDO;
import com.yc.ship.module.trade.controller.admin.report.vo.RefundStatisticsVO;
import com.yc.ship.module.trade.dal.mysql.bill.BillMapper;
import com.yc.ship.module.trade.dal.mysql.order.TradeOrderPayMapper;
import com.yc.ship.module.trade.service.report.BankStatementReportService;
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.time.LocalDate;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 银行对账报表 Service 实现类
 *
 * @author auto-generated
 */
@Service
@Slf4j
public class BankStatementReportServiceImpl implements BankStatementReportService {

    @Resource
    private TradeOrderPayMapper tradeOrderPayMapper;

    @Resource
    private BillMapper billMapper;

    /**
     * 查询银行对账报表分页
     *
     * @param pageReqVO 分页查询条件
     * @return 分页结果
     */
    @Override
    public PageResult<BankStatementReportRespVO> getBankStatementReportPage(BankStatementReportPageReqVO pageReqVO) {
        // 1. 构建日期列表
        List<LocalDate> dateList = buildDateList(pageReqVO.getStartDate(), pageReqVO.getEndDate());
        if (CollUtil.isEmpty(dateList)) {
            return new PageResult<>(new ArrayList<>(), 0L);
        }

        // 2. 分页处理日期列表
        int pageNo = pageReqVO.getPageNo();
        int pageSize = pageReqVO.getPageSize();
        int total = dateList.size();
        int fromIndex = (pageNo - 1) * pageSize;
        int toIndex = Math.min(fromIndex + pageSize, total);

        if (fromIndex >= total) {
            return new PageResult<>(new ArrayList<>(), (long) total);
        }
        List<LocalDate> subDateList = new ArrayList<>();
        if (toIndex == -1) {
            subDateList = dateList.subList(fromIndex, dateList.size());
        } else {
            subDateList = dateList.subList(fromIndex, toIndex);
        }

        LocalDate startDate = subDateList.get(0);
        LocalDate endDate = subDateList.get(subDateList.size() - 1);

        // 3. 批量查询当前页日期范围内的所有统计数据(使用数据库聚合)
        LocalDateTime startDateTime = startDate.atStartOfDay();
        LocalDateTime endDateTime = endDate.plusDays(1).atStartOfDay();

        // 3.1 批量查询账单还款数据 - 按日期分组汇总
        LambdaQueryWrapper<BillDO> billWrapper = new LambdaQueryWrapper<>();
        billWrapper.in(BillDO::getBillStatus, 1,2)
                .between(BillDO::getPayTime, startDateTime, endDateTime)
                .select(BillDO::getPayTime, BillDO::getPayAmount); // 只查询必要字段
        List<BillDO> billList = billMapper.selectList(billWrapper);

        // 3.2 批量查询预存款充值数据
        LambdaQueryWrapper<TradeOrderPayDO> depositWrapper = new LambdaQueryWrapper<>();
        depositWrapper.eq(TradeOrderPayDO::getPayStatus, 1)
                .eq(TradeOrderPayDO::getPaymentType, 5)
                .between(TradeOrderPayDO::getPaymentDate, startDateTime, endDateTime)
                .select(TradeOrderPayDO::getPaymentDate, TradeOrderPayDO::getPayAmount);
        List<TradeOrderPayDO> depositList = tradeOrderPayMapper.selectList(depositWrapper);

        // 3.3 批量查询订单支付数据（需减去退款金额，计算净支付）
        LambdaQueryWrapper<TradeOrderPayDO> orderWrapper = new LambdaQueryWrapper<>();
        orderWrapper.eq(TradeOrderPayDO::getPayStatus, 1)
                .in(TradeOrderPayDO::getPaymentType, 1,2,3)
                .between(TradeOrderPayDO::getPaymentDate, startDateTime, endDateTime)
                .select(TradeOrderPayDO::getPaymentDate, TradeOrderPayDO::getPayAmount);
        List<TradeOrderPayDO> orderPaymentList = tradeOrderPayMapper.selectList(orderWrapper);

        // 3.4 批量查询退款数据（关联trade_order_pay和trade_refund，按退款时间分组）
        List<RefundStatisticsVO> refundList = tradeOrderPayMapper.selectRefundStatistics(startDateTime, endDateTime);

        // 4. 按日期分组并计算统计数据
        Map<LocalDate, BigDecimal> billMap = groupByDateAndSum(billList, BillDO::getPayTime, BillDO::getPayAmount);
        Map<LocalDate, BigDecimal> depositMap = groupByDateAndSum(depositList, TradeOrderPayDO::getPaymentDate, TradeOrderPayDO::getPayAmount);
        Map<LocalDate, BigDecimal> refundMap = groupByDateAndSumRefund(refundList);
        Map<LocalDate, BigDecimal> orderMap = groupByDateAndSum(orderPaymentList, TradeOrderPayDO::getPaymentDate, TradeOrderPayDO::getPayAmount);

        // 5. 构建结果列表
        List<BankStatementReportRespVO> resultList = new ArrayList<>(subDateList.size());
        for (LocalDate tradeDate : subDateList) {
            BankStatementReportRespVO report = new BankStatementReportRespVO();
            report.setTradeDate(tradeDate.toString());

            BigDecimal billRepayment = billMap.getOrDefault(tradeDate, BigDecimal.ZERO);
            BigDecimal depositRecharge = depositMap.getOrDefault(tradeDate, BigDecimal.ZERO);
            BigDecimal refundAmount = refundMap.getOrDefault(tradeDate, BigDecimal.ZERO);
            BigDecimal orderPayment = orderMap.getOrDefault(tradeDate, BigDecimal.ZERO);

            report.setBillRepayment(billRepayment);
            report.setDepositRecharge(depositRecharge);
            report.setOrderPayment(orderPayment.subtract(refundAmount));

            // 商户平台合计 = 账单还款 + 预存款充值 + 订单支付 - 退款
            BigDecimal merchantPlatformTotal = billRepayment.add(depositRecharge).add(report.getOrderPayment());
            report.setMerchantPlatformTotal(merchantPlatformTotal);

            // 对公充值预存款 - TODO: 需要根据实际业务逻辑查询对公充值数据
            BigDecimal publicDepositRecharge = BigDecimal.ZERO;
            report.setPublicDepositRecharge(publicDepositRecharge);

            // 农行进账合计 = 商户平台合计 + 对公充值预存款
            BigDecimal agriculturalBankTotal = merchantPlatformTotal.add(publicDepositRecharge);
            report.setAgriculturalBankTotal(agriculturalBankTotal);

            // 其他银行(手工下账单) - TODO: 根据实际业务逻辑查询其他银行手工下账单数据
            BigDecimal otherBankManual = BigDecimal.ZERO;
            report.setOtherBankManual(otherBankManual);

            // 银行进账合计 = 其他银行 + 农行进账合计
            BigDecimal bankTotal = otherBankManual.add(agriculturalBankTotal);
            report.setBankTotal(bankTotal);

            resultList.add(report);
        }

        return new PageResult<>(resultList, (long) total);
    }

    /**
     * 按日期分组并求和
     */
    private <T> Map<LocalDate, BigDecimal> groupByDateAndSum(List<T> list, Function<T, LocalDateTime> dateExtractor, Function<T, BigDecimal> amountExtractor) {
        if (CollUtil.isEmpty(list)) {
            return new HashMap<>();
        }
        return list.stream()
                .collect(Collectors.groupingBy(
                        item -> dateExtractor.apply(item).toLocalDate(),
                        Collectors.reducing(BigDecimal.ZERO, amountExtractor, BigDecimal::add)
                ));
    }

    /**
     * 按日期分组并求和（退款数据专用）
     */
    private Map<LocalDate, BigDecimal> groupByDateAndSumRefund(List<RefundStatisticsVO> list) {
        if (CollUtil.isEmpty(list)) {
            return new HashMap<>();
        }
        return list.stream()
                .filter(item -> item.getRefundTime() != null)
                .collect(Collectors.groupingBy(
                        item -> item.getRefundTime().toLocalDate(),
                        Collectors.reducing(BigDecimal.ZERO, RefundStatisticsVO::getRefundAmount, BigDecimal::add)
                ));
    }

    /**
     * 导出银行对账报表 Excel
     *
     * @param pageReqVO 查询条件
     * @param response  HTTP响应
     * @throws IOException IO异常
     */
    @Override
    public void exportBankStatementReportExcel(BankStatementReportPageReqVO pageReqVO, HttpServletResponse response) throws IOException {
        // 1. 查询所有数据(不分页)
        pageReqVO.setPageNo(1);
        pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
        PageResult<BankStatementReportRespVO> pageResult = getBankStatementReportPage(pageReqVO);
        List<BankStatementReportRespVO> list = pageResult.getList();

        // 2. 计算合计行
        if (CollUtil.isNotEmpty(list)) {
            BankStatementReportRespVO summary = calculateSummary(list);
            summary.setTradeDate("合计"); // 合计行日期置空
            list.add(summary);
        }

        // 3. 导出 Excel
        ExcelUtils.write(response, "银行对账报表.xls", "数据", BankStatementReportRespVO.class, list);
    }

    /**
     * 构建日期列表
     */
    private List<LocalDate> buildDateList(LocalDate startDate, LocalDate endDate) {
        List<LocalDate> dateList = new ArrayList<>();
        if (startDate == null || endDate == null) {
            return dateList;
        }
        if (startDate.isAfter(endDate)) {
            return dateList;
        }

        long daysBetween = ChronoUnit.DAYS.between(startDate, endDate);
        for (int i = 0; i <= daysBetween; i++) {
            dateList.add(startDate.plusDays(i));
        }
        return dateList;
    }

    /**
     * 根据日期构建报表数据
     */
    private BankStatementReportRespVO buildReportByDate(LocalDate tradeDate) {
        BankStatementReportRespVO report = new BankStatementReportRespVO();
        report.setTradeDate(tradeDate.toString());

        LocalDateTime startDateTime = tradeDate.atStartOfDay();
        LocalDateTime endDateTime = tradeDate.plusDays(1).atStartOfDay();

        // 1. 查询账单还款(扫码) - 从bill表中查询
        LambdaQueryWrapper<BillDO> billWrapper = new LambdaQueryWrapper<>();
        billWrapper.eq(BillDO::getBillStatus, 1) // 已支付
                .between(BillDO::getPayTime, startDateTime, endDateTime);
        List<BillDO> billList = billMapper.selectList(billWrapper);
        BigDecimal billRepayment = billList.stream()
                .map(BillDO::getPayAmount)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        report.setBillRepayment(billRepayment == null ? BigDecimal.ZERO : billRepayment);

        // 2. 查询预存款充值(扫码) - 从支付表中查询分销商充值订单
        // TODO: 需要根据实际业务逻辑判断哪些是预存款充值
        // 这里假设orderType为其他订单的是预存款充值
        LambdaQueryWrapper<TradeOrderPayDO> depositWrapper = new LambdaQueryWrapper<>();
        depositWrapper.eq(TradeOrderPayDO::getPayStatus, 1) // 已支付
                .in(TradeOrderPayDO::getPaymentType, CollUtil.newArrayList(5)) // 支付方式
                .between(TradeOrderPayDO::getPaymentDate, startDateTime, endDateTime);
        // TODO: 需要添加具体的判断条件来区分扫码支付方式
        List<TradeOrderPayDO> depositList = tradeOrderPayMapper.selectList(depositWrapper);
        BigDecimal depositRecharge = depositList.stream()
                .map(TradeOrderPayDO::getPayAmount)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        report.setDepositRecharge(depositRecharge == null ? BigDecimal.ZERO : depositRecharge);

        // 3. 查询订单支付(扫码) - 从支付表中查询交易订单
        LambdaQueryWrapper<TradeOrderPayDO> orderWrapper = new LambdaQueryWrapper<>();
        orderWrapper.eq(TradeOrderPayDO::getPayStatus, 1) // 已支付
                .in(TradeOrderPayDO::getPaymentType, CollUtil.newArrayList(1,2,3)) // 支付方式
                .between(TradeOrderPayDO::getPaymentDate, startDateTime, endDateTime);
        // TODO: 需要添加具体的判断条件来区分扫码支付方式
        List<TradeOrderPayDO> orderPaymentList = tradeOrderPayMapper.selectList(orderWrapper);
        BigDecimal orderPayment = orderPaymentList.stream()
                .map(TradeOrderPayDO::getPayAmount)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        report.setOrderPayment(orderPayment == null ? BigDecimal.ZERO : orderPayment);

        // 4. 商户平台合计 = 账单还款 + 预存款充值 + 订单支付
        BigDecimal merchantPlatformTotal = report.getBillRepayment()
                .add(report.getDepositRecharge())
                .add(report.getOrderPayment());
        report.setMerchantPlatformTotal(merchantPlatformTotal);

        // 5. 对公充值预存款 - 从支付表中查询对公充值
        LambdaQueryWrapper<TradeOrderPayDO> publicDepositRechargeWrapper = new LambdaQueryWrapper<>();
        orderWrapper.eq(TradeOrderPayDO::getPayStatus, 1) // 已支付
                .in(TradeOrderPayDO::getPaymentType, CollUtil.newArrayList(10)) // 支付方式
                .between(TradeOrderPayDO::getPaymentDate, startDateTime, endDateTime);
        // TODO: 需要根据实际业务逻辑查询对公充值数据
        List<TradeOrderPayDO> publicDepositRechargeList = tradeOrderPayMapper.selectList(publicDepositRechargeWrapper);
        BigDecimal publicDepositRecharge = publicDepositRechargeList.stream()
                .map(TradeOrderPayDO::getPayAmount)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        report.setPublicDepositRecharge(publicDepositRecharge == null ? BigDecimal.ZERO : publicDepositRecharge);

        // 6. 农行进账合计 = 商户平台合计 + 对公充值预存款
        BigDecimal agriculturalBankTotal = report.getMerchantPlatformTotal()
                .add(report.getPublicDepositRecharge());
        report.setAgriculturalBankTotal(agriculturalBankTotal);

        // 7. 其他银行(手工下账单) - 需要根据实际业务逻辑查询
        // TODO: 根据实际业务逻辑查询其他银行手工下账单数据
        BigDecimal otherBankManual = BigDecimal.ZERO;
        report.setOtherBankManual(otherBankManual);

        // 8. 银行进账合计 = 其他银行 + 农行进账合计
        BigDecimal bankTotal = report.getOtherBankManual()
                .add(report.getAgriculturalBankTotal());
        report.setBankTotal(bankTotal);

        return report;
    }

    /**
     * 计算合计
     */
    private BankStatementReportRespVO calculateSummary(List<BankStatementReportRespVO> list) {
        BankStatementReportRespVO summary = new BankStatementReportRespVO();

        BigDecimal totalBillRepayment = BigDecimal.ZERO;
        BigDecimal totalDepositRecharge = BigDecimal.ZERO;
        BigDecimal totalOrderPayment = BigDecimal.ZERO;
        BigDecimal totalMerchantPlatformTotal = BigDecimal.ZERO;
        BigDecimal totalPublicDepositRecharge = BigDecimal.ZERO;
        BigDecimal totalAgriculturalBankTotal = BigDecimal.ZERO;
        BigDecimal totalOtherBankManual = BigDecimal.ZERO;
        BigDecimal totalBankTotal = BigDecimal.ZERO;

        for (BankStatementReportRespVO item : list) {
            totalBillRepayment = totalBillRepayment.add(item.getBillRepayment());
            totalDepositRecharge = totalDepositRecharge.add(item.getDepositRecharge());
            totalOrderPayment = totalOrderPayment.add(item.getOrderPayment());
            totalMerchantPlatformTotal = totalMerchantPlatformTotal.add(item.getMerchantPlatformTotal());
            totalPublicDepositRecharge = totalPublicDepositRecharge.add(item.getPublicDepositRecharge());
            totalAgriculturalBankTotal = totalAgriculturalBankTotal.add(item.getAgriculturalBankTotal());
            totalOtherBankManual = totalOtherBankManual.add(item.getOtherBankManual());
            totalBankTotal = totalBankTotal.add(item.getBankTotal());
        }

        summary.setBillRepayment(totalBillRepayment);
        summary.setDepositRecharge(totalDepositRecharge);
        summary.setOrderPayment(totalOrderPayment);
        summary.setMerchantPlatformTotal(totalMerchantPlatformTotal);
        summary.setPublicDepositRecharge(totalPublicDepositRecharge);
        summary.setAgriculturalBankTotal(totalAgriculturalBankTotal);
        summary.setOtherBankManual(totalOtherBankManual);
        summary.setBankTotal(totalBankTotal);

        return summary;
    }

}
