
package com.tmerclub.cloud.search.feign;


import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson2.JSON;
import com.tmerclub.cloud.api.feign.SearchOrderRefundTaskFeignClient;
import com.tmerclub.cloud.api.order.bo.EsOrderRefundBO;
import com.tmerclub.cloud.api.vo.EsPageVO;
import com.tmerclub.cloud.common.constant.Constant;
import com.tmerclub.cloud.common.constant.SendTypeEnum;
import com.tmerclub.cloud.common.exception.LuckException;
import com.tmerclub.cloud.common.order.constant.ReturnProcessStatusEnum;
import com.tmerclub.cloud.common.order.dto.OrderRefundSearchDTO;
import com.tmerclub.cloud.common.order.dto.OrderSearchDTO;
import com.tmerclub.cloud.common.order.vo.EsOrderVO;
import com.tmerclub.cloud.common.order.vo.SendNotifyBO;
import com.tmerclub.cloud.common.response.ResponseEnum;
import com.tmerclub.cloud.common.response.ServerResponseEntity;
import com.tmerclub.cloud.common.rocketmq.config.RocketMqConstant;
import com.tmerclub.cloud.common.util.BeanUtil;
import com.tmerclub.cloud.search.constant.EsConstant;
import com.tmerclub.cloud.search.manager.OrderRefundSearchManager;
import com.tmerclub.cloud.search.manager.OrderSearchManager;
import com.tmerclub.cloud.search.util.EsSearchUtil;
import org.apache.dubbo.config.annotation.DubboService;
import org.apache.rocketmq.client.producer.SendStatus;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.search.SearchScrollRequest;
import org.elasticsearch.core.TimeValue;
import org.elasticsearch.search.Scroll;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.support.GenericMessage;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;

/**
 * 搜索符合定时任务条件的订单
 *
 * @author YXF
 * @date 2020/12/07
 */
@DubboService
public class SearchOrderRefundTaskFeignController implements SearchOrderRefundTaskFeignClient {
    private static final Logger logger = LoggerFactory.getLogger(SearchOrderRefundTaskFeignController.class);

    /**
     * 退款订单超时关闭
     */
    private static final Integer CANAL_ORDER = 1;
    /**
     * 退款即将超时发送消息
     */
    private static final Integer TIMEOUT_REMINDER = 2;
    /**
     * 发放退款
     */
    private static final Integer REFUND_REQUEST = 3;
    /**
     * 平台介入退款超时
     */
    private static final Integer REFUND_INTERVENTION_TIMEOUT = 4;

    @Autowired
    private OrderSearchManager orderSearchManager;
    @Autowired
    private OrderRefundSearchManager orderRefundSearchManager;
    @Autowired
    private RocketMQTemplate sendNotifyToUserTemplate;
    @Autowired
    private RocketMQTemplate orderRefundCancelMqTemplate;
    @Autowired
    private RocketMQTemplate orderRefundRequstMqTemplate;

    @Override
    public ServerResponseEntity<Object> cancelWhenTimeOut() {
        //设置查询超时时间
        OrderRefundSearchDTO param = new OrderRefundSearchDTO();
        param.setEndTime(DateUtil.offsetDay(new Date(), -Constant.MAX_REFUND_APPLY_TIME).toJdkDate());
        param.setReturnMoneyStsList(ReturnProcessStatusEnum.closeStatus());
        param.setFetchSource(EsConstant.REFUND_ORDER_CANAL_TASK_FETCH_SOURCE);
        return handleOrder(param, CANAL_ORDER);
    }

    @Override
    public ServerResponseEntity<Object> pressProximityTimeOutOrder() {
        // 临时超时时间为 最大申请时间 - 12小时
        int overTime = Constant.MAX_REFUND_APPLY_TIME * 24;
        Date beginTime = DateUtil.offsetHour(new Date(), Constant.MAX_REFUND_HOUR - overTime).toJdkDate();
        Date endTime = DateUtil.offsetDay(new Date(), -Constant.MAX_REFUND_APPLY_TIME).toJdkDate();
        OrderRefundSearchDTO param = new OrderRefundSearchDTO();
        param.setBeginTime(beginTime);
        param.setEndTime(endTime);
        param.setReturnMoneyStsList(ReturnProcessStatusEnum.closeStatus());
        param.setFetchSource(EsConstant.REFUND_ORDER_TIMEOUT_REMINDER_TASK_FETCH_SOURCE);
        return handleOrder(param, TIMEOUT_REMINDER);
    }

