package com.ghy.order.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.ghy.common.adapay.model.AdapayStatusEnum;
import com.ghy.common.adapay.model.DivMember;
import com.ghy.common.adapay.model.PaymentDTO;
import com.ghy.common.constant.UserConstants;
import com.ghy.common.core.text.Convert;
import com.ghy.common.enums.AdapayOrderType;
import com.ghy.common.enums.FinancialDetailType;
import com.ghy.common.enums.OrderStatus;
import com.ghy.common.enums.PayStatus;
import com.ghy.common.utils.AdapayUtils;
import com.ghy.order.domain.OrderDetail;
import com.ghy.order.domain.OrderMaster;
import com.ghy.order.mapper.OrderMasterMapper;
import com.ghy.order.request.AppOrderRequest;
import com.ghy.order.service.OrderDetailService;
import com.ghy.order.service.OrderMasterService;
import com.ghy.payment.domain.FinancialDetail;
import com.ghy.payment.domain.FinancialMaster;
import com.ghy.payment.service.AdapayService;
import com.ghy.payment.service.FinancialDetailService;
import com.ghy.payment.service.FinancialMasterService;
import com.huifu.adapay.core.exception.BaseAdaPayException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.atomic.AtomicLong;

/**
 * 商品模块实现类
 *
 * @author clunt
 */
@Service
public class OrderMasterServiceImpl implements OrderMasterService {

    private static final Logger logger = LoggerFactory.getLogger(OrderMasterServiceImpl.class);

    @Resource
    private ThreadPoolTaskExecutor executor;
    @Resource
    private AdapayService adapayService;
    @Resource
    private OrderMasterMapper orderMasterMapper;
    @Resource
    private OrderDetailService orderDetailService;
    @Resource
    private FinancialMasterService financialMasterService;
    @Resource
    private FinancialDetailService financialDetailService;

    private static final AtomicLong INDEX = new AtomicLong(1L);

    private final static ThreadLocal<SimpleDateFormat> dateFormat = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyyMMddHHmmss"));

    @Override
    public String createOrderCode() {
        INDEX.compareAndSet(9999L, 1L);
        return "om" + dateFormat.get().format(new Date()) + INDEX.getAndIncrement();
    }

    @Override
    public OrderMaster createMasterOrder(AppOrderRequest appOrderRequest) {
        return null;
    }

    @Override
    public int insertOrderMaster(OrderMaster orderMaster) {
        return orderMasterMapper.insertOrderMaster(orderMaster);
    }

    @Override
    public int updateOrderMaster(OrderMaster orderMaster) throws BaseAdaPayException {
        return orderMasterMapper.updateOrderMaster(orderMaster);
    }

    @Override
    public List<OrderMaster> selectOrderMasterList(OrderMaster orderMaster) {
        return orderMasterMapper.selectOrderMasterList(orderMaster);
    }

    @Override
    public Long countOrderMasterList(OrderMaster orderMaster) {
        return orderMasterMapper.countOrderMasterList(orderMaster);
    }

    @Override
    public OrderMaster selectById(Long orderMasterId) {
        return orderMasterMapper.selectById(orderMasterId);
    }

    @Override
    public OrderMaster selectByCode(String orderMasterCode) {
        return orderMasterMapper.selectByCode(orderMasterCode);
    }

    @Override
    public int deleteOrderMasterByIds(String ids) {
        Long[] orderMasterIds = Convert.toLongArray(ids);
        return orderMasterMapper.deleteOrderMasterByIds(orderMasterIds);
    }

    @Override
    public String checkOrderMasterCodeUnique(OrderMaster orderMaster) {
        long orderMasterId = orderMaster.getId() == null ? -1L : orderMaster.getId();
        OrderMaster info = orderMasterMapper.checkOrderMasterCodeUnique(orderMaster.getCode());
        if (info != null && info.getId() != orderMasterId) {
            return UserConstants.ORDER_CODE_NOT_UNIQUE;
        }
        return UserConstants.ORDER_CODE_UNIQUE;
    }

    @Override
    public int updateStatus(Long orderMasterId, int orderStatus) {
        return orderMasterMapper.updateStatus(orderMasterId, orderStatus);
    }

