package com.czg.service.order.print;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.czg.account.dto.HandoverRecordDTO;
import com.czg.account.entity.PrintMachine;
import com.czg.account.entity.ShopInfo;
import com.czg.account.service.PrintMachineService;
import com.czg.account.service.ShopInfoService;
import com.czg.config.RabbitPublisher;
import com.czg.config.RedisCst;
import com.czg.exception.CzgException;
import com.czg.market.service.OrderInfoService;
import com.czg.order.entity.OrderDetail;
import com.czg.order.entity.OrderInfo;
import com.czg.order.service.OrderDetailService;
import com.czg.print.*;
import com.czg.product.entity.Product;
import com.czg.product.service.ProductService;
import com.czg.service.RedisService;
import com.czg.service.order.enums.OrderStatusEnums;
import com.czg.utils.FunUtils;
import com.mybatisflex.core.query.QueryWrapper;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.beans.BeanUtils;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;

import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;


/**
 * 打印机责任链初始化
 *
 * @author Administrator
 */
@Slf4j
@Component
public class PrintConfig implements ApplicationRunner {

    @Resource
    private ApplicationContext applicationContext;
    @Resource
    protected OrderDetailService orderDetailService;
    @Resource
    protected OrderInfoService orderInfoService;
    @DubboReference
    private PrintMachineService printMachineService;
    @DubboReference
    protected ShopInfoService shopInfoService;
    @DubboReference
    protected ProductService productService;
    @Resource
    protected RedisService redisService;
    @Resource
    private RabbitPublisher rabbitPublisher;

    public static final Map<String, PrinterHandler> PRINTER_MAP = new ConcurrentHashMap<>();

    @Override
    public void run(ApplicationArguments args) throws Exception {

        Map<String, PrinterHandler> beans = applicationContext.getBeansOfType(PrinterHandler.class);

        for (Map.Entry<String, PrinterHandler> entry : beans.entrySet()) {
            PrinterHandler handler = entry.getValue();
            String brand = handler.printerBrand;
            PRINTER_MAP.put(brand, handler);
        }
        log.info("已注册打印机品牌：{}", PRINTER_MAP.keySet());
    }


    /**
     * 获取打印机
     */
    public PrinterHandler getPrinter(String key) {
        PrinterHandler printer = PRINTER_MAP.get(key);
        if (printer == null) {
            throw new CzgException("未找到打印机: " + key);
        }
        return printer;
    }