    @Override
    public ServerResponseEntity<Object> refundRequest() {
        // 找到十分钟之前已经处理退款但是发放退款失败的退款订单
        OrderRefundSearchDTO param = new OrderRefundSearchDTO();
        param.setReturnMoneySts(ReturnProcessStatusEnum.PROCESSING.value());
        Date decisionTime = DateUtil.offsetMinute(new Date(), -10).toJdkDate();
        param.setDecisionTime(decisionTime);
        param.setFetchSource(EsConstant.REFUND_ORDER_CANAL_TASK_FETCH_SOURCE);
        return handleOrder(param, REFUND_REQUEST);
    }

    @Override
    public ServerResponseEntity<Object> cancelRefundInterventionTimeOut() {
        // 设置平台介入查询超时时间
        OrderRefundSearchDTO param = new OrderRefundSearchDTO();
        param.setApplyInterventionTime(DateUtil.offsetDay(new Date(), -Constant.MAX_INTERVENTION_REFUND_APPLY_TIME).toJdkDate());
        param.setPlatformInterventionStatus(1);
        param.setFetchSource(EsConstant.REFUND_ORDER_CANAL_TASK_FETCH_SOURCE);
        return handleOrder(param, REFUND_INTERVENTION_TIMEOUT);
    }

    private ServerResponseEntity<Object> handleOrder(OrderRefundSearchDTO param, Integer type) {
        String scrollId = null;
        // 设置查询超时时间
        Scroll scroll = new Scroll(TimeValue.timeValueMinutes(5L));
        SearchRequest searchRequest = orderRefundSearchManager.buildScrollSearchRequest(param, scroll);
        try {
            // 进行第一次滚动查询
            SearchResponse searchResponse = EsSearchUtil.search(searchRequest);
            scrollId = searchResponse.getScrollId();
            /*
             在这个位置已经读到了前一百条数据，可以在这先对这一百数据进行处理。下面滚动查询剩下的数据
             */
            sendOrderMq(searchResponse, type);
            //记录要滚动的ID
            //滚动查询部分，将从第1001笔数据开始取
            SearchHit[] hitsScroll = searchResponse.getHits().getHits();
            while (hitsScroll != null && hitsScroll.length > 0) {
                //构造滚动查询条件
                SearchScrollRequest searchScrollRequest = new SearchScrollRequest(scrollId);
                searchScrollRequest.scroll(scroll);
                searchResponse = EsSearchUtil.scroll(searchScrollRequest);
                hitsScroll = searchResponse.getHits().getHits();
                scrollId = searchResponse.getScrollId();
                // 发送消息
                sendOrderMq(searchResponse, type);
            }
        } catch (Exception e) {
            logger.error("退款订单定时任务异常", e);
            return ServerResponseEntity.fail(ResponseEnum.EXCEPTION);
        } finally {
            if (Objects.nonNull(scrollId)) {
                //清除滚动，否则影响下次查询
                EsSearchUtil.clearScroll(scrollId);
            }
        }
        return ServerResponseEntity.success();
    }

    /**
     * 发送消息
     *
     * @param searchResponse
     * @param type
     */
    private void sendOrderMq(SearchResponse searchResponse, Integer type) {
        if (Objects.equals(type, CANAL_ORDER)) {
            cancelWhenTimeOutMq(searchResponse);
        } else if (Objects.equals(type, TIMEOUT_REMINDER)) {
            pressProximityTimeOutOrderMq(searchResponse);
        } else if (Objects.equals(type, REFUND_REQUEST)) {
            refundRequest(searchResponse);
        } else if (Objects.equals(type, REFUND_INTERVENTION_TIMEOUT)) {
            cancelRefundInterventionWhenTimeOutMq(searchResponse);
        }
    }

