package com.bonus.canteen.core.order.business;

import cn.hutool.core.collection.CollUtil;
import com.bonus.canteen.core.account.constants.AccTradeTypeEnum;
import com.bonus.canteen.core.account.constants.AccWalletTypeEnum;
import com.bonus.canteen.core.account.domain.AccWalletInfo;
import com.bonus.canteen.core.account.domain.WalletUpdateDTO;
import com.bonus.canteen.core.account.service.IAccWalletInfoService;
import com.bonus.canteen.core.common.utils.MqUtil;
import com.bonus.canteen.core.common.utils.ObjectUtils;
import com.bonus.canteen.core.cook.domain.CookRecipeDishes;
import com.bonus.canteen.core.cook.service.ICookRecipeDishesService;
import com.bonus.canteen.core.order.constants.OrderDetailTypeEnum;
import com.bonus.canteen.core.order.constants.OrderStateEnum;
import com.bonus.canteen.core.order.domain.OrderDetail;
import com.bonus.canteen.core.order.domain.OrderInfo;
import com.bonus.canteen.core.order.domain.OrderPayDTO;
import com.bonus.canteen.core.order.domain.OrderPayResultDTO;
import com.bonus.canteen.core.order.mapper.OrderInfoMapper;
import com.bonus.canteen.core.order.service.IOrderDetailService;
import com.bonus.canteen.core.pay.constants.PayChannelEnum;
import com.bonus.canteen.core.pay.constants.PayStateEnum;
import com.bonus.canteen.core.pay.constants.PayTypeEnum;
import com.bonus.canteen.core.user.domain.DeviceMqPersonalUpdateMessageDTO;
import com.bonus.common.core.exception.ServiceException;
import com.bonus.common.houqin.mq.constant.LeMqConstant;
import com.bonus.common.houqin.utils.JacksonUtil;
import com.bonus.common.security.utils.SecurityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

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

@Component
public class OrderBusiness {
    private static final Logger log = LoggerFactory.getLogger(OrderBusiness.class);

    @Autowired
    private IOrderDetailService orderDetailService;
    @Autowired
    private OrderInfoMapper orderInfoMapper;
    @Autowired
    private ICookRecipeDishesService cookRecipeDishesService;
    @Autowired
    private IAccWalletInfoService accWalletInfoService;

    @Transactional(rollbackFor = Exception.class)
    public List<OrderInfo> orderPlaceHandler(List<OrderInfo> orderInfoList) {
        if(CollUtil.isEmpty(orderInfoList)) {
            throw new ServiceException("订单明细不能为空");
        }
        orderInfoMapper.batchInsertOrderInfo(orderInfoList);
        if(CollUtil.isNotEmpty(orderInfoList.get(0).getOrderDetailList())) {
            orderInfoList.forEach(orderInfo -> {
                List<OrderDetail> orderDetailList = orderInfo.getOrderDetailList();
                orderDetailList.forEach(orderDetail -> orderDetail.setOrderId(orderInfo.getOrderId()));
                orderDetailService.batchInsertOrderDetail(orderDetailList);
                reduceMenuDishSupplyNum(orderDetailList);
            });
        }
        return orderInfoList;
    }

    public void reduceMenuDishSupplyNum(List<OrderDetail> orderDetailList) {
        log.info("订单扣减库存入参：{}", JacksonUtil.writeValueAsString(orderDetailList));
        for(OrderDetail orderDetail : orderDetailList) {
            if(OrderDetailTypeEnum.KEYAMOUNT.getKey().equals(orderDetail.getDetailType())) {
                continue;
            }
            CookRecipeDishes menuRecipeDishes = new CookRecipeDishes();
            ObjectUtils.setAllFieldsToNull(menuRecipeDishes);
            menuRecipeDishes.setRecipeDetailId(orderDetail.getRecipeDetailId());
            menuRecipeDishes.setDishesId(orderDetail.getGoodsId());
            cookRecipeDishesService.reduceMenuRecipeDishesSupplyNum(menuRecipeDishes, orderDetail.getQuantity());
        }
    }

    public void addMenuDishSupplyNum(List<OrderDetail> orderDetailList) {
        log.info("订单新增库存入参：{}", JacksonUtil.writeValueAsString(orderDetailList));
        for(OrderDetail orderDetail : orderDetailList) {
            if(OrderDetailTypeEnum.KEYAMOUNT.getKey().equals(orderDetail.getDetailType())) {
                continue;
            }
            CookRecipeDishes menuRecipeDishes = new CookRecipeDishes();
            ObjectUtils.setAllFieldsToNull(menuRecipeDishes);
            menuRecipeDishes.setRecipeDetailId(orderDetail.getRecipeDetailId());
            menuRecipeDishes.setDishesId(orderDetail.getGoodsId());
            cookRecipeDishesService.addMenuRecipeDishesSupplyNum(menuRecipeDishes, orderDetail.getQuantity());
        }
    }

    @Transactional(rollbackFor = Exception.class)
    public void updateOrderPayFailed(List<OrderInfo> orderInfoList) {
        OrderPayResultDTO orderPayResultDTO = new OrderPayResultDTO();
        orderPayResultDTO.setOrderIdList(orderInfoList.stream().map(OrderInfo::getOrderId).collect(Collectors.toList()));
        orderPayResultDTO.setPayState(PayStateEnum.PAY_FAIL.getKey());
        orderPayResultDTO.setUpdateBy(SecurityUtils.getUsername());
        orderPayResultDTO.setPayTime(LocalDateTime.now());
        orderPayResultDTO.setUpdateTime(LocalDateTime.now());
        orderInfoMapper.updateOrderPayResult(orderPayResultDTO);
    }

