package com.czg.service.order.print;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
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.dto.PrintOrderDetailDTO;
import com.czg.account.entity.*;
import com.czg.account.service.*;
import com.czg.config.RedisCst;
import com.czg.order.entity.OrderDetail;
import com.czg.order.entity.OrderInfo;
import com.czg.order.service.OrderDetailService;
import com.czg.order.service.OrderInfoService;
import com.czg.order.service.PrintMachineLogService;
import com.czg.product.entity.ProdSku;
import com.czg.product.entity.Product;
import com.czg.product.service.ProdSkuService;
import com.czg.product.service.ProductService;
import com.czg.resp.CzgResult;
import com.czg.service.RedisService;
import com.czg.service.order.enums.OrderStatusEnums;
import com.czg.system.dto.SysParamsDTO;
import com.czg.system.service.SysParamsService;
import com.mybatisflex.core.query.QueryWrapper;
import jakarta.annotation.Resource;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author Administrator
 */
@Slf4j
@ToString
public abstract class PrinterHandler {
    @Setter
    protected PrinterHandler nextPrinter;
    protected String printerBrand;
    // 创建 ThreadLocal 变量
    private static final ThreadLocal<String> ERR_MSG = ThreadLocal.withInitial(() -> "");


    @Resource
    protected OrderDetailService orderDetailService;
    @Resource
    protected OrderInfoService orderInfoService;
    @Resource
    protected RedisService redisService;
    @Resource
    protected PrintMachineLogService printMachineLogService;

    @DubboReference
    protected SysParamsService sysParamsService;
    @DubboReference
    protected ShopUserService shopUserService;
    @DubboReference
    protected CallQueueService callQueueService;
    @DubboReference
    protected CallTableService callTableService;
    @DubboReference
    protected ShopInfoService shopInfoService;
    @DubboReference
    protected ProductService productService;
    @DubboReference
    protected ProdSkuService prodSkuService;
    @DubboReference
    private PrintMachineService printMachineService;
    @DubboReference
    private HandoverRecordRpcService handoverRecordService;

    @Getter
    public enum PrintTypeEnum {
        HANDOVER("交班", "handover"),
        ORDER("订单", "order"), ONE("菜品", "one"), CALL("叫号", "call"), ONE_AND_ORDER("菜品和结算单同时打印", "oneAndOrder"), PRE_ORDER("预结算单", "preOrder");
        private final String name;
        private final String code;

        PrintTypeEnum(String name, String code) {
            this.name = name;
            this.code = code;
        }
    }

    @Data
    @Accessors(chain = true)
    public static class PrintDetailInfo {
        private boolean isPrint;
        //        private boolean isReturn;
        private long orderId;
        private long detailId;
        private BigDecimal printNum;
        private BigDecimal printReturnNum;
    }

    @Data
    @Accessors(chain = true)
    public static class PrintInfoDTO {
        private long count;
        // 打印标题 退款单/结算单
        private String printTitle;
        private String shopName;
        private String printType;
        private String pickupNum;
        private String orderNo;
        private String tradeDate;
        private String operator;
        private String payAmount;
        private String originalAmount;
        private String balance;
        private String payType;
        private String integral;
        private String remark;
        private String outNumber;
        private String discountAmount;
        private String discountRadio;
        private String orderNum;
        // 是否退款单
        private boolean isReturn;

    }

    public PrinterHandler(String printerBrand) {
        this.printerBrand = printerBrand;
    }