    private void pressProximityTimeOutOrderMq(SearchResponse searchResponse) {
        List<Long> orderIds = new ArrayList<>(Constant.INITIAL_CAPACITY);
        SearchHits hits = searchResponse.getHits();
        for (SearchHit hit : hits.getHits()) {
            EsOrderRefundBO orderRefundBO = JSON.parseObject(hit.getSourceAsString(), EsOrderRefundBO.class);
            orderIds.add(orderRefundBO.getOrderId());
        }
        if (CollUtil.isEmpty(orderIds)) {
            return;
        }
        OrderSearchDTO orderSearchDTO = new OrderSearchDTO();
        orderSearchDTO.setOrderIds(orderIds);
        orderSearchDTO.setPageNum(1);
        orderSearchDTO.setPageSize(orderIds.size());
        orderSearchDTO.setFetchSource(null);
        EsPageVO<EsOrderVO> orderPage = orderSearchManager.pageSearchResult(orderSearchDTO);
        List<SendNotifyBO> sendNotifyList = new ArrayList<>(Constant.INITIAL_CAPACITY);
        for (EsOrderVO esOrderVO : orderPage.getList()) {
            SendNotifyBO sendNotifyBO = BeanUtil.map(esOrderVO, SendNotifyBO.class);
            sendNotifyBO.setBizId(esOrderVO.getOrderId());
            sendNotifyBO.setSendType(SendTypeEnum.REFUND_OUT_TIME.getValue());
            sendNotifyBO.setHour(Constant.MAX_REFUND_APPLY_TIME);
            sendNotifyList.add(sendNotifyBO);
        }
        // 消息推送-退款临近超时
        SendStatus sendBizStatus = sendNotifyToUserTemplate.syncSend(RocketMqConstant.SEND_NOTIFY_TO_USER_TOPIC, new GenericMessage<>(sendNotifyList)).getSendStatus();
        if (!Objects.equals(sendBizStatus, SendStatus.SEND_OK)) {
            // 消息发不出去就抛异常，因为定时任务回调会有多次，几乎不可能每次都无法发送出去，发的出去无所谓因为接口是幂等的
            throw new LuckException(ResponseEnum.EXCEPTION);
        }
    }

    private void cancelWhenTimeOutMq(SearchResponse searchResponse) {
        List<Long> refundIds = getRefundIds(searchResponse);
        if (CollUtil.isEmpty(refundIds)) {
            return;
        }
        // 发送消息，取消申请超时的订单，无论该超时订单处于任何状态
        SendStatus sendStatus = orderRefundCancelMqTemplate.syncSend(RocketMqConstant.ORDER_REFUND_CANAL_TOPIC, new GenericMessage<>(refundIds)).getSendStatus();
        if (!Objects.equals(sendStatus, SendStatus.SEND_OK)) {
            // 消息发不出去就抛异常，因为定时任务回调会有多次，几乎不可能每次都无法发送出去，发的出去无所谓因为接口是幂等的
            throw new LuckException(ResponseEnum.EXCEPTION);
        }
    }

    private void cancelRefundInterventionWhenTimeOutMq(SearchResponse searchResponse) {
        List<Long> refundIds = getRefundIds(searchResponse);
        if (CollUtil.isEmpty(refundIds)) {
            return;
        }
        // 发送消息，平台介入超时自动拒绝
        SendStatus sendStatus = orderRefundCancelMqTemplate.syncSend(RocketMqConstant.REFUND_INTERVENTION_CANAL_TOPIC, new GenericMessage<>(refundIds)).getSendStatus();
        if (!Objects.equals(sendStatus, SendStatus.SEND_OK)) {
            // 消息发不出去就抛异常，因为定时任务回调会有多次，几乎不可能每次都无法发送出去，发的出去无所谓因为接口是幂等的
            throw new LuckException(ResponseEnum.EXCEPTION);
        }
    }

    private static List<Long> getRefundIds(SearchResponse searchResponse) {
        List<Long> refundIds = new ArrayList<>(Constant.INITIAL_CAPACITY);
        for (SearchHit hit : searchResponse.getHits().getHits()) {
            EsOrderRefundBO orderRefundBO = JSON.parseObject(hit.getSourceAsString(), EsOrderRefundBO.class);
            refundIds.add(orderRefundBO.getRefundId());
        }
        return refundIds;
    }

    private void refundRequest(SearchResponse searchResponse) {
        List<Long> refundIds = getRefundIds(searchResponse);
        if (CollUtil.isEmpty(refundIds)) {
            return;
        }
        // 发送消息，发放退款
        SendStatus sendStatus = orderRefundRequstMqTemplate.syncSend(RocketMqConstant.ORDER_REFUND_REQUEST_TOPIC, new GenericMessage<>(refundIds)).getSendStatus();
        if (!Objects.equals(sendStatus, SendStatus.SEND_OK)) {
            // 消息发不出去就抛异常，因为定时任务回调会有多次，几乎不可能每次都无法发送出去，发的出去无所谓因为接口是幂等的
            throw new LuckException(ResponseEnum.EXCEPTION);
        }
    }

}