    @Transactional(rollbackFor = Exception.class)
    public void orderPay(List<OrderInfo> orderInfoList) {
        OrderPayDTO orderPayDTO = buildOrderPayDTO(orderInfoList);
        try {
            List<AccWalletInfo> walletInfoList = accWalletInfoService.selectAccWalletInfoByUserId(orderPayDTO.getUserId());
            if (CollUtil.isEmpty(walletInfoList)) {
                throw new ServiceException("钱包不存在");
            }

            BigDecimal subsidyWalletBal = getWalletBalance(walletInfoList, AccWalletTypeEnum.SUBSIDY);
            BigDecimal personalWalletBal = getWalletBalance(walletInfoList, AccWalletTypeEnum.WALLET);
            BigDecimal totalWalletBal = subsidyWalletBal.add(personalWalletBal);

            if (totalWalletBal.compareTo(orderPayDTO.getOrderTotalAmount()) < 0) {
                throw new ServiceException("钱包余额不足");
            }
            for(OrderInfo orderInfo : orderInfoList) {
                deductFromWallets(orderInfo, subsidyWalletBal);
            }
        } catch (ServiceException ex) {
            log.error("订单支付异常", ex);
            throw new ServiceException(ex.getMessage());
        } catch (Exception ex) {
            log.error("订单支付异常", ex);
            throw new ServiceException("支付失败");
        }
        OrderPayResultDTO orderPayResultDTO = new OrderPayResultDTO();
        orderPayResultDTO.setOrderIdList(orderInfoList.stream().map(OrderInfo::getOrderId).collect(Collectors.toList()));
        orderPayResultDTO.setPayState(PayStateEnum.PAY_SUCC.getKey());
        orderPayResultDTO.setUpdateBy(SecurityUtils.getUsername());
        orderPayResultDTO.setOrderState(OrderStateEnum.PLACE.getKey());
        orderPayResultDTO.setPayTime(LocalDateTime.now());
        orderPayResultDTO.setUpdateTime(LocalDateTime.now());
        orderInfoMapper.updateOrderPayResult(orderPayResultDTO);
        try {
            DeviceMqPersonalUpdateMessageDTO bean = new DeviceMqPersonalUpdateMessageDTO().setUpdatePerson(Math.toIntExact(orderInfoList.get(0).getUserId()),"update");
            String jsonString = JacksonUtil.writeValueAsString(bean);
            log.info("账户变动发送mq内容：{}", jsonString);
            MqUtil.pushToTenantAllDevice(bean, LeMqConstant.Topic.DEVICE_UPDATE_PERSONAL_CONFIG_V4);
        } catch (Exception e) {
            log.error("发送MQ消息失败", e);
        }
    }

    private OrderPayDTO buildOrderPayDTO(List<OrderInfo> orderInfoList) {
        OrderPayDTO orderPayDTO = new OrderPayDTO();
        BigDecimal totalAmount = orderInfoList.stream()
                .map(OrderInfo::getRealAmount)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        orderPayDTO.setOrderTotalAmount(totalAmount);
        orderPayDTO.setUserId(orderInfoList.get(0).getUserId());
        orderPayDTO.setOrderInfoList(orderInfoList);
        return orderPayDTO;
    }

    private void deductFromWallets(OrderInfo orderInfo, BigDecimal subsidyWalletBal) {
        Long orderNo = orderInfo.getOrderId();
        String deviceSn = orderInfo.getDeviceSn();
        if (orderInfo.getRealAmount().compareTo(subsidyWalletBal) <= 0) {
            deductWalletBalance(orderInfo.getRealAmount(), orderInfo.getUserId(), AccWalletTypeEnum.SUBSIDY, orderNo, deviceSn);
        } else if (subsidyWalletBal.compareTo(BigDecimal.ZERO) > 0) {
            deductWalletBalance(subsidyWalletBal, orderInfo.getUserId(), AccWalletTypeEnum.SUBSIDY, orderNo, deviceSn);
            BigDecimal remainAmount = orderInfo.getRealAmount().subtract(subsidyWalletBal);
            deductWalletBalance(remainAmount, orderInfo.getUserId(), AccWalletTypeEnum.WALLET, orderNo, deviceSn);
        } else {
            deductWalletBalance(orderInfo.getRealAmount(), orderInfo.getUserId(), AccWalletTypeEnum.WALLET, orderNo, deviceSn);
        }
    }

    private void deductWalletBalance(BigDecimal amount,
                                     Long userId,
                                     AccWalletTypeEnum walletTypeEnum,
                                     Long orderNo,
                                     String deviceSn) {
        WalletUpdateDTO walletUpdateDTO = new WalletUpdateDTO();
        walletUpdateDTO.setAmount(amount);
        walletUpdateDTO.setUserId(userId);
        walletUpdateDTO.setAccWalletTypeEnum(walletTypeEnum);
        walletUpdateDTO.setAccTradeTypeEnum(AccTradeTypeEnum.CONSUME);
        walletUpdateDTO.setPayChannelEnum(PayChannelEnum.ACC);
        walletUpdateDTO.setPayTypeEnum(PayTypeEnum.MEAL_CARD);
        walletUpdateDTO.setOrderNo(orderNo);
        walletUpdateDTO.setDeviceSn(deviceSn);
        walletUpdateDTO.setBatch(true);
        accWalletInfoService.clearAccWalletBalance(walletUpdateDTO);
    }

    private BigDecimal getWalletBalance(List<AccWalletInfo> walletInfoList, AccWalletTypeEnum walletTypeEnum) {
        return walletInfoList.stream()
                .filter(walletInfo -> walletTypeEnum.getKey().equals(walletInfo.getWalletType()))
                .map(AccWalletInfo::getWalletBal)
                .findFirst()
                .orElse(BigDecimal.ZERO);
    }
}
