package cn.freemud.service;

import cn.freemud.adapter.MessageNoticeAdapter;
import cn.freemud.adapter.OrderCancelReqAdapter;
import cn.freemud.adapter.OrderPrintDtoAdapter;
import cn.freemud.adapter.OrderTaskReqAdapter;
import cn.freemud.base.util.DateUtil;
import cn.freemud.demoTest.enums.TradeBizTypeEnum;
import cn.freemud.entities.dto.delivery.QueryFreightRefundSupportedResponse;
import cn.freemud.entities.dto.delivery.QueryFreightRefundSupportedVo;
import cn.freemud.entities.dto.ecology.SendMessageRequest;
import cn.freemud.entities.dto.store.BusinessInfoDto;
import cn.freemud.entities.vo.OrderDevelopRefundVo;
import cn.freemud.entities.vo.OrderRefundVo;
import cn.freemud.enums.AfterSalesOrderCreateEventEnum;
import cn.freemud.enums.PayRefundStatus;
import cn.freemud.enums.PayStatus;
import cn.freemud.enums.ResponseResult;
import cn.freemud.interceptor.ServiceException;
import cn.freemud.management.entities.dto.request.order.OrderManagerRequest;
import cn.freemud.management.entities.dto.response.pay.PayRefundResponse;
import cn.freemud.management.enums.AfterSalesRefunStateEnum;
import cn.freemud.management.service.adapter.OrderManagerAdapter;
import cn.freemud.management.service.handle.CouponQueryHandle;
import cn.freemud.management.service.handle.OrderCouponHandle;
import cn.freemud.management.service.handle.OrderVerifyHandle;
import cn.freemud.management.service.handle.PaymentHandle;
import cn.freemud.redis.RedisCache;
import cn.freemud.service.impl.OrderQueueService;
import cn.freemud.service.store.StoreManager;
import cn.freemud.service.thirdparty.DeliveryFeiginClient;
import cn.freemud.service.thirdparty.EcologyAdminApplicationClient;
import cn.freemud.service.thirdparty.FMAssistantCloudPrintClient;
import cn.freemud.utils.BeanUtil;
import com.alibaba.fastjson.JSON;
import com.freemud.api.assortment.datamanager.entity.db.AssortmentCloudPrinter;
import com.freemud.api.assortment.datamanager.manager.AssortmentCloudPrinterManager;
import com.freemud.application.sdk.api.constant.ResponseConstant;
import com.freemud.application.sdk.api.log.ErrorLog;
import com.freemud.application.sdk.api.log.LogThreadLocal;
import com.freemud.application.sdk.api.ordercenter.enums.*;
import com.freemud.application.sdk.api.ordercenter.request.OrderCancelReq;
import com.freemud.application.sdk.api.ordercenter.request.OrderExtInfoDto;
import com.freemud.application.sdk.api.ordercenter.response.OrderBaseResp;
import com.freemud.application.sdk.api.ordercenter.response.orderInfo.AfterSalesOrderResp;
import com.freemud.application.sdk.api.ordercenter.response.orderInfo.OrderInfoReqs;
import com.freemud.application.sdk.api.ordercenter.response.orderInfo.OrderSettlementResp;
import com.freemud.application.sdk.api.ordercenter.response.orderInfo.QueryByCodeResponse;
import com.freemud.application.sdk.api.ordercenter.service.OrderSdkService;
import com.freemud.application.sdk.api.structure.request.PushMessageNoticeDto;
import com.freemud.application.sdk.api.structure.service.MessageCenterClient;
import com.freemud.sdk.api.assortment.order.adapter.OrderSdkAdapter;
import com.freemud.sdk.api.assortment.order.constant.OrderRedisKeyConstant;
import com.freemud.sdk.api.assortment.order.entities.OrderRefundConfigEntity;
import com.freemud.sdk.api.assortment.order.enums.AutoOrderConfigTime;
import com.freemud.sdk.api.assortment.order.service.OrderCenterSdkService;
import lombok.RequiredArgsConstructor;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang.BooleanUtils;
import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.MessageFormat;
import java.util.*;
import java.util.stream.Collectors;

