package com.czg.service.order.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.NumberUtil;
import com.czg.config.RabbitPublisher;
import com.czg.excel.ExcelExportUtil;
import com.czg.exception.CzgException;
import com.czg.order.dto.FinanceStsDTO;
import com.czg.order.entity.ShopOrderStatistic;
import com.czg.order.param.FinanceStsQueryParam;
import com.czg.order.param.SaleSummaryCountParam;
import com.czg.order.service.FinanceStsService;
import com.czg.order.service.ShopOrderStatisticService;
import com.czg.print.DayReportPrintDTO;
import com.czg.print.DaySettlePrintDTO;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.LongStream;

/**
 * @author yjjie
 * @date 2026/2/2 10:11
 */
@Service
public class FinanceStsServiceImpl implements FinanceStsService {

    @Resource
    private ShopOrderStatisticService orderStatisticService;
    @Resource
    private RabbitPublisher rabbitPublisher;

    @Override
    public FinanceStsDTO getFinanceSts(FinanceStsQueryParam param) {
        if (param.notValidDateRange()) {
            throw new CzgException("日期范围无效");
        }

        ShopOrderStatistic statistic = getStatisticData(param);

        return new FinanceStsDTO()
                .setDate(LocalDateTimeUtil.format(param.getQueryDate(), "yyyy-MM-dd"))
                .setTurnover(new FinanceStsDTO.TurnoverSts()
                        .setTurnover(statistic.getPayAmount().add(statistic.getRechargeAmount()))
                        .setWechat(statistic.getWechatPayAmount())
                        .setAlipay(statistic.getAlipayPayAmount())
                        .setSelfScan(statistic.getMainScanPayAmount())
                        .setBarScan(statistic.getBackScanPayAmount())
                        .setCash(statistic.getCashPayAmount())
                        .setRecharge(statistic.getRechargeAmount())
                        .setOwed(statistic.getCreditPayAmount())
                        .setBalance(statistic.getMemberPayAmount()))
                .setOrder(new FinanceStsDTO.OrderSts()
                        .setOrderAmount(statistic.getPayAmount())
                        .setOrderCount(statistic.getOrderCount()))
                .setDiscount(new FinanceStsDTO.DiscountSts()
                        .setDiscountAmount(statistic.getDiscountAmount())
                        .setDiscountCount(statistic.getDiscountCount())
                        .setNewConsumerDiscount(statistic.getNewCustomerDiscountAmount())
                        .setFreeCashAmount(statistic.getBackDiscountAmount())
                        .setFullMinusAmount(statistic.getFullDiscountAmount())
                        .setCouponAmount(statistic.getCouponDiscountAmount())
                        .setMemberDiscount(statistic.getMemberDiscountAmount())
                        .setPointsDiscountAmount(statistic.getPointDiscountAmount())
                        .setOrderDiscount(statistic.getOrderPriceDiscountAmount()))
                .setRefund(new FinanceStsDTO.RefundSts()
                        .setRefundAmount(statistic.getRefundAmount().add(statistic.getRechargeRefundAmount()))
                        .setOnlineRefundAmount(statistic.getOnlineRefundAmount())
                        .setCashRefundAmount(statistic.getCashRefundAmount())
                        .setCreditRefundAmount(statistic.getCreditRefundAmount())
                        .setMemberRefundAmount(statistic.getMemberRefundAmount())
                        .setRechargeRefundAmount(statistic.getRechargeRefundAmount())
                        .setOnlineRechargeRefundAmount(statistic.getOnlineRechargeRefundAmount())
                        .setCashRechargeRefundAmount(statistic.getCashRechargeRefundAmount()))
                .setSts(new FinanceStsDTO.Sts()
                        .setCustomerCount(statistic.getCustomerCount())
                        .setOrderCount(statistic.getOrderCount())
                        .setTableCount(statistic.getTableCount())
                        .setAvgPayAmount(statistic.getAvgPayAmount())
                        .setTurnoverRate(statistic.getTurnoverRate())
                        .setProfitAmount(statistic.getProfitAmount())
                        .setProductCostAmount(statistic.getProductCostAmount())
                        .setProfitRate(statistic.getProfitRate())
                        .setNetProfitRate(statistic.getNetProfitRate())
                        .setNetProfitRate(statistic.getNetProfitRate()));
    }