    /**
     * 处理订单打印
     *
     * @param data          传递的数据
     * @param printTypeEnum GUEST_ORDER ORDER PRE_ORDER ONLY_KITCHEN ALL_KITCHEN
     */
    public void orderHandler(Long data, PrinterHandler.PrintTypeEnum printTypeEnum, Integer placeNum) {
        OrderInfo orderInfo = orderInfoService.getById(data);
        if (orderInfo == null) {
            throw new RuntimeException("订单打印失败，订单不存在");
        }
        List<OrderDetail> orderDetailList = new ArrayList<>();
        orderDetailList = switch (printTypeEnum) {
            case PrinterHandler.PrintTypeEnum.GUEST_ORDER -> {
                orderInfo.setPlaceNum(placeNum);
                yield orderDetailService.list(new QueryWrapper().eq(OrderDetail::getOrderId, orderInfo.getId()).eq(OrderDetail::getPlaceNum, placeNum));
            }
            case PrinterHandler.PrintTypeEnum.ONLY_KITCHEN, PrinterHandler.PrintTypeEnum.ALL_KITCHEN -> {
                QueryWrapper queryWrapper = new QueryWrapper().eq(OrderDetail::getOrderId, orderInfo.getId());
                if ("after-pay".equals(orderInfo.getPayMode())) {
                    queryWrapper.eq(OrderDetail::getPlaceNum, orderInfo.getPlaceNum());
                }
                yield orderDetailService.list(queryWrapper);
            }
            case PrinterHandler.PrintTypeEnum.ORDER, PrinterHandler.PrintTypeEnum.PRE_ORDER ->
                    orderDetailService.list(new QueryWrapper().eq(OrderDetail::getOrderId, orderInfo.getId()));
            default -> orderDetailList;
        };
        sendOrderPrintMsg(orderInfo.getShopId(), orderInfo.getId(), placeNum, printTypeEnum + "", null, "", "");
        for (PrintMachine machine : getPrintMachine(orderInfo.getShopId(), printTypeEnum)) {
            PrinterHandler printer = getPrinter(machine.getBrand());
            switch (printTypeEnum) {
                case PrinterHandler.PrintTypeEnum.GUEST_ORDER:
                    log.info("准备开始打印客看订单");
                    printer.guestOrderPrint(orderInfo, machine, orderDetailList);
                    break;
                case PrinterHandler.PrintTypeEnum.ORDER:
                    log.info("准备开始打印订单");
                    printer.orderPrintBefore(machine, orderInfo, orderDetailList);
                    break;
                case PrinterHandler.PrintTypeEnum.PRE_ORDER:
                    log.info("准备开始打印预结算订单");
                    printer.preOrderPrintBefore(machine, orderInfo, orderDetailList);
                    break;
                case PrinterHandler.PrintTypeEnum.ONLY_KITCHEN:
                    // 判断订单是否是先付费或者已结算
                    if (!"after-pay".equals(orderInfo.getPayMode()) && (OrderStatusEnums.UNPAID.getCode().equals(orderInfo.getStatus()) || OrderStatusEnums.CANCELLED.getCode().equals(orderInfo.getStatus()))) {
                        log.warn("此订单未支付, 订单信息: {}", orderInfo.getId());
                    }
                    List<OrderDetail> tbOrderDetailList = getCanPrintOrderDetails(machine, orderDetailList);
                    if (CollUtil.isEmpty(tbOrderDetailList)) {
                        log.info("此订单无打印菜品, 订单信息: {}", orderInfo.getId());
                        break;
                    }
                    log.info("准备开始打印菜品单");
                    //后厨单菜品
                    boolean isNotice = true;
                    for (OrderDetail item : tbOrderDetailList) {
                        Boolean b = redisService.hasKey(RedisCst.kitchenOnly(orderInfo.getId(), machine.getId(), item.getId()));
                        if (!b) {
                            if (!isNotice) {
                                machine.setVolumeSwitch(0);
                            }
                            isNotice = false;
                            ShopInfo shopInfo = shopInfoService.getById(orderInfo.getShopId());
                            FunUtils.safeRunVoid(() -> printer.onlyKitchenPrint(shopInfo.getIsKitchenCode(), orderInfo, item, machine),
                                    "订单id:{} ,后厨分单打印失败", orderInfo.getId());
                            // 保存已打印信息
                            redisService.set(RedisCst.kitchenOnly(orderInfo.getId(), machine.getId(), item.getId()), "", 20);
                        }
                    }
                    break;
                case PrinterHandler.PrintTypeEnum.ALL_KITCHEN:
                    // 判断订单是否是先付费或者已结算
                    if (!"after-pay".equals(orderInfo.getPayMode()) && (OrderStatusEnums.UNPAID.getCode().equals(orderInfo.getStatus()) || OrderStatusEnums.CANCELLED.getCode().equals(orderInfo.getStatus()))) {
                        log.warn("此订单未支付, 订单信息: {}", orderInfo.getId());
                    }
                    List<OrderDetail> allOrderDetailList = getCanPrintOrderDetails(machine, orderDetailList);
                    if (CollUtil.isEmpty(allOrderDetailList)) {
                        log.info("此订单无打印菜品, 订单信息: {}", orderInfo.getId());
                        break;
                    }
                    log.info("准备开始打印后厨整单");
                    printer.allKitchenPrint(orderInfo, allOrderDetailList, machine);
                    break;
            }
        }
    }

