package com.xxfc.platform.order.biz;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.github.wxiaoqi.security.admin.feign.UserFeign;
import com.github.wxiaoqi.security.common.exception.BaseException;
import com.github.wxiaoqi.security.common.msg.ObjectRestResponse;
import com.github.wxiaoqi.security.common.util.IntervalUtil;
import com.github.wxiaoqi.security.common.util.process.ResultCode;
import com.xxfc.platform.order.biz.inner.OrderCalculateBiz;
import com.xxfc.platform.order.biz.inner.OrderMsgBiz;
import com.xxfc.platform.order.contant.enumerate.*;
import com.xxfc.platform.order.entity.*;
import com.xxfc.platform.order.pojo.DedDetailDTO;
import com.xxfc.platform.order.pojo.account.OrderAccountDTO;
import com.xxfc.platform.order.pojo.account.OrderAccountDeduction;
import com.xxfc.platform.order.pojo.account.OrderAccountDetail;
import com.xxfc.platform.order.pojo.mq.OrderMQDTO;
import com.xxfc.platform.universal.constant.DictionaryKey;
import com.xxfc.platform.universal.entity.Dictionary;
import com.xxfc.platform.universal.feign.ThirdFeign;
import com.xxfc.platform.universal.vo.OrderRefundVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.jexl2.MapContext;
import org.mockito.internal.util.collections.Sets;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.xxfc.platform.order.mapper.OrderAccountMapper;
import com.github.wxiaoqi.security.common.biz.BaseBiz;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.List;
import java.util.Map;
import java.util.Set;

import static com.github.wxiaoqi.security.common.constant.CommonConstants.SYS_FALSE;
import static com.github.wxiaoqi.security.common.constant.CommonConstants.SYS_TRUE;
import static com.xxfc.platform.universal.constant.DictionaryKey.APP_ORDER;

/**
 * 订单帐目
 *
 * @author zhoujw
 * @email 18178966185@163.com
 * @date 2019-09-09 15:51:16
 */
@Service
@Slf4j
public class OrderAccountBiz extends BaseBiz<OrderAccountMapper,OrderAccount> {

    @Autowired
    BaseOrderBiz baseOrderBiz;

    @Autowired
    OrderRentVehicleBiz orderRentVehicleBiz;

    @Autowired
    OrderVehicleCrosstownBiz crosstownBiz;

    @Autowired
    OrderMsgBiz orderMsgBiz;

    @Autowired
    OrderItemBiz orderItemBiz;

    @Autowired
    OrderDepositRefundRecordBiz orderDepositRefundRecordBiz;

    @Autowired
    OrderCalculateBiz orderCalculateBiz;

    @Autowired
    ThirdFeign thirdFeign;

    @Autowired
    UserFeign userFeign;


    /**
     * 租车退款流程
     * @param baseOrder
     * @param mainItemRealAmount 主要商品（租车费用、旅游费用等）
     * @param timeLag 与开始时间的时间差
     * @param dicParentKey
     */
    public BigDecimal rentRefundProcessCancel(BaseOrder baseOrder, BigDecimal mainItemRealAmount, Long timeLag, String dicParentKey, BigDecimal depositAmount, BigDecimal originalDeductAmount, OrderAccountDetail oad) {
        //计算退款金额
        //商品价格 - 优惠券减免的价格
        BigDecimal originalRefundAmount = BigDecimal.ZERO.add(mainItemRealAmount);
        StringBuilder refundDescBuilder = new StringBuilder("取消订单退款:");
        if(null == oad) {
            oad = new OrderAccountDetail();
        }
        BigDecimal refundMainGoodsAmount = calculateRefund(originalRefundAmount, timeLag, dicParentKey, refundDescBuilder, oad);
        refundMainGoodsAmount = refundMainGoodsAmount.setScale(2, RoundingMode.HALF_UP);

        //退款金额 = 主要商品退款 + （其他商品退款） 即--> 主要商品退款 + （总商品款 - 主要商品款）
        oad.setOrderAmount(refundMainGoodsAmount.add(baseOrder.getGoodsAmount().subtract(mainItemRealAmount)));
        BigDecimal refundAmount = oad.getOrderAmount();

        // 押金
        BigDecimal originalRefundAmountDeposit = BigDecimal.ZERO.add(depositAmount);
        BigDecimal refundAmountDeposit = BigDecimal.ZERO.add(depositAmount);
        refundDescBuilder = refundDescBuilder.append(",押金退款:");
        if(null != originalDeductAmount && BigDecimal.ZERO.compareTo(originalDeductAmount) < 0) {
            //通过原扣除款 计算剩余款
            BigDecimal residueAmount = calculateRefund(originalDeductAmount, timeLag, dicParentKey, refundDescBuilder, oad);
            residueAmount = residueAmount.setScale(2, RoundingMode.HALF_UP);

            //退款金额 ：押金 - (原扣除款 - 剩余款)   即： 押金 - （免费天数对应的钱 - 剩余款）
            refundAmountDeposit = originalRefundAmountDeposit.subtract(originalDeductAmount.subtract(residueAmount));
        }

        originalRefundAmount = originalRefundAmount.add(originalRefundAmountDeposit);
        oad.setDepositAmount(refundAmountDeposit);
        refundAmount = refundAmount.add(refundAmountDeposit);

        //退款子流程: 订单基础，退款描述, 款金额
        refundSubProcess(baseOrder, refundDescBuilder.toString(), originalRefundAmount, refundAmount, AccountTypeEnum.OUT_ORDER_FUND.getCode(), RefundStatusEnum.ALL.getCode(), oad);
        return refundAmount;
    }