import static cn.freemud.constant.OrderRefundConstant.ALLOW_REFUND;

/**
 * @author Clover.z
 * @version 1.0.0
 * @since 1.0.0
 */
@Service
@RequiredArgsConstructor
public class RefundService {

    private final DeliveryFeiginClient deliveryFeiginClient;
    private final OrderSdkService orderSdkService;
    private final RedisCache redisCache;
    private final PaymentHandle paymentHandle;
    private final OrderSdkAdapter orderSdkAdapter;
    private final OrderCenterSdkService orderCenterSdkService;
    private final OrderQueueService orderQueueService;
    private final EcologyAdminApplicationClient ecologyAdminApplicationClient;
    private final MessageCenterClient messageCenterClient;
    private final AssortmentCloudPrinterManager cloudPrinterManager;
    private final FMAssistantCloudPrintClient fmAssistantCloudPrintClient;
    private final OrderManagerAdapter orderManagerAdapter;
    private final OrderVerifyHandle orderVerifyHandle;
    private final StoreManager storeManager;
    private final CouponQueryHandle couponQueryHandle;
    private final OrderCouponHandle orderCouponHandle;

    private OrderInfoReqs preValidRefund(String partnerId, String orderCode, Byte refundMode) {
        // 查询订单信息
        QueryByCodeResponse orderResp = orderSdkService.getOrderInfo(partnerId, orderCode);
        if (null == orderResp || null == orderResp.getResult()) throw new ServiceException("订单信息不存在");
        OrderInfoReqs order = orderResp.getResult();
        if (!PayStatus.HAVE_PAID.getCode().equals(order.getPayState())
                || NewOrderStatus.PENDING_PAYMENT.getIndex().equals(order.getOrderState())) {
            throw new ServiceException("订单未支付，无需退款");
        }
        if (NewOrderStatus.CLOSED.getIndex().equals(order.getOrderState())) {
            throw new ServiceException("订单已取消，无法退款");
        }

        // 微商城订单
        if (Objects.equals(BizTypeEnum.MALL.getBizType(), order.getBizType())) {
            // 完成/配送中 可以选择退货退款
            if (!Arrays.asList(NewOrderStatus.DELIVERING.getIndex(), NewOrderStatus.COMPLETE.getIndex()).contains(order.getOrderState())
                    && !Objects.equals(RefundModeEnum.UN_REFUND_GOODS.getIndex(), refundMode)) {
                throw new ServiceException("还未发货，请选择只退款不退货");
            }
        }
        return order;
    }

    /**
     * C端用户发起退款
     *
     * @param orderRefundVo {@link OrderRefundVo} 退款参数
     * @param reason        退款原因/说明
     */
    public void refundOrder(OrderRefundVo orderRefundVo, String reason) {
        String partnerId = orderRefundVo.getPartnerId();
        String orderCode = orderRefundVo.getOid();
        Long refundAmount = orderRefundVo.getRefundAmount();
        String remark = orderRefundVo.getRemarks();
        Boolean isPartRefund = orderRefundVo.getIsPartRefund();
        Byte refundMode = orderRefundVo.getRefundMode();

        if (StringUtils.isEmpty(reason)) reason = "退款";
        // 退款校验
        OrderInfoReqs order = preValidRefund(partnerId, orderCode, refundMode);
        // 存在售后信息
        if (CollectionUtils.isNotEmpty(order.getAfterSalesOrderList())) {
            Optional<AfterSalesOrderResp> exists = order.getAfterSalesOrderList().stream()
                    .filter(r -> AfterSalesStatus.PENDING.getIndex().equals(r.getAfterSalesStatus()))
                    .findAny();
            if (exists.isPresent()) throw new ServiceException("商家正在处理中，请稍后……");
        }
        // 校验是否允许发起退款
        if (!orderVerifyHandle.getRefundConfig(order))
            throw new ServiceException("该门店不支持线上退款，请联系门店进行线下处理");

        // 如果是买券订单，查询券是否已使用，如果没有使用，冻结券，否则不许退款
        if (BizTypeEnum.SALE_COUPON.getBizType().equals(order.getBizType())) {
            if (BooleanUtils.isTrue(orderRefundVo.getIsPartRefund())) {
                throw new ServiceException("该订单不支持部分退款，请联系门店进行线下处理");
            }
            freezeCouponCode(partnerId, order);
        }

        // 退款是否退配送费
        boolean refundDeliveryFee = isRefundDeliveryFee(order, refundAmount);
        // 未接单
        if (NewOrderStatus.PLACE_AN_ORDER.getIndex().equals(order.getOrderState())) {
            // 商家未接单逻辑处理    1.调用支付退款 2.根据支付退款返回状态组装订单取消参数,调用订单取消接口
            refundOrder(order, isPartRefund, reason, remark, refundDeliveryFee, refundMode);
            orderQueueService.backOrdersStatusChange(order.getOrderCode(), orderSdkAdapter.getOldStatus(order.getOrderState()), order.getPayState(), order.getPartnerId());
        } else {
            // 已接单的，创建售后单
            createAfterSales(order, refundAmount, isPartRefund, reason, remark, refundDeliveryFee, refundMode);
        }
    }