    @Override
    public void exportFinanceSts(FinanceStsQueryParam param, HttpServletResponse response) {
        if (param.notValidDateRange()) {
            throw new CzgException("日期范围无效");
        }

        ShopOrderStatistic statistic = getStatisticData(param);

        if ("czg".equals(param.getPlatform())) {
            ExcelExportUtil.exportToResponse(List.of(statistic), ShopOrderStatistic.class, "财务报表", response);
            return;
        }
        throw new CzgException("平台不支持");
    }

    @Override
    public void printDayReport(SaleSummaryCountParam param) {
        List<Map<String, Object>> list = buildDayReportPrint(param);
        if (CollUtil.isEmpty(list)) {
            throw new CzgException("无可打印数据");
        }
        // 打印经营日报
        rabbitPublisher.sendOtherPrintMsg(param.getShopId(), list, "DAY_REPORT");
    }

    @Override
    public void printDaySettle(SaleSummaryCountParam param) {
        List<Map<String, Object>> daySettleList = buildDaySettlePrint(param);
        if (CollUtil.isEmpty(daySettleList)) {
            throw new CzgException("无可打印数据");
        }
        // 打印日结单
        rabbitPublisher.sendOtherPrintMsg(param.getShopId(), daySettleList, "DAY_ORDER");
    }