    /**
     * 获取可用打印机
     *
     * @param shopId      店铺id
     * @param subType     打印类型（分类）label标签 cash小票 kitchen出品
     * @param printMethod 打印方式 all-全部打印 normal-仅打印结账单「前台」one-仅打印制作单「厨房」
     * @param printType   打印类型，JSON数组 refund-确认退款单 handover-交班单 queue-排队取号
     * @return 打印机列表
     */
    private List<PrintMachine> getPrintMachine(Long shopId, String subType, String printMethod, String printType) {
        QueryWrapper wrapper = new QueryWrapper()
                .eq(PrintMachine::getStatus, 1)
                .eq(PrintMachine::getShopId, shopId)
                .eq(PrintMachine::getSubType, subType)
                .eq(PrintMachine::getConnectionType, "网络");

        if (StrUtil.isNotEmpty(printMethod)) {
            List<String> arrayList = switch (printMethod) {
                case "all" ->Arrays.asList("one", "normal", "all");
                case "one" -> Arrays.asList("one", "all");
                case "normal" -> Arrays.asList("normal", "all");
                default -> new ArrayList<>();
            };
            if (!arrayList.isEmpty()) {
                wrapper.in(PrintMachine::getPrintMethod, arrayList);
            }
        }
        if ("callTicket".equals(printType)) {
            printType = "queue";
        }
        if (StrUtil.isNotEmpty(printType)) {
            wrapper.like(PrintMachine::getPrintType, printType);
        }
        List<PrintMachine> list = printMachineService.list(wrapper);
//        for (PrintMachine item : list) {
//            //实际打印以传递的参数为准
//            item.setPrintMethod(printMethod);
//        }
        if (list.isEmpty()) {
            log.error("店铺未配置打印机，店铺id: {}", shopId);
            return list;
        }

        log.info("打印机列表: {}", list);
        return list;
    }

    /**
     * 处理打印
     * @param data 传递的数据
     * @param printTypeEnum order returnOrder preOrder one call handover
     */
    public void handler(String data, PrintTypeEnum printTypeEnum) {
        Long shopId;
        String printMethod = "";
        String printType = "";
        Object obj;
        OrderInfo orderInfo;
        switch (printTypeEnum) {
            case PrintTypeEnum.HANDOVER:
                HandoverRecordDTO record = handoverRecordService.getHandoverRecordById(Long.parseLong(data));
                if (record == null) {
                    throw new RuntimeException("交班票打印失败，交班记录不存在");
                }
                obj = record;
                shopId = record.getShopId();
                printType = "handover";
                break;
            case PrintTypeEnum.ORDER:
                orderInfo = orderInfoService.getById(data);
                if (orderInfo == null) {
                    throw new RuntimeException("订单打印失败，订单不存在");
                }
                obj = orderInfo;
                shopId = orderInfo.getShopId();
                printMethod = "normal";
                break;
            case PrintTypeEnum.ONE:
                orderInfo = orderInfoService.getById(data);
                if (orderInfo == null) {
                    throw new RuntimeException("订单打印失败，订单不存在");
                }
                shopId = orderInfo.getShopId();
                obj = orderInfo;
                printMethod = "one";
                break;
            case PrintTypeEnum.PRE_ORDER:
                orderInfo = orderInfoService.getById(data);
                if (orderInfo == null) {
                    throw new RuntimeException("订单打印失败，订单不存在");
                }
                shopId = orderInfo.getShopId();
                obj = orderInfo;
                printMethod = "normal";
                break;
            case PrintTypeEnum.ONE_AND_ORDER:
                orderInfo = orderInfoService.getById(data);
                if (orderInfo == null) {
                    throw new RuntimeException("订单打印失败，订单不存在");
                }
                shopId = orderInfo.getShopId();
                obj = orderInfo;
                printMethod = "all";
                break;

            case PrintTypeEnum.CALL:
                CallQueue queue = callQueueService.getById(data);
                if (queue == null) {
                    throw new RuntimeException("叫号信息不存在: " + data);
                }
                shopId = queue.getShopId();
                obj = queue;
                printType = "queue";
                break;
            default:
                throw new RuntimeException("未知打印类型");

        }
        if (shopId == null) {
            throw new RuntimeException("店铺不存在, id:" + shopId);
        }

        Object finalObj = obj;
        getPrintMachine(shopId, "cash", printMethod, printType).forEach(machine -> {
            handleRequest(machine, finalObj, printTypeEnum);
//                printPlaceTicket(isReturn, machine, orderInfo, shopInfo);
        });
    }