    /**
     * 退款子流程
     * @param baseOrder
     * @param refundDesc 退款描述
     * @param refundAmount 退款金额
     * @param refundType 退款类型
     * @param refundStatus 退款状态
     */
    public void refundSubProcess(BaseOrder baseOrder, String refundDesc, BigDecimal originalRefundAmount, BigDecimal refundAmount, Integer refundType, Integer refundStatus, OrderAccountDetail oad) {
        String refundTradeNo = null;
        //0 小于 退款金额
        if(BigDecimal.ZERO.compareTo(refundAmount) < 0) {
            OrderRefundVo orv = new OrderRefundVo();
            orv.setAmount(baseOrder.getRealAmount().multiply(new BigDecimal("100")).intValue());
            orv.setOrderNo(baseOrder.getNo());
            orv.setRefundDesc(refundDesc+ refundAmount.toString());
            orv.setRefundAmount(refundAmount.multiply(new BigDecimal("100")).intValue());
            ObjectRestResponse<String> result = thirdFeign.refund(orv);
            refundTradeNo = result.getData();
            if(null == refundTradeNo) {
                log.error("退款没有refundTradeNo，订单号为："+ baseOrder.getNo()+ ", 微服务调用结果为"+ JSONUtil.toJsonStr(result));
            }
        }

        //记录订单退款记录
        Integer flag = addOrderAccount(baseOrder.getId(), refundDesc, originalRefundAmount, refundAmount, refundTradeNo, refundType, JSONUtil.toJsonStr(oad));

        //更新订单的退款状态和退款时间
        BaseOrder updateBaseOrder = new BaseOrder();
        updateBaseOrder.setId(baseOrder.getId());
        updateBaseOrder.setRefundStatus(refundStatus);
        updateBaseOrder.setRefundTime(System.currentTimeMillis());
        updateBaseOrder.setVersion(baseOrder.getVersion());
        if(SYS_TRUE.equals(flag) && null != refundStatus) {
            BeanUtil.copyProperties(baseOrderBiz.updateSelectiveByIdReT(updateBaseOrder), baseOrder);
        }
    }

    public BigDecimal calculateRefund(BigDecimal goodsAmount, Long timeLag, String dicParentKey, StringBuilder refundDescBuilder, OrderAccountDetail orderAccountDetail) {
        BigDecimal refundGoodsAmount = goodsAmount;
        if(null == orderAccountDetail) {
            orderAccountDetail = new OrderAccountDetail();
        }

        //根据时间处理goodsAmount
        //获取出发时间 到现在 距离时间
        Integer hourLag = Long.valueOf(timeLag/(1000L * 60L * 60L)).intValue();

        Map<String, Dictionary> dictionaryMap = thirdFeign.dictionaryGetAll4Map().getData();
        Set<Dictionary> rentRefunds = dictionaryMap.get(dicParentKey).getChildrens();

        for(com.xxfc.platform.universal.entity.Dictionary dic : rentRefunds) {
            if(StrUtil.isBlank(dic.getName())) {
                continue;
            }
            String[] names = dic.getName().split("\\|");
            if(names.length < 2) {
                continue;
            }

            //符合范围
            if(IntervalUtil.staticIsInTheInterval(hourLag.toString(), names[0])){
                refundGoodsAmount = new BigDecimal((IntervalUtil.evaluate(dic.getDetail(), new MapContext(){{
                    //ga : goodsAmount
                    set("ga", goodsAmount);
                }})+ "").toString());
                refundDescBuilder = refundDescBuilder.insert(0, names[1]+ ",");
                if(goodsAmount.subtract(refundGoodsAmount).compareTo(BigDecimal.ZERO) > 0) {
                    orderAccountDetail.getDeductions().add(initDeduction(goodsAmount.subtract(refundGoodsAmount), names[1], DeductionTypeEnum.VIOLATE_CANCEL, OrderAccountDeduction.ORIGIN_DEPOSIT));
                }
                break;
            }
        }

        return refundGoodsAmount;
    }