    private List<Map<String, Object>> buildDayReportPrint(SaleSummaryCountParam param) {
        List<Map<String, Object>> list = new ArrayList<>();
        if (param.getBeginDate() != null && param.getEndDate() != null) {
            // 计算相差天数
            long days = java.time.temporal.ChronoUnit.DAYS.between(param.getBeginDate(), param.getEndDate());
            List<LocalDate> dateList = LongStream.rangeClosed(0, days)
                    .mapToObj(param.getBeginDate()::plusDays)
                    .toList();
            for (LocalDate localDate : dateList) {
                Map<String, Object> dayReportMap = new HashMap<>();
                DayReportPrintDTO dayReportPrintDTO = new DayReportPrintDTO();
                FinanceStsQueryParam financeStsQueryParam = new FinanceStsQueryParam();
                financeStsQueryParam.setQueryDate(localDate);
                financeStsQueryParam.setShopId(param.getShopId());
                ShopOrderStatistic statisticData = getStatisticData(financeStsQueryParam);
                // ====================== 核心：填充DTO ======================
                // 1. 填充基础信息
                dayReportPrintDTO.setStatisticsTime(localDate.toString());
                dayReportPrintDTO.setOperator(param.getOperator());


                // 2. 填充 营业统计 BusinessSts
                DayReportPrintDTO.BusinessSts businessSts = new DayReportPrintDTO.BusinessSts();
                businessSts.setOriginAmount(statisticData.getOriginAmount()); // 订单原价总额
                businessSts.setTurnover(NumberUtil.add(statisticData.getPayAmount(), statisticData.getRechargeAmount())); // 营业额=实付金额
                businessSts.setDiscountAmount(statisticData.getDiscountAmount()); // 优惠总金额
                businessSts.setOrderCount(statisticData.getOrderCount()); // 订单总数
                businessSts.setRefundAmount(statisticData.getRefundAmount()); // 退款金额
                // 退款订单数 实体无直接字段，用refundCount/自定义计算
                businessSts.setRefundOrderCount(statisticData.getRefundCount() == null ? 0L : statisticData.getRefundCount());
                // 计算折前/折后单均价
                businessSts.setAverageOrderAmount(calcAvg(statisticData.getOriginAmount(), statisticData.getOrderCount()));
                businessSts.setAverageTurnover(calcAvg(statisticData.getPayAmount(), statisticData.getOrderCount()));
                dayReportPrintDTO.setTurnover(businessSts);

                // 3. 填充 收入来源（原价统计）sourceIncome
                DayReportPrintDTO.Income sourceIncome = new DayReportPrintDTO.Income();
                sourceIncome.setCash(statisticData.getCashPayAmount());
                sourceIncome.setWechat(statisticData.getWechatPayAmount());
                sourceIncome.setAlipay(statisticData.getAlipayPayAmount());
                sourceIncome.setMeituan(statisticData.getMeituanGroup());
                sourceIncome.setDouyin(statisticData.getDouyinGroup());

                sourceIncome.setBarScan(statisticData.getBackScanPayAmount()); // 扫码收款
                sourceIncome.setSelfScan(statisticData.getMainScanPayAmount()); // 二维码收款
                sourceIncome.setOwed(statisticData.getCreditPayAmount()); // 挂账
                sourceIncome.setBalance(statisticData.getMemberPayAmount()); // 余额支付
                sourceIncome.setRecharge(statisticData.getRechargeAmount()); // 充值
                // other 无对应字段设为0
                sourceIncome.setOther(NumberUtil.add(sourceIncome.getBarScan(), sourceIncome.getSelfScan(), sourceIncome.getOwed(), sourceIncome.getBalance(), sourceIncome.getRecharge()));
                dayReportPrintDTO.setSourceIncome(sourceIncome);

                // 4. 填充 实收统计 actualIncome (和来源一致，按业务需求调整)
                DayReportPrintDTO.Income actualIncome = new DayReportPrintDTO.Income();
                actualIncome.setCash(statisticData.getCashPayAmount());
                actualIncome.setWechat(statisticData.getWechatPayAmount());
                actualIncome.setAlipay(statisticData.getAlipayPayAmount());
                actualIncome.setBarScan(statisticData.getBackScanPayAmount());
                actualIncome.setSelfScan(statisticData.getMainScanPayAmount());
                actualIncome.setOwed(statisticData.getCreditPayAmount());
                actualIncome.setBalance(statisticData.getMemberPayAmount());
                actualIncome.setRecharge(statisticData.getRechargeAmount());
                actualIncome.setMeituan(statisticData.getMeituanGroup());
                actualIncome.setDouyin(statisticData.getDouyinGroup());
                actualIncome.setOther(NumberUtil.add(actualIncome.getBarScan(), actualIncome.getSelfScan(), actualIncome.getOwed(), actualIncome.getBalance(), actualIncome.getRecharge()));
                dayReportPrintDTO.setActualIncome(actualIncome);

                // 5. 填充 优惠统计 DiscountSts
                DayReportPrintDTO.DiscountSts discountSts = new DayReportPrintDTO.DiscountSts();
                discountSts.setNewConsumerDiscount(statisticData.getNewCustomerDiscountAmount()); // 新客立减
                discountSts.setFreeCashAmount(statisticData.getBackDiscountAmount()); // 霸王餐
                discountSts.setFullMinusAmount(statisticData.getFullDiscountAmount()); // 满减
                discountSts.setCouponAmount(statisticData.getCouponDiscountAmount()); // 优惠券
                discountSts.setMemberDiscount(statisticData.getMemberDiscountAmount()); // 会员折扣
                discountSts.setPointsDiscountAmount(statisticData.getPointDiscountAmount()); // 积分抵扣
                discountSts.setOrderDiscount(statisticData.getOrderPriceDiscountAmount()); // 订单改价
                dayReportPrintDTO.setDiscountSta(discountSts);
                // 加入结果集
                dayReportMap.put("date", localDate);
                dayReportMap.put("data", dayReportPrintDTO);
                list.add(dayReportMap);
            }
        }
        return list;
    }