    private void handleRequest(PrintMachine machine, Object data, PrintTypeEnum printTypeEnum) {

        if (canHandleRequest(machine.getContentType(), machine.getConnectionType())) {
            log.info("打印机: {}, 传递信息: {}", machine.getName(), data);
            print(machine, data, printTypeEnum);
        } else if (nextPrinter != null) {
            log.info("当前打印机无法处理: {}，将请求传递给下一个打印机:{}...", this.printerBrand, nextPrinter.printerBrand);
            nextPrinter.handleRequest(machine, data, printTypeEnum);
        } else {
            log.warn("未找到匹配打印机");
        }
    }

    boolean canHandleRequest(String currentBrand, String connectType) {
        log.info("handle判断是否可处理: {}, 连接类型: {}, handler类型: {}", currentBrand, connectType, printerBrand);
        if (StrUtil.isBlank(printerBrand)) {
            throw new RuntimeException("打印机品牌未赋值");
        }
        return printerBrand.equals(currentBrand) && "网络".equals(connectType);
    }

    protected List<OrderDetail> getCanPrintOrderDetails(boolean partPrint, Long orderId, List<OrderDetail> tbOrderDetailList, List<?> categoryIds) {
        List<Long> productIds = tbOrderDetailList.stream().map(OrderDetail::getProductId).collect(Collectors.toList());

        Map<Long, Boolean> canPrintProMap = categoryIds.isEmpty() ? new HashMap<>() :
                productService.list(new QueryWrapper().in(Product::getCategoryId, categoryIds).in(Product::getId, productIds))
                        .stream().collect(Collectors.toMap(Product::getId, i -> true));

        ArrayList<OrderDetail> orderDetails = new ArrayList<>();
        tbOrderDetailList.forEach(item -> {
            Object o = redisService.get(RedisCst.getPrintOrderDetailKey(orderId, item.getId()));

            if (item.getIsPrint() != null && item.getIsPrint() == 1 && (canPrintProMap.get(item.getProductId()) != null || !partPrint)) {
                item.setReturnNum(item.getReturnNum() == null ? BigDecimal.ZERO : item.getReturnNum());
                PrintDetailInfo printDetailInfo = o != null ? JSONObject.parseObject((String) o, PrintDetailInfo.class) : null;
                if (printDetailInfo != null) {
                    if (item.getNum().compareTo(BigDecimal.ZERO) != 0 && item.getNum().compareTo(printDetailInfo.getPrintNum()) <= 0) {
                        log.info("此菜品已打印, {} {} {}", item.getProductName(), item.getSkuName(), printDetailInfo);
                    }

                    if (item.getReturnNum().compareTo(BigDecimal.ZERO) != 0 && item.getReturnNum().compareTo(printDetailInfo.getPrintReturnNum()) <= 0) {
                        log.info("此退菜菜品已打印, {} {} {}", item.getProductName(), item.getSkuName(), printDetailInfo);
                    }
                    item.setNum(item.getNum().subtract(printDetailInfo.getPrintNum()));
                    item.setReturnNum(item.getReturnNum().subtract(printDetailInfo.getPrintReturnNum()));
                    orderDetails.add(item);
                }else {
                    orderDetails.add(item);
                }


            } else {
                log.info("此菜品不在打印分类或属于免打, {} {} {}", item.getProductName(), item.getSkuName(), item.getId());
            }
        });

        return orderDetails;
    }