    /**
     * 退订单金额（仅退款，不会处理订单状态）
     *
     * @param partnerId 商户号
     * @param orderCode 订单号
     * @return 退款状态 {@link PayRefundStatus}
     */
    public Integer refundOrderAmt(String partnerId, String orderCode, String refundReason) {
        // 查询订单信息
        QueryByCodeResponse response = orderSdkService.getOrderInfo(partnerId, orderCode);
        if (null == response || null == response.getResult()) throw new ServiceException("订单信息不存在");
        OrderInfoReqs order = response.getResult();
        // 退款，这里不用关心订单状态，直接退这笔订单的支付金额
        PayRefundResponse refundResponse = paymentHandle.getCommonPayRefundResponse(order, refundReason);
        return refundResponse.getPayRefundStatus().getCode();
    }

    /**
     * 研发批量退款，内部技术支持使用
     */
    public Map<String, String> batchDevelopRefund(OrderDevelopRefundVo orderRefundVo) {
        if (CollectionUtils.isEmpty(orderRefundVo.getOrderCodes())) {
            throw new ServiceException(ResponseResult.ORDER_QUERYORDER_ERROR.getCode(), "未传递订单号");
        }
        Map<String, String> resultMap = new LinkedHashMap<>();
        orderRefundVo.getOrderCodes().forEach(orderCode -> {
            try {
                this.developRefund(orderRefundVo, orderCode);
                resultMap.put(orderCode, "成功");
            } catch (Exception e) {
                resultMap.put(orderCode, e.getMessage());
            }
        });
        return resultMap;
    }

    private void developRefund(OrderDevelopRefundVo reqVo, String orderCode) {
        if (StringUtils.isEmpty(reqVo.getReason())) {
            reqVo.setReason("内部操作退款,操作人:" + reqVo.getOperator());
        }
        // 退款校验
        OrderInfoReqs order = preValidRefund(reqVo.getPartnerId(), orderCode, reqVo.getRefundMode());
        // fisherman 仅仅支持 餐饮类订单进行操作
        List<Integer> bizTypes = Arrays.asList(1, 7);
        if (!bizTypes.contains(order.getBizType())) {
            throw new ServiceException(ResponseResult.ORDER__ERRORREFUND.getCode(), "仅支持餐饮订单操作");
        }

        if (!PayStatus.HAVE_PAID.getCode().equals(order.getPayState()))
            throw new ServiceException("订单未支付！");

        // fisherman 该接口的调用 肯定是因已经商户确认要退款的操作, 所以不走正常的 是否可退款判断
        boolean refundDeliveryFee = isRefundDeliveryFee(order, order.getActualPayAmount().longValue());
        // 未接单
        if (NewOrderStatus.PLACE_AN_ORDER.getIndex().equals(order.getOrderState())) {
            // 未接单的 请用户直接操作C端小程序处理
            throw new ServiceException(ResponseResult.ORDER__ERRORREFUND.getCode(), "订单未接单,请指引用户C端操作退款！");
        }

        // 查询是否有未处理的售后单,如果有 直接调用请求,
        boolean isCreateAfter = false;
        if (CollectionUtils.isNotEmpty(order.getAfterSalesOrderList())) {
            Optional<AfterSalesOrderResp> exists = order.getAfterSalesOrderList().stream()
                    .filter(r -> AfterSalesStatus.PENDING.getIndex().equals(r.getAfterSalesStatus()))
                    .findAny();
            if (exists.isPresent()) isCreateAfter = true;
        }

        if (!isCreateAfter) {
            // 没有已申请的售后单，创建售后单
            createAfterSales(order, null, false, reqVo.getReason(), reqVo.getReason(), refundDeliveryFee, reqVo.getRefundMode());
        }
        // 调用oms的 同意退款操作
        OrderManagerRequest request = new OrderManagerRequest();
        request.setPartnerId(order.getPartnerId());
        request.setStoreId(order.getStoreId());
        request.setOrderId(order.getOrderCode());
        request.setReason(reqVo.getReason());
        request.setOperateType(cn.freemud.management.enums.OperateType.ORDER_DEVELOPER_AGREE_REFUND.getOpType());
        request.setOperator(reqVo.getOperator());
        orderManagerAdapter.developRefund(request);
    }