    /**
     * 处理其它打印
     *
     * @param data          传递的数据
     * @param printTypeEnum IN_STOCK DAY_REPORT PRODUCT_REPORT RECHARGE STOCK_CHECK
     */
    public void otherHandler(Long shopId, JSONObject data, JSONArray data1, PrinterHandler.PrintTypeEnum printTypeEnum) {
        if (shopId == null || (Objects.isNull(data) && Objects.isNull(data1))) {
            log.info("otherHandler 打印数据为空, shopId: {}, data: {}", shopId, data);
            return;
        }
        ShopInfo shopInfo = shopInfoService.getById(shopId);
        switch (printTypeEnum) {
            case PrinterHandler.PrintTypeEnum.STOCK:
                getPrintMachine(shopId, printTypeEnum)
                        .forEach(machine -> getPrinter(machine.getBrand())
                                .stockPrint(machine, shopInfo.getShopName(), JSONObject.parseObject(data.toJSONString(), StockPrintDTO.class)));
                break;
            case PrinterHandler.PrintTypeEnum.DAY_REPORT:
                for (PrintMachine machine : getPrintMachine(shopId, printTypeEnum)) {
                    PrinterHandler printer = getPrinter(machine.getBrand());
                    for (Object obj : data1) {
                        JSONObject item = (JSONObject) obj;
                        printer.dayReportPrint(machine, shopInfo.getShopName(), item.getObject("data", DayReportPrintDTO.class));
                    }
                }
                break;
            case PrinterHandler.PrintTypeEnum.DAY_ORDER:
                for (PrintMachine machine : getPrintMachine(shopId, printTypeEnum)) {
                    PrinterHandler printer = getPrinter(machine.getBrand());
                    for (Object obj : data1) {
                        JSONObject item = (JSONObject) obj;
                        printer.daySettlePrint(machine, shopInfo.getShopName(), item.getObject("data", DaySettlePrintDTO.class));
                    }
                }
                break;
            case PrinterHandler.PrintTypeEnum.PRODUCT_REPORT:
                for (PrintMachine machine : getPrintMachine(shopId, printTypeEnum)) {
                    PrinterHandler printer = getPrinter(machine.getBrand());
                    for (Object obj : data1) {
                        JSONObject item = (JSONObject) obj;
                        printer.productReportPrint(machine, shopInfo.getShopName(), item.getObject("data", ProductReportPrintDTO.class));
                    }
                }
                break;
            case PrinterHandler.PrintTypeEnum.RECHARGE:
                getPrintMachine(shopId, printTypeEnum)
                        .forEach(machine -> getPrinter(machine.getBrand())
                                .rechargePrint(machine, shopInfo.getShopName(), JSONObject.parseObject(data.toJSONString(), RechargePrintDTO.class)));
                break;
            case PrinterHandler.PrintTypeEnum.STOCK_CHECK:
                getPrintMachine(shopId, printTypeEnum)
                        .forEach(machine -> getPrinter(machine.getBrand())
                                .stockCheckPrint(machine, shopInfo.getShopName(), JSONObject.parseObject(data.toJSONString(), StockCheckPrintDTO.class)));
                break;
            case PrinterHandler.PrintTypeEnum.HANDOVER:
                log.info("准备开始打印交班");
                getPrintMachine(shopId, printTypeEnum)
                        .forEach(machine -> getPrinter(machine.getBrand()).handoverPrint(machine, shopInfo.getShopName(), JSONObject.parseObject(data.toJSONString(), HandoverRecordDTO.class)));
                break;
            case PrinterHandler.PrintTypeEnum.CALL:
                log.info("准备开始打印叫号单");
                getPrintMachine(shopId, printTypeEnum)
                        .forEach(machine -> getPrinter(machine.getBrand())
                                .callNumPrintBefore(machine, shopInfo.getShopName(), JSONObject.parseObject(data.toJSONString(), CallTablePrintDTO.class)));
                break;
            default:
                throw new CzgException("otherHandler 未知打印类型");
        }
    }

    /**
     * 处理退菜退款订单打印
     */
    public void kitchenRefundAllHandler(String operator, OrderInfo orderInfo, List<OrderDetail> detailList) {
        sendOrderPrintMsg(orderInfo.getShopId(), orderInfo.getId(), null, PrinterHandler.PrintTypeEnum.REFUND_KITCHEN + "", detailList, operator, "");
        //后厨退菜单
        getPrintMachine(orderInfo.getShopId(), PrinterHandler.PrintTypeEnum.REFUND_KITCHEN).forEach(machine -> {
            getPrinter(machine.getBrand()).returnKitchenPrint(operator, orderInfo, detailList, machine);
        });
    }

    /**
     * 处理退菜退款订单打印
     *
     * @param printTitle   退菜单 / 退款单
     * @param operator     操作人
     * @param refundAmount 退款金额
     * @param refundReason 退款原因
     * @param refundType   退款方式
     * @param orderInfo    订单信息
     * @param detailList   退菜/退款 列表
     */
    public void refundOrderHandler(String printTitle, String operator, String refundAmount, String refundReason, String refundType,
                                   OrderInfo orderInfo, List<OrderDetail> detailList) {
        //前台退菜单
        if ("退菜单".equals(printTitle)) {
            sendOrderPrintMsg(orderInfo.getShopId(), orderInfo.getId(), null, PrinterHandler.PrintTypeEnum.RETURN_ORDER + "", detailList, operator, refundAmount);
            getPrintMachine(orderInfo.getShopId(), PrinterHandler.PrintTypeEnum.RETURN_ORDER).forEach(machine ->
                    getPrinter(machine.getBrand()).returnOrderPrint(printTitle, operator, refundAmount, refundReason, refundType, orderInfo, machine, detailList));
        } else if ("退款单".equals(printTitle)) {
            sendOrderPrintMsg(orderInfo.getShopId(), orderInfo.getId(), null, PrinterHandler.PrintTypeEnum.REFUND_ORDER + "", detailList, operator, refundAmount);
            getPrintMachine(orderInfo.getShopId(), PrinterHandler.PrintTypeEnum.REFUND_ORDER).forEach(machine -> {
                PrinterHandler printer = getPrinter(machine.getBrand());
                machine.setVolumeSwitch(0);
                printer.returnOrderPrint(printTitle, operator, refundAmount, refundReason, refundType, orderInfo, machine, detailList);
            });
        } else {
            log.error("未知的前台退款打印类型: {}", printTitle);
        }
    }