    protected void print(PrintMachine machine, Object data, PrintTypeEnum printTypeEnum) {
        switch (printTypeEnum) {
            case PrintTypeEnum.HANDOVER:
                log.info("准备开始打印交班");
                if (data instanceof HandoverRecordDTO record) {
                    handoverPrint(machine, record);
                }else {
                    throw new RuntimeException("传递数据类型有误");
                }
                break;
            case PrintTypeEnum.ORDER:
                log.info("准备开始打印订单");
                if (data instanceof OrderInfo orderInfo) {
                    List<OrderDetail> orderDetailList = orderDetailService.list(new QueryWrapper().eq(OrderDetail::getOrderId, orderInfo.getId()));
                    onlyFrontDesk(machine, false, orderInfo, orderDetailList);
                }else {
                    throw new RuntimeException("传递数据类型有误");
                }
                break;
            case PrintTypeEnum.PRE_ORDER:
                log.info("准备开始打印预结算订单");
                if (data instanceof OrderInfo orderInfo) {
                    List<OrderDetail> orderDetailList = orderDetailService.list(new QueryWrapper().eq(OrderDetail::getOrderId, orderInfo.getId()));
                    onlyFrontDesk(machine, true, orderInfo, orderDetailList);
                }else {
                    throw new RuntimeException("传递数据类型有误");
                }
                break;
            case PrintTypeEnum.ONE:
                log.info("准备开始打印菜品单");
                if (data instanceof OrderInfo orderInfo) {
                    List<OrderDetail> orderDetailList = orderDetailService.list(new QueryWrapper().eq(OrderDetail::getOrderId, orderInfo.getId()));
                    onlyKitchen(machine, orderInfo, orderDetailList);
                }else {
                    throw new RuntimeException("传递数据类型有误");
                }
                break;
            case PrintTypeEnum.CALL:
                log.info("准备开始打印叫号单");
                if (data instanceof CallQueue queue) {
                    onlyCallNumPrint(machine, queue);
                }else {
                    throw new RuntimeException("传递数据类型有误");
                }
                break;
            case PrintTypeEnum.ONE_AND_ORDER:
                log.info("准备开始打印菜品以及结算单");
                if (data instanceof OrderInfo orderInfo) {
                    List<OrderDetail> orderDetailList = orderDetailService.list(new QueryWrapper().eq(OrderDetail::getOrderId, orderInfo.getId()));
                    switch (machine.getPrintMethod()) {
                        case "all":
                            onlyFrontDesk(machine, false, orderInfo, orderDetailList);
                            onlyKitchen(machine, orderInfo, orderDetailList);
                            break;
                        case "one":
                            log.info("打印机仅打印one");
                            onlyKitchen(machine, orderInfo, orderDetailList);
                            break;
                        case "normal":
                            log.info("打印机仅打印normal");
                            onlyFrontDesk(machine, false, orderInfo, orderDetailList);
                            break;
                        default:
                            throw new RuntimeException("打印方法有误");
                    }
                }else {
                    throw new RuntimeException("传递数据类型有误");
                }

        }

    }