    /**
     * 主订单完成
     * 由定时器触发 而不是由用户触发（用户只能对子订单操作完成）
     * 当所有的子订单完成后 定时器进入此方法
     * 1.修改主订单状态
     * 2.对提成、分销、罚款、手续费等类型的财务单分销和提现
     *
     * @param orderMasterId 主订单ID
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    // 暂时先用 synchronized 来避免重复提交
    public synchronized void finish(Long orderMasterId) throws BaseAdaPayException {
        // 校验订单
        OrderMaster orderMaster = selectById(orderMasterId);
        Assert.notNull(orderMaster, "订单不存在");
        List<OrderDetail> orderDetails = orderDetailService.selectByOrderMasterId(orderMasterId);
        for (OrderDetail orderDetail : orderDetails) {
            logger.info("主订单[id={}]存在未完成的子订单", orderMasterId);
            if (orderDetail.getOrderStatus() < 5) {
                return;
            }
        }
        FinancialMaster financialMaster = financialMasterService.selectByOrderMasterId(orderMasterId);
        Assert.notNull(financialMaster, String.format("主订单[id=%d]找不到对应的主财务单", orderMasterId));
        if (financialMaster.getPayStatus() != 1) {
            logger.info("主订单[id={}]未支付", orderMasterId);
            return;
        }
        PaymentDTO payment = financialMasterService.selectPaymentById(financialMaster.getPaymentId());
        Assert.notNull(payment, String.format("主订单[id=%d]找不到支付记录", orderMasterId));

        updateStatus(orderMasterId, OrderStatus.FINISH_CHECK.code());

        if (BigDecimal.ZERO.compareTo(financialMaster.getPayMoney()) > -1) {
            logger.info("订单[code={}]支付金额<=0，不需要分账", orderMaster.getCode());
            // 支付金额<=0的话 不需要走下面的流程了
            return;
        }

        // 用子财务单组成分账信息
        List<FinancialDetail> financialDetails = financialDetailService.selectByFinancialMasterId(financialMaster.getId());
        // 校验金额 主财务单的金额减去所有子财务单的金额是否=0
        BigDecimal checkMoney = financialMaster.getPayMoney();
        // 确认支付金额 = 主财务单付款金额 - 退款金额 - 已确认金额
        BigDecimal confirmAmt = financialMaster.getPayMoney();

        // key:memberId(分账账户ID) value:分账金额
        HashMap<String, BigDecimal> memberMap = new HashMap<>();

        for (FinancialDetail financialDetail : financialDetails) {
            checkMoney = checkMoney.subtract(financialDetail.getPayMoney());
            String memberId;
            switch (financialDetail.getFinancialDetailType()) {
                case 0:
                    // 上门师傅的财务单单独确认分账了 不在这里分
                    // 减掉已确认金额
                    confirmAmt = confirmAmt.subtract(financialDetail.getPayMoney());
                    break;
                case 1:
                    // 大师傅/店铺提成
                    memberId = AdapayUtils.getWorkerMemberId(financialDetail.getPayeeId(), orderMaster.getDeptId());
                    memberMap.merge(memberId, financialDetail.getPayMoney(), BigDecimal::add);
                    break;
                case 2:
                    // 平台提成 并且是手续费承担方
                    memberMap.merge("0", financialDetail.getPayMoney(), BigDecimal::add);
                    break;
                case 3:
                    // 分销
                    Long payeeId = financialDetail.getPayeeId();
                    if (payeeId != null) {
                        memberId = AdapayUtils.getCustomerMemberId(payeeId, orderMaster.getDeptId());
                        // 如果有分销人 那这笔钱就是分销人的
                        memberMap.merge(memberId, financialDetail.getPayMoney(), BigDecimal::add);
                    } else {
                        // 如果没有分销人 那这笔钱就分到平台账户
                        memberMap.merge("0", financialDetail.getPayMoney(), BigDecimal::add);
                    }
                    break;
                case 4:
                    // 减掉退款金额
                    confirmAmt = confirmAmt.subtract(financialDetail.getPayMoney());
                case 5:
                    // 订单超时罚金 归平台所有
                    memberMap.merge("0", financialDetail.getPayMoney(), BigDecimal::add);
                    break;
                default:
                    break;
            }
        }

        // 这里校验一次主财务单的金额减去所有子财务单的金额是否=0
        Assert.isTrue(BigDecimal.ZERO.compareTo(checkMoney) == 0, "订单金额异常，请稍后再试");

        // 分账账户
        ArrayList<DivMember> divMembers = new ArrayList<>();
        // 需要自动提现的账户
        ArrayList<DivMember> autoDrawCashMembers = new ArrayList<>();
        for (Map.Entry<String, BigDecimal> entry : memberMap.entrySet()) {
            String memberId = entry.getKey();
            BigDecimal money = entry.getValue();
            String amount = AdapayUtils.bigDecimalToString(money);
            if ("0".equals(memberId)) {
                // 0是平台账户 只参与分账 不自动提现
                divMembers.add(new DivMember("0", amount, true));
            } else {
                DivMember member = new DivMember(memberId, amount, false);
                divMembers.add(member);
                autoDrawCashMembers.add(member);
            }
        }

        logger.info("订单[code={}]分账信息: {}", orderMaster.getCode(), JSON.toJSONString(divMembers));
        JSONObject response = adapayService.paymentConfirm(orderMaster.getDeptId(), payment.getId(), payment.getOrderNo(),
                AdapayUtils.bigDecimalToString(confirmAmt), divMembers, null, null);
        logger.info("订单[code={}]分账结果: {}", orderMaster.getCode(), response.toJSONString());

        boolean status = AdapayStatusEnum.pending.code.equals(response.getString("status")) ||
                AdapayStatusEnum.succeeded.code.equals(response.getString("status"));
        // 如果确认支付失败 这里抛出异常
        Assert.isTrue(status, response.getString("error_msg"));

        // 走到这里确认支付和分账都成功了 异步进入自动提现流程
        logger.info("订单[code={}]开始自动提现", orderMaster.getCode());
        autoDrawCashMembers.forEach(member -> executor.execute(() -> {
            String memberId = member.getMemberId();
            String amount = member.getAmount();
            try {
                drawCash(orderMaster.getDeptId(), memberId, amount);
            } catch (BaseAdaPayException e) {
                logger.error("自动发起提现失败: orderMasterId={}, memberId={}, cashAmt={}", orderMasterId, memberId, amount, e);
            }
        }));
    }

    /**
     * 发起提现
     *
     * @param deptId   公司ID
     * @param memberId Adapay实名账户ID
     * @param amount   提现金额
     */
    private void drawCash(Long deptId, String memberId, String amount) throws BaseAdaPayException {
        String orderNo = AdapayUtils.createOrderNo(AdapayOrderType.DRAW_CASH);
        JSONObject response = adapayService.drawCash(deptId, orderNo, "T1", amount, memberId, "订单结算", null);

        boolean status = AdapayStatusEnum.pending.code.equals(response.getString("status")) ||
                AdapayStatusEnum.succeeded.code.equals(response.getString("status"));
        if (!status) {
            //如果提现失败 把信息记录到error日志里
            logger.error("提现失败: deptId={}, memberId={}, amount={}, 失败原因:{}", deptId, memberId, amount, response.getString("error_msg"));
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void cancel(Long orderMasterId) {
        OrderMaster orderMaster = selectById(orderMasterId);
        Assert.notNull(orderMaster, "找不到对应的订单");
        switch (orderMaster.getOrderStatus()) {
            case 0: // 待接单状态可直接取消
            case 1: // 待排期状态可直接取消
                // 把主订单状态设置为已取消
                updateStatus(orderMasterId, OrderStatus.CANCEL.code());
                // 把所有子订单状态设置为已取消
                OrderDetail update = new OrderDetail();
                update.setOrderMasterId(orderMasterId);
                update.setOrderStatus(OrderStatus.CANCEL.code());
                orderDetailService.updateByOrderMasterId(update);
                // 发起退款
                try {
                    refund(orderMaster);
                } catch (BaseAdaPayException e) {
                    logger.error("订单[code={}]退款失败", orderMaster.getCode(), e);
                }
                break;
            case 2: // 待上门状态需要师傅同意才能取消
                updateStatus(orderMasterId, OrderStatus.GOING_CANCEL.code());
                break;
            case 3: // 服务中状态需要师傅同意才能取消
                updateStatus(orderMasterId, OrderStatus.SERVER_CANCEL.code());
                break;
            case 4: // 待确认状态需要师傅同意才能取消
                updateStatus(orderMasterId, OrderStatus.FINISH_CHECK_CANCEL.code());
                break;
            default: // 其他状态不可取消
                throw new IllegalArgumentException("这个订单不能取消了");
        }
    }

    @Override
    public void cancelAgree(Long orderMasterId, Integer agree) {
        OrderMaster orderMaster = selectById(orderMasterId);
        Assert.notNull(orderMaster, "找不到对应的订单");
        if (Integer.valueOf(1).equals(agree)) {
            // 师傅同意取消订单
            // 把主订单状态设置为已取消
            updateStatus(orderMasterId, OrderStatus.CANCEL.code());
            // 把所有子订单状态设置为已取消
            OrderDetail update = new OrderDetail();
            update.setOrderMasterId(orderMasterId);
            update.setOrderStatus(OrderStatus.CANCEL.code());
            orderDetailService.updateByOrderMasterId(update);
            // 发起退款
            try {
                refund(orderMaster);
            } catch (BaseAdaPayException e) {
                logger.error("订单[code={}]退款失败", orderMaster.getCode(), e);
            }
        } else {
            // 师傅不同意取消订单
            Integer orderStatus = orderMaster.getOrderStatus();
            // 修改主订单状态为原来的状态
            updateStatus(orderMasterId, Math.abs(orderStatus));
        }
    }

    @Override
    public List<OrderMaster> selectByStatus(List<Integer> status) {
        Assert.isTrue(!CollectionUtils.isEmpty(status), "订单状态为空");
        return orderMasterMapper.selectByStatus(status);
    }

    /**
     * 主订单发起退款
     *
     * @param orderMaster 主订单
     */
    private void refund(OrderMaster orderMaster) throws BaseAdaPayException {
        FinancialMaster financialMaster = financialMasterService.selectByOrderMasterId(orderMaster.getId());
        // 支付状态
        Integer payStatus = financialMaster.getPayStatus();
        if (!payStatus.equals(1)) {
            // 如果状态不是已支付 则不需要退款
            return;
        }
        List<FinancialDetail> financialDetails = financialDetailService.selectByFinancialMasterId(financialMaster.getId());

        // 主订单实付金额 - 已退款金额 = 待退款金额
        BigDecimal payMoney = financialMaster.getPayMoney();
        for (FinancialDetail fd : financialDetails) {
            if (FinancialDetailType.RETURN_FEE.getCode().equals(fd.getFinancialDetailType())) {
                // 如果有退款子财务单 则减去子财务单的已退款金额
                payMoney = payMoney.subtract(fd.getPayMoney());
            }
        }

        if (BigDecimal.ZERO.compareTo(payMoney) > -1) {
            logger.info("订单[code={}]待退款金额<=0，不需要退款", orderMaster.getCode());
            return;
        }

        // 把主财务单状态设置为已退款
        FinancialMaster fmUpdate = new FinancialMaster();
        fmUpdate.setId(financialMaster.getId());
        fmUpdate.setPayStatus(PayStatus.REFUND.getCode());
        financialMasterService.updateFinancialMaster(fmUpdate);
        // 把所有子财务单状态设置为已退款
        FinancialDetail fdUpdate = new FinancialDetail();
        fdUpdate.setFinancialMasterId(financialMaster.getId());
        fdUpdate.setPayStatus(PayStatus.REFUND.getCode());
        financialDetailService.updateByFinancialMasterId(fdUpdate);

        String refundAmt = AdapayUtils.bigDecimalToString(payMoney);
        JSONObject response = adapayService.payReverse(orderMaster.getDeptId(), financialMaster.getPaymentId(), refundAmt);
        logger.debug("退款结果: " + response.toJSONString());
        boolean status = AdapayStatusEnum.pending.code.equals(response.getString("status")) ||
                AdapayStatusEnum.succeeded.code.equals(response.getString("status"));
        // 如果退款失败 这里携带失败原因抛出异常 回滚订单状态
        Assert.isTrue(status, response.getString("error_msg"));
        logger.info("订单[code={}]退款成功", orderMaster.getCode());
    }

    @Override
    public List<OrderMaster> selectUnfinished() {
        return orderMasterMapper.selectUnfinished();
    }
}