    /**
     * 是否退配送费判断
     */
    private boolean isRefundDeliveryFee(OrderInfoReqs order, Long refundAmount) {
        // 退款金额小于实付金额的情况,表示部分退不需要退配送费
        if (refundAmount != null && refundAmount < order.getSettlementAmount().longValue()) {
            return false;
        }
        boolean refundDeliveryFee = true;
        if (Objects.equals(BizTypeEnum.MALL.getBizType(), order.getBizType())
                && StringUtils.isNotEmpty(order.getExtInfo())) {
            // 商城查询配送费
            OrderExtInfoDto orderExt = JSON.parseObject(order.getExtInfo(), OrderExtInfoDto.class);
            if (null != orderExt && null != orderExt.getProvince()) {
                //整合商城订单状态，订单状态 1 2 3不需校验直接退运费 20210513
                List<Integer> isRefundDeliveryFeeCodes = Arrays.asList(
                        NewOrderStatus.PENDING_PAYMENT.getIndex(),
                        NewOrderStatus.PLACE_AN_ORDER.getIndex(),
                        NewOrderStatus.ACCEPTED.getIndex());
                if (!isRefundDeliveryFeeCodes.contains(order.getOrderState())) {
                    String province = orderExt.getProvince();
                    QueryFreightRefundSupportedVo query = new QueryFreightRefundSupportedVo();
                    query.setPartnerId(order.getPartnerId());
                    query.setProvince(province);
                    QueryFreightRefundSupportedResponse queryResp = deliveryFeiginClient.isFreightRefundSupported(query);
                    if (null != queryResp && 100 == queryResp.getCode()) {
                        refundDeliveryFee = queryResp.getData();
                    }
                }
            }
        } else {
            // 查询小程序是否配置了退运费
            String configStr = redisCache.hashGet(MessageFormat.format(OrderRedisKeyConstant.ORDER_REFUND_CONFIG, order.getPartnerId(), order.getAppId()), OrderRedisKeyConstant.HashKeyForOrderRefundConfig.TAKE_OUT);
            if (StringUtils.isNotEmpty(configStr)) {
                OrderRefundConfigEntity config = JSON.parseObject(configStr, OrderRefundConfigEntity.class);
                String refundDeliveryFeeConfig = config.getRefundShippingFee();
                // 0、null表示 关闭 ; 1 表示开启
                if (null != refundDeliveryFeeConfig
                        && Arrays.asList(NewOrderStatus.DELIVERING.getIndex(), NewOrderStatus.COMPLETE.getIndex()).contains(order.getOrderState())
                        && !ALLOW_REFUND.equals(refundDeliveryFeeConfig)) {
                    refundDeliveryFee = false;
                }
            }
        }
        return refundDeliveryFee;
    }