    /**
     * 仅打印制作单「厨房」
     */
    private void onlyKitchen(PrintMachine machine, OrderInfo orderInfo, List<OrderDetail> tbOrderDetailList) {
        // 判断订单是否是先付费或者已结算
        if (!"after-pay".equals(orderInfo.getPayMode()) && (OrderStatusEnums.UNPAID.getCode().equals(orderInfo.getStatus()) || OrderStatusEnums.CANCELLED.getCode().equals(orderInfo.getStatus()))) {
            log.warn("此订单未支付, 订单信息: {}", orderInfo);
//            return;
        }

        List<?> categoryIds = JSONObject.parseObject(StrUtil.emptyToDefault(machine.getCategoryIds(), "[]"), List.class);
        if (StrUtil.isEmpty(machine.getClassifyPrint())) {
            log.error("分类打印是空, classifyPrint: {}", machine.getClassifyPrint());
            return;
        }

        // 已打印详情信息
        Map<Long, OrderDetail> detailMap = tbOrderDetailList.stream().map(item -> BeanUtil.copyProperties(item, OrderDetail.class)).collect(Collectors.toMap(OrderDetail::getId, i -> i));
        tbOrderDetailList = getCanPrintOrderDetails("1".equals(machine.getClassifyPrint()), orderInfo.getId(), tbOrderDetailList, categoryIds);
        tbOrderDetailList.parallelStream().filter(o -> ObjectUtil.defaultIfNull(o.getIsPrint(), 0) == 1).forEach(item -> {
            if (item.getNum().compareTo(BigDecimal.ZERO) <= 0 && item.getReturnNum().compareTo(BigDecimal.ZERO) <= 0) {
                return;
            }
            log.info("开始打印菜品，商品名:{}", item.getProductName());
            Integer isWaitCall = ObjectUtil.defaultIfNull(item.getIsWaitCall(), 0);
            if (isWaitCall == 1) {
                if (!item.getProductName().contains("【等叫】")) {
                    item.setProductName("【等叫】" + item.getProductName());
                }
            }
            Integer isTemporary = ObjectUtil.defaultIfNull(item.getIsTemporary(), 0);
            if (isTemporary == 1) {
                item.setProductId(0L);
                item.setSkuId(0L);
                if (!item.getProductName().contains("【临】")) {
                    item.setProductName("【临】" + item.getProductName());
                }
            }
            boolean isGift = item.getIsGift() != null && item.getIsGift() == 1;
            if (isGift && !item.getProductName().contains("【赠】")) {
                item.setProductName("【赠】" + item.getProductName());
            }
            // 台位费不打印
            if (item.getProductId().equals(-999L)) {
                log.info("台位费商品，不打印");
                return;
            }
//            ProdSku sku = prodSkuService.getById(item.getSkuId());
//            if (isTemporary == 0 && sku == null) {
//                log.error("商品不存在, id: {}", item.getSkuId());
//                return;
//            } else if (isTemporary == 1) {
//                sku = new ProdSku();
//            }


//            String remark = StrUtil.isNotBlank(sku.getSpecInfo()) ? sku.getSpecInfo() : "";
//            item.setRemark(remark);
            if (item.getReturnNum().compareTo(BigDecimal.ZERO) > 0) {
                returnDishesPrint(orderInfo, item, machine);
            }

            if (item.getNum().compareTo(BigDecimal.ZERO) > 0) {
                normalDishesPrint(orderInfo, item, machine);
            }

            // 保存已打印信息
            OrderDetail orderDetail = detailMap.get(item.getId());
            redisService.set(RedisCst.getPrintOrderDetailKey(orderInfo.getId(), item.getId()), JSONObject.toJSONString(new PrintDetailInfo().setPrint(item.getIsPrint() == 1).setDetailId(item.getId())
                    .setPrintNum(orderDetail.getNum()).setPrintReturnNum(orderDetail.getReturnNum())), 3600 * 24);

        });

    }

    /**
     * 仅打印结算单「前台」
     */
    private void onlyFrontDesk(PrintMachine machine, boolean isPre, OrderInfo orderInfo, List<OrderDetail> tbOrderDetailList) {
        // 判断订单是否是先付费或者已结算
        if (OrderStatusEnums.UNPAID.getCode().equals(orderInfo.getStatus()) || OrderStatusEnums.CANCELLED.getCode().equals(orderInfo.getStatus())) {
            log.warn("此订单未支付或已取消, 订单信息: {}", orderInfo);
//            return;
        }

        if (tbOrderDetailList.isEmpty()) {
            log.info("待打印列表为空");
            return;
        }
        List<PrintOrderDetailDTO> detailList = new ArrayList<>();
        tbOrderDetailList.forEach(it -> {
            Integer isTemporary = ObjectUtil.defaultIfNull(it.getIsTemporary(), 0);
            String remark = "";
            if (isTemporary == 0) {
                ProdSku prodSku = prodSkuService.getById(it.getSkuId());
                if (ObjectUtil.isNotEmpty(prodSku) && ObjectUtil.isNotEmpty(prodSku.getSpecInfo())) {
                    remark = prodSku.getSpecInfo();
                }
            }
            Integer isWaitCall = ObjectUtil.defaultIfNull(it.getIsWaitCall(), 0);
            if (isWaitCall == 1) {
                it.setProductName("【等叫】%s".formatted(it.getProductName()));
            }
            if (isTemporary == 1) {
                it.setProductName("【临】%s".formatted(it.getProductName()));
            }
            it.setPackAmount(it.getPackAmount() == null ? BigDecimal.ZERO : it.getPackAmount());
            boolean isGift = it.getIsGift() != null && it.getIsGift() == 1;
            if (isGift) {
                it.setProductName("【赠】%s".formatted(it.getProductName()));
            }
            BigDecimal unitPrice = it.getPrice();
            if (isGift) {
                unitPrice = BigDecimal.ZERO;
            }
            PrintOrderDetailDTO detail = new PrintOrderDetailDTO(it.getProductName(), it.getNum().toString(), unitPrice.stripTrailingZeros().toPlainString(), remark, it.getProGroupInfo());
            detailList.add(detail);

        });
        String balance = "0";

        if ("deposit".equals(orderInfo.getPayType())) {
            ShopUser user = shopUserService.getOne(new QueryWrapper().eq(ShopUser::getShopId, orderInfo.getShopId()).eq(ShopUser::getUserId, orderInfo.getUserId()));
            if (ObjectUtil.isNotEmpty(user) && ObjectUtil.isNotEmpty(user.getAmount())) {
                balance = user.getAmount().toPlainString();
            }
        }

        if (!detailList.isEmpty()) {
            if (isReturn(orderInfo)) {
                returnOrderPrint(orderInfo, machine, balance, tbOrderDetailList);
            } else {
                normalOrderPrint(orderInfo, isPre, machine, balance, tbOrderDetailList);
            }
        }
    }