    /**
     * 初始化deduction
     * @param subtract
     * @param name
     * @return
     */
    public OrderAccountDeduction initDeduction(BigDecimal subtract, String name, DeductionTypeEnum dte, Integer origin) {
        OrderAccountDeduction orderAccountDeduction = new OrderAccountDeduction();
        orderAccountDeduction.setType(dte.getCode());
        orderAccountDeduction.setName(name);
        orderAccountDeduction.setAmount(subtract);
        orderAccountDeduction.setOrigin(origin);
        return orderAccountDeduction;
    }

    /**
     * 记录订单退款记录
     * @param orderId
     * @param accountDesc
     * @param accountAmount
     * @param tradeNo
     */
    public Integer addOrderAccount(Integer orderId, String accountDesc, BigDecimal originalAmount, BigDecimal accountAmount, String tradeNo, Integer accountType, String detail) {
        //如果返回的流水为空，则当做失败
        Integer refundStatus = SYS_TRUE;
        if(StrUtil.isBlank(tradeNo)) {
            refundStatus = SYS_FALSE;
        }

        //创建订单退款记录
        OrderAccount orderAccount = new OrderAccount(){{
            setOrderId(orderId);
            setAccountTime(System.currentTimeMillis());
            setTradeNo(tradeNo);
            setAccountType(RefundTypeEnum.ORDER_FUND.getCode());
        }};
        orderAccount.setOriginalAmount(originalAmount);
        orderAccount.setDeductAmount(originalAmount.subtract(accountAmount));
        orderAccount.setAccountAmount(accountAmount);
        orderAccount.setAccountDesc(accountDesc);
        orderAccount.setAccountStatus(refundStatus);
        orderAccount.setAccountType(accountType);
        orderAccount.setAccountDetail(detail);
        insertSelective(orderAccount);

        return refundStatus;
    }

    public void refundTrigger(BaseOrder baseOrder, OrderRentVehicleDetail orvd, BigDecimal residueAmount, BigDecimal originalRefundAmount, BigDecimal refundAmont, String refundDesc, Integer refundStatus, AccountTypeEnum accountTypeEnum, OrderAccountDetail oad) {
        //退款子流程: 订单基础，退款描述，退款金额
        refundSubProcess(baseOrder, refundDesc, originalRefundAmount, refundAmont, accountTypeEnum.getCode(), refundStatus, oad);
        //设置剩余没有返还的钱
        orderRentVehicleBiz.updateSelectiveById(new OrderRentVehicleDetail(){{
            setId(orvd.getId());
            setReturnPayResidue(residueAmount);
        }});
    }