    private List<Map<String, Object>> buildDaySettlePrint(SaleSummaryCountParam param) {
        List<Map<String, Object>> map = new ArrayList<>();
        if (param.getBeginDate() != null && param.getEndDate() != null) {
            // 计算相差天数
            long days = java.time.temporal.ChronoUnit.DAYS.between(param.getBeginDate(), param.getEndDate());
            List<LocalDate> dateList = LongStream.rangeClosed(0, days)
                    .mapToObj(param.getBeginDate()::plusDays)
                    .toList();
            for (LocalDate localDate : dateList) {
                Map<String, Object> dayReportMap = new HashMap<>();
                DaySettlePrintDTO printDTO = new DaySettlePrintDTO();
                FinanceStsQueryParam param1 = new FinanceStsQueryParam();
                param1.setQueryDate(localDate);
                param1.setShopId(param.getShopId());

                ShopOrderStatistic data = getStatisticData(param1);
                printDTO.setOperator(param.getOperator());
                // ===================== 1.基础信息 =====================
                printDTO.setStatisticsTime(localDate.toString());
                // ===================== 2.营业统计 BusinessSts =====================
                DaySettlePrintDTO.BusinessSts business = new DaySettlePrintDTO.BusinessSts();
                business.setOriginAmount(data.getOriginAmount());                // 订单原价总额
                business.setTurnover(NumberUtil.add(data.getPayAmount(), data.getRechargeAmount()));                       // 营业额=实付金额
                business.setDiscountAmount(data.getDiscountAmount());            // 优惠总金额
                business.setOrderCount(data.getOrderCount());                   // 订单总数
                business.setRefundOrderCount(data.getRefundCount());
                business.setRefundAmount(data.getRefundAmount());               // 退款金额
                business.setCash(data.getCashPayAmount());                      // 现金收款

                printDTO.setTurnover(business);

                // ===================== 3.收款构成 Income =====================
                DaySettlePrintDTO.Income income = new DaySettlePrintDTO.Income();
                income.setCash(data.getCashPayAmount());                        // 现金
                income.setWechat(data.getWechatPayAmount());                    // 微信
                income.setAlipay(data.getAlipayPayAmount());                    // 支付宝
                // 团购 = 美团 + 抖音
                income.setGroup(NumberUtil.add(data.getMeituanGroup(), data.getDouyinGroup()));
                printDTO.setIncome(income);

                // ===================== 4.优惠统计 DiscountSts =====================
                DaySettlePrintDTO.DiscountSts discount = new DaySettlePrintDTO.DiscountSts();
                discount.setNewConsumerDiscount(data.getNewCustomerDiscountAmount());    // 新客立减
                discount.setFreeCashAmount(data.getBackDiscountAmount());                // 霸王餐
                discount.setFullMinusAmount(data.getFullDiscountAmount());              // 满减
                discount.setCouponAmount(data.getCouponDiscountAmount());               // 优惠券
                discount.setMemberDiscount(data.getMemberDiscountAmount());             // 会员折扣
                discount.setPointsDiscountAmount(data.getPointDiscountAmount());        // 积分抵扣
                discount.setOrderDiscount(data.getOrderPriceDiscountAmount());          // 订单改价
                printDTO.setDiscountSta(discount);

                // ===================== 5.敏感操作记录 OperationRecord =====================
                List<DaySettlePrintDTO.OperationRecord> records = new ArrayList<>();
                // 退单
                records.add(new DaySettlePrintDTO.OperationRecord()
                        .setOperation("退单")
                        .setCount(data.getRefundAllNum() == null ? 0L : data.getRefundAllNum())
                        .setAmount(data.getRefundAllAmount()));
                // 退菜
                records.add(new DaySettlePrintDTO.OperationRecord()
                        .setOperation("退菜")
                        .setCount(data.getRefundItemNum() == null ? 0L : data.getRefundItemNum())
                        .setAmount(data.getRefundItemAmount()));
                // 折扣
                records.add(new DaySettlePrintDTO.OperationRecord()
                        .setOperation("折扣")
                        .setCount(data.getDiscountOrderNum() == null ? 0L : data.getDiscountOrderNum())
                        .setAmount(data.getDiscountOrderAmount()));
                // 未结款
                records.add(new DaySettlePrintDTO.OperationRecord()
                        .setOperation("未结款")
                        .setCount(data.getUnpaidOrderNum() == null ? 0L : data.getUnpaidOrderNum())
                        .setAmount(data.getUnpaidOrderAmount()));
                printDTO.setOperationRecords(records);

                //加入结果
                dayReportMap.put("date", localDate);
                dayReportMap.put("data", printDTO);
                map.add(dayReportMap);
            }
        }
        return map;
    }


    /**
     * 计算平均值：金额 / 数量，空值安全处理
     */
    private BigDecimal calcAvg(BigDecimal amount, Long count) {
        if (amount == null || count == null || count == 0) {
            return BigDecimal.ZERO;
        }
        return amount.divide(new BigDecimal(count), 2, RoundingMode.HALF_UP);
    }

    private ShopOrderStatistic getStatisticData(FinanceStsQueryParam param) {
        LocalDate today = LocalDate.now();
        if (param.getQueryDate().equals(today)) {
            return orderStatisticService.getRealTimeDataByDay(param.getShopId(), param.getQueryDate());
        }
        return orderStatisticService.getStatSingleDate(param.getShopId(), param.getQueryDate());
    }
}