    /**
     * 打印排队小票
     */
    private void onlyCallNumPrint(PrintMachine machine, CallQueue queue) {
        if (queue == null) {
            log.warn("叫号记录不存在");
            return;
        }

        CzgResult<SysParamsDTO> paramsByCode = sysParamsService.getParamsByCode("call_page_url");
        SysParamsDTO params = paramsByCode.getData();
        String callUrl = null;
        if (params != null && StrUtil.isNotBlank(params.getParamValue())) {
            callUrl = params.getParamValue();
        }

        CallTable tbCallTable = callTableService.getById(queue.getCallTableId());
        ShopInfo shopInfo = shopInfoService.getById(queue.getShopId());
        callNumPrint(machine, queue.getCallNum(), shopInfo.getShopName(), tbCallTable.getName(), tbCallTable.getNote(), String.valueOf(callQueueService.count(new QueryWrapper()
                        .eq(CallQueue::getShopId, queue.getShopId())
                        .eq(CallQueue::getCallTableId, queue.getCallTableId())
                        .lt(CallQueue::getId, queue.getId())
                        .in(CallQueue::getState, 0, 1))), callUrl == null ? "未配置页面" : StrUtil.format(callUrl, queue.getShopId(), queue.getId()), queue.getCreateTime(),
                StrUtil.format("过号顺延{}桌 {}桌后需重新排号 谢谢理解！", tbCallTable.getPostponeNum(), tbCallTable.getPostponeNum()));
    }

    /**
     * 获取取餐号
     */
    protected static String getPickupNum(OrderInfo orderInfo) {
        return StrUtil.isBlank(orderInfo.getTableName()) ? orderInfo.getTakeCode() : orderInfo.getTableName();
    }

    /**
     * 根据订单信息判断是否是退单
     *
     * @param orderInfo 顶动感信息
     * @return 是否退款
     */
    protected static boolean isReturn(OrderInfo orderInfo) {
        return ArrayUtil.contains(new String[]{"refunding", "part-refund", "refund"}, orderInfo.getStatus());
    }


    protected abstract void normalDishesPrint(OrderInfo orderInfo, OrderDetail orderDetail, PrintMachine machine);

    protected abstract void returnDishesPrint(OrderInfo orderInfo, OrderDetail orderDetail, PrintMachine machine);

    protected abstract void returnOrderPrint(OrderInfo orderInfo, PrintMachine machine, String balance, List<OrderDetail> detailList);

    protected abstract void normalOrderPrint(OrderInfo orderInfo, boolean isPre, PrintMachine machine, String balance, List<OrderDetail> detailList);

    protected abstract void callNumPrint(PrintMachine machine, String callNum, String shopName, String tableName, String tableNote, String preNum,
                                         String codeUrl, LocalDateTime takeTime, String shopNote);

    protected abstract void handoverPrint(PrintMachine machine, HandoverRecordDTO record);

}