    protected List<OrderDetail> getCanPrintOrderDetails(PrintMachine machine, List<OrderDetail> tbOrderDetailList) {
        Set<Long> canPrintProSet = new HashSet<>();
        boolean partPrint = false;
        if ("1".equals(machine.getClassifyPrint())) {
            partPrint = true;

            //617,616,615,614
            List<String> categoryIds = List.of();
            if (StrUtil.isNotBlank(machine.getCategoryIds())) {
                // 按逗号分割，自动去空字符串
                categoryIds = Arrays.stream(machine.getCategoryIds().split(","))
                        .map(String::trim)
                        .filter(StrUtil::isNotBlank)
                        .collect(Collectors.toList());
            }
            if (CollUtil.isEmpty(categoryIds)) {
                return Collections.emptyList();
            }
            List<Long> productIds = tbOrderDetailList.stream().map(OrderDetail::getProductId).collect(Collectors.toList());
            canPrintProSet = productService.list(new QueryWrapper()
                            .in(Product::getCategoryId, categoryIds)
                            .in(Product::getId, productIds))
                    .stream()
                    .map(Product::getId)
                    .collect(Collectors.toSet());
        }
        ArrayList<OrderDetail> orderDetails = new ArrayList<>();
        for (OrderDetail item : tbOrderDetailList) {
            //-999 台位费
            if (item.getNum().compareTo(BigDecimal.ZERO) <= 0 || item.getProductId().equals(-999L)) {
                continue;
            }
            if (item.getIsPrint() != null && item.getIsPrint() == 1 && (!partPrint || canPrintProSet.contains(item.getProductId()))) {
                orderDetails.add(item);
            } else {
                log.info("此菜品不在打印分类或属于免打, {} {} {}", item.getProductName(), item.getSkuName(), item.getId());
            }
        }
        return orderDetails;
    }

    /**
     * 获取可用打印机
     *
     * @param shopId      店铺id
     * @param contentType GUEST_ORDER("客看单"),
     *                    PRE_ORDER("预结算单"),
     *                    ORDER("订单结算单"),
     *                    RETURN_ORDER("退菜单"),
     *                    REFUND_ORDER("退款单"),
     *                    <p>
     *                    ALL_KITCHEN("后厨整单"),
     *                    ONLY_KITCHEN("后厨分单"),
     *                    REFUND_KITCHEN("后厨退菜单"),
     *                    <p>
     *                    HANDOVER("交班单"),
     *                    CALL("排队取号"),
     *                    RECHARGE("储值单"),
     *                    STOCK("出入库单"),
     *                    STOCK_CHECK("盘点单"),
     *                    PRODUCT_REPORT("商品报表"),
     *                    DAY_REPORT("经营日报"),
     *                    DAY_ORDER("日结单");
     * @return 打印机列表
     */
    private List<PrintMachine> getPrintMachine(Long shopId, PrinterHandler.PrintTypeEnum contentType) {
        QueryWrapper wrapper = new QueryWrapper()
                .eq(PrintMachine::getStatus, 1)
                .eq(PrintMachine::getShopId, shopId)
                .eq(PrintMachine::getPrintType, "cash")
                .like(PrintMachine::getPrintContentType, contentType)
                .eq(PrintMachine::getConnectionType, "云打印");
        List<PrintMachine> list = printMachineService.list(wrapper);
        if (list.isEmpty()) {
            log.error("店铺未配置打印机，店铺id: {}", shopId);
            return list;
        }
        log.info("打印机列表: {}", list);
        return list;
    }


    private void sendOrderPrintMsg(Long shopId, Long orderId, Integer placeNum, String printType, List<OrderDetail> detailList, String operator, String amount) {
        FunUtils.safeRunVoid(() -> {
            List<OrderDetailPrintDTO> list = new ArrayList<>();
            if (CollUtil.isNotEmpty(detailList)) {
                list = detailList.stream()
                        .map(detail -> {
                            OrderDetailPrintDTO printDTO = new OrderDetailPrintDTO();
                            // 自动拷贝相同名称的字段
                            BeanUtils.copyProperties(detail, printDTO);
                            return printDTO;
                        }).toList();
            }
            rabbitPublisher.sendOrderPrintLocalMsg(shopId, orderId, placeNum, printType, JSONObject.toJSONString(list), operator, amount);
        }, "订单{}消息发送失败，订单信息: {}", printType, orderId);
    }

}