    /**
     * 退还部分押金
     * @param orderMQDTO
     */
    public void refundPartDeposit(OrderMQDTO orderMQDTO){
        Map<String, Dictionary> dictionaryMap = thirdFeign.dictionaryGetAll4Map().getData();
        BigDecimal illegalReserve = new BigDecimal(dictionaryMap.get(APP_ORDER+ "_"+ DictionaryKey.ILLEGAL_RESERVE).getDetail());

        //未退还， 进行挂起保留违章预备金 的退还
        if(RefundStatusEnum.NONE.getCode().equals(orderMQDTO.getRefundStatus())) {
            Integer crosstownTypeEnum;
            DepositRefundStatus depositRefundRecordStatus;
            //判断是否定损过
            if(SYS_TRUE.equals(orderMQDTO.getOrderRentVehicleDetail().getFixedLossStatus())) {
                crosstownTypeEnum = CrosstownTypeEnum.FIXED_LOSS.getCode();
                depositRefundRecordStatus = DepositRefundStatus.FIXLOSSREFUNDARRIVAL;
            }else {
                crosstownTypeEnum = CrosstownTypeEnum.ARRIVE.getCode();
                depositRefundRecordStatus = DepositRefundStatus.REFUNDARRIVAL;
            }
            OrderVehicleCrosstown crosstown = crosstownBiz.selectOne(new OrderVehicleCrosstown(){{
                setOrderId(orderMQDTO.getId());
                setType(crosstownTypeEnum);
            }});

            OrderItem orderItem = orderItemBiz.selectOne(new OrderItem(){{
                setType(ItemTypeEnum.VEHICLE_MODEL.getCode());
                setOrderId(orderMQDTO.getId());
            }});

            if(null == crosstown) {
                throw new BaseException(ResultCode.PARAM_ILLEGAL_CODE, Sets.newSet("退款第一部分押金失败，获取不了还车/定损记录，订单号："+ orderMQDTO.getId()));
            }

            if(null == crosstown.getRestDeposit()
                    || crosstown.getRestDeposit().subtract(illegalReserve).compareTo(BigDecimal.ZERO) < 0 ){
                throw new BaseException(ResultCode.PARAM_ILLEGAL_CODE, Sets.newSet("退第一笔押金剩余金额异常，异常记录为："+ crosstown.getId()));
            }

            OrderAccountDetail oad = new OrderAccountDetail();
            orderCalculateBiz.calculateOrderComplete(orderMQDTO, orderMQDTO.getOrderRentVehicleDetail(), oad, orderItem, orderMQDTO.getOrderRentVehicleDetail().getUsedDay());

            //还车扣除款 剩余的 钱，再减去违章预备金
            oad.getDeductions().add(
                    initDeduction(illegalReserve, "违章保证金", DeductionTypeEnum.VIOLATE_TRAFFIC_KEEP, OrderAccountDeduction.ORIGIN_DEPOSIT)
            );
            oad.getDeductions().add(
                    initDeduction(crosstown.getDeductionCost(), "定损赔偿金", DeductionTypeEnum.DAMAGES, OrderAccountDeduction.ORIGIN_DEPOSIT)
            );
            //剩余押金 -
            oad.setDepositAmount(oad.getDepositAmount().subtract(illegalReserve).subtract(crosstown.getDeductionCost()));
            handleCrosstownDetail(crosstown, oad);
            BigDecimal refundAmont = crosstown.getRestDeposit().subtract(illegalReserve);
            BigDecimal originalRefundAmount = crosstown.getRestDeposit().add(crosstown.getDeductionCost()).subtract(illegalReserve);
            String refundDesc = "退还押金："+ refundAmont.toString()+ "(已扣除 违章预备金："+ illegalReserve.toString();
            refundDesc = handleDed(crosstown, refundDesc);
            refundDesc += ")";
            refundTrigger(orderMQDTO, orderMQDTO.getOrderRentVehicleDetail(), illegalReserve, originalRefundAmount, refundAmont, refundDesc, RefundStatusEnum.RESIDUE_ILLEGAL.getCode(), AccountTypeEnum.OUT_PART_DEPOSIT, oad);

            orderDepositRefundRecordBiz.completeRecordStatus(crosstown.getId(), depositRefundRecordStatus);
            orderMsgBiz.handelMsgDeposit(orderMQDTO.getOrderRentVehicleDetail(), orderMQDTO, userFeign.userDetailById(orderMQDTO.getUserId()).getData());
        }
    }

    private String handleDed(OrderVehicleCrosstown crosstown, String refundDesc) {
        try{
            if(null != crosstown.getDedDetail()) {
                List<DedDetailDTO> dddList = JSONUtil.toList(JSONUtil.parseArray(crosstown.getDedDetail()), DedDetailDTO.class);
                for(DedDetailDTO ddd : dddList) {
                    refundDesc += ", "+ ddd.getDeductions()+ ":"+ ddd.getCost();
                }
            }
        }catch (Exception e) {
            log.error("crosstown.getDedDetail() crosstown id :"+crosstown.getId() +" 转换失败");
        }
        return refundDesc;
    }

    private void handleCrosstownDetail(OrderVehicleCrosstown crosstown, OrderAccountDetail oad) {
        try{
            if(null != crosstown.getViolateDetail()) {
                List<DedDetailDTO> dddList = JSONUtil.toList(JSONUtil.parseArray(crosstown.getViolateDetail()), DedDetailDTO.class);
                for(DedDetailDTO vio : dddList) {
                    if(OrderViolateEnum.BEFORE.getCode().equals(vio.getType())) {
                        for(OrderAccountDeduction deduction : oad.getDeductions()) {
                            if(DeductionTypeEnum.VIOLATE_ADVANCE.getCode().equals(deduction.getType())) {
                                deduction.setName(vio.getDeductions());
                                BigDecimal diff = vio.getCost().subtract(deduction.getAmount());
                                //修改归还押金金额
                                oad.setDepositAmount(oad.getDepositAmount().subtract(diff));
                                deduction.setAmount(vio.getCost());
                            }
                        }
                    }
                }
            }
        }catch (Exception e) {
            log.error("crosstown.getViolateDetail() crosstown id :"+crosstown.getId() +" 转换失败");
        }
    }


    /**
     * 获取每日订单账目,用于统计
     * @param code
     * @param day
     * @return
     */
    public List<OrderAccountDTO> getOrderAccountByOrderType(Integer code, Integer day) {


        return mapper.getOrderAccountByOrderType(code,day);
    }







}