    /**
     * 商家未接单,取消订单逻辑处理
     * 1.调用支付退款 2.根据支付退款返回状态组装订单取消参数,调用订单取消接口
     */
    private void refundOrder(OrderInfoReqs order, Boolean isPartRefund, String reason, String remark, Boolean isRefundDeliveryFee, Byte refundMode) {
        int state = PayRefundStatus.SUCCESS.getCode();
        List<OrderCancelReq.PayRefundItem> refundItemList = new ArrayList<>();
        PayRefundResponse refundResponse = null;
        if (order.getSettlementAmount().longValue() > 0) {
            refundResponse = paymentHandle.getCommonPayRefundResponse(order, reason);
            state = refundResponse.getPayRefundStatus().getCode();
            if (CollectionUtils.isNotEmpty(refundResponse.getPayRefundItemList())) {
                refundResponse.getPayRefundItemList().forEach(item -> {
                    OrderCancelReq.PayRefundItem refundItem = new OrderCancelReq.PayRefundItem();
                    BeanUtils.copyProperties(item, refundItem);
                    refundItemList.add(refundItem);
                });
            }
        }
        Long refundAmount = refundResponse != null ? refundResponse.getRefundAmount().longValue() : null;
        OrderCancelReq req = OrderCancelReqAdapter.convert(order, UUID.randomUUID().toString(),
                AfterSalesType.USER_CANCEL, isPartRefund, refundAmount, reason, remark, isRefundDeliveryFee, refundMode);
        req.setRefundState(AfterSalesRefunStateEnum.STATE_4.getIndex());
        if (Objects.equals(PayRefundStatus.SUCCESS.getCode(), state)) {
            //退款成功
            req.setCreateEvent(AfterSalesOrderCreateEventEnum.REFUND_COMPLETE.getCreateEvent());
            // 设置退款信息
            req.setPayRefundItemList(refundItemList);
        } else if (Objects.equals(PayRefundStatus.RUNNING.getCode(), state)) {
            req.setCreateEvent(null);
            req.setRefundState(AfterSalesRefunStateEnum.STATE_2.getIndex());
        } else {
            // 其他退款失败的情况 不生成售后单
            throw new ServiceException(ResponseResult.MULTIORDER__ERRORREFUND.getMessage());
        }

        OrderBaseResp<?> resp = orderSdkService.cancelOrder(req, LogThreadLocal.getTrackingNo());
        if (resp == null || !ObjectUtils.equals(ResponseConstant.SUCCESS_RESPONSE_CODE_STR, resp.getCode())) {
            throw new ServiceException(ResponseResult.ORDER__ERRORREFUND.getMessage());
        }

        if (state == PayRefundStatus.RUNNING.getCode()) {
            // 如果退款中, 直接来个申请记录,  退款回调进行处理
            throw new ServiceException(ResponseResult.PAY_REFUND_ERROR);
        }
    }

    /**
     * 创建售后单
     */
    private void createAfterSales(OrderInfoReqs order, Long refundAmount, Boolean isPartRefund, String reason, String remark, Boolean refundDeliveryFee, Byte refundMode) {
        AfterSalesType type = NewOrderStatus.COMPLETE.getIndex().equals(order.getOrderState())
                ? AfterSalesType.USER_SALE_RETURN : AfterSalesType.USER_CANCEL;
        OrderCancelReq req = OrderCancelReqAdapter.convert(order, null, type, isPartRefund, refundAmount, reason, remark, refundDeliveryFee, refundMode);
        // 查询门店服务配置，退单模式为2为自动退款，判断自动退款时间,设置为0则不传入timeout,
        BusinessInfoDto config = storeManager.queryStoreBusiness(order.getPartnerId(), order.getStoreId());
        if (Objects.equals(config.getAutoChargebackOrderType(), 2) && !Objects.equals(config.getAutoChargebackOrderTime(), 0)) {
            req.setTimeOut(AutoOrderConfigTime.getTime(config.getAutoChargebackOrderTime().toString()));
        }
        req.setOrderTask(OrderTaskReqAdapter.convert(config));
        OrderBaseResp<?> resp = orderSdkService.cancelOrder(req, LogThreadLocal.getTrackingNo());
        if (!Objects.equals(resp.getCode(), "100")) {
            throw new ServiceException(ResponseResult.ORDER__ERRORREFUND.getCode(), resp.getMessage());
        }

        // 金额大于0的退款，发送到消息中心推送到pos
        if (order.getActualPayAmount().longValue() > 0) {
            PushMessageNoticeDto notify = MessageNoticeAdapter.convent2PushMessageNoticeDto(2, order.getPartnerId(), order.getStoreId(),
                    null, null, null, null);
            messageCenterClient.createMessage(notify, LogThreadLocal.getTrackingNo());
        }
        // 通知小助手发送申请退款公众号订阅消息
        sendApplyRefundSubscriptionNotice(order, reason);
        // 已接单可发起退款申请，查询门店有无云打印机，有则打印退款申请小票
        printApplyRefundSmallTicket(order, reason);
    }

    /**
     * 通知小助手发送申请退款公众号订阅消息
     */
    private void sendApplyRefundSubscriptionNotice(OrderInfoReqs order, String reason) {
        try {
            if (StringUtils.isBlank(order.getAppId())) return;
            SendMessageRequest sendMessageRequest = new SendMessageRequest();
            sendMessageRequest.setMessageType("REFUND_ORDER");
            sendMessageRequest.setOrderId(order.getOrderCode());
            sendMessageRequest.setPartnerId(order.getPartnerId());
            sendMessageRequest.setRemark(reason);
            sendMessageRequest.setStoreId(order.getStoreId());
            sendMessageRequest.setAmount(order.getActualPayAmount().divide(new BigDecimal(100), RoundingMode.UP).toString());
            sendMessageRequest.setRemindMessage("用户申请退款");
            sendMessageRequest.setOrderTime(DateUtil.convert2String(new Date(Long.parseLong(order.getPayTime())), "yyyy-MM-dd HH:mm:ss"));
            sendMessageRequest.setUserName(order.getUserName());
            sendMessageRequest.setPhone(order.getUserMobile());
            ecologyAdminApplicationClient.sendTemplateMessage(sendMessageRequest);
        } catch (Throwable e) {
            ErrorLog.errorConvertJson(this.getClass(), "退款公众号订阅消息发送异常", e);
        }
    }

    /**
     * 申请退款小票打印
     */
    private void printApplyRefundSmallTicket(OrderInfoReqs order, String reason) {
        // 有云打印机 打印小票，杯贴
        try {
            List<AssortmentCloudPrinter> storePrinters = cloudPrinterManager.getStorePrinters(order.getPartnerId(), order.getStoreId());
            if (CollectionUtils.isNotEmpty(storePrinters)) {
                boolean printerStatus = false;
                for (AssortmentCloudPrinter cloudPrinter : storePrinters) {
                    if (null != cloudPrinter.getStatus() && cloudPrinter.getStatus() == 1) {
                        printerStatus = true;
                    }
                }
                //在线打印
                if (printerStatus) {
                    fmAssistantCloudPrintClient.applyRefundPrint(OrderPrintDtoAdapter.convert(order, reason));
                }
            }
        } catch (Throwable e) {
            ErrorLog.errorConvertJson(this.getClass(), "打印退款小票异常", e);
        }
    }

    /**
     * 校验券状态，如果没有核销则冻结券，否则抛异常
     * <pre></pre>
     * @param partnerId
     * @param orderInfo
     * @throws ServiceException
     */
    private void freezeCouponCode(String partnerId, OrderInfoReqs orderInfo) {
        List<String> couponCodes = Optional.ofNullable(orderInfo.getOrderSettlementDetailList()).orElse(new ArrayList<>()).stream()
                .filter(dto -> Objects.equals(OrderSettlementType.SEND_COUPON.getIndex(), dto.getSettlementType()) && StringUtils.isNotBlank(dto.getExternalObjectId()))
                .map(OrderSettlementResp::getExternalObjectId)
                .collect(Collectors.toList());

        boolean couponUsed = couponQueryHandle.isCouponUsed(partnerId, couponCodes);
        if (couponUsed) {
            throw new ServiceException("识别到您已使用过该券包中的部分或全部券，故不支持退款。");
        }
        // 冻结券
        boolean freeze = orderCouponHandle.freezeCodes(partnerId, orderInfo.getUserId(), couponCodes, "买券订单退款");
        if (!freeze) {
            throw new ServiceException("优惠券校验失败，请稍后再试！");
        }
    }
}
