package com.liquidnet.service.platform.service.impl.kylin;

import com.liquidnet.common.cache.redis.util.RedisUtil;
import com.liquidnet.commons.lang.util.DateUtil;
import com.liquidnet.commons.lang.util.HttpUtil;
import com.liquidnet.service.base.SqlMapping;
import com.liquidnet.service.base.constant.MQConst;
import com.liquidnet.service.base.constant.RedisKeyExpireConst;
import com.liquidnet.service.kylin.constant.GroupBuyStatusConst;
import com.liquidnet.service.kylin.constant.KylinRedisConst;
import com.liquidnet.service.kylin.dto.vo.groupbuy.KylinGroupBuyActivityVo;
import com.liquidnet.service.kylin.dto.vo.groupbuy.KylinGroupBuyMemberVo;
import com.liquidnet.service.kylin.dto.vo.groupbuy.KylinGroupBuyOrderVo;
import com.liquidnet.service.kylin.dto.vo.mongo.KylinOrderTicketEntitiesVo;
import com.liquidnet.service.kylin.dto.vo.mongo.KylinOrderTicketVo;
import com.liquidnet.service.platform.utils.QueueUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;

import java.time.LocalDateTime;
import java.util.List;

@Service
@Slf4j
public class PlatformGroupBuyTaskServiceImpl {

    @Autowired
    private MongoTemplate mongoTemplate;
    @Autowired
    private RedisUtil redisUtil;
    @Autowired
    private QueueUtils queueUtils;

    @Value("${liquidnet.service.kylin.url}")
    private String kylinUrl;

    public boolean timeout() {
        String now = now();
        Query query = Query.query(Criteria.where("status").is(GroupBuyStatusConst.OrderStatus.PENDING)
                .and("expiresAt").lt(now));
        List<KylinGroupBuyOrderVo> orders = mongoTemplate.find(query,
                KylinGroupBuyOrderVo.class, KylinGroupBuyOrderVo.class.getSimpleName());
        for (KylinGroupBuyOrderVo order : orders) {
            Query cas = Query.query(Criteria.where("groupOrderId").is(order.getGroupOrderId())
                    .and("status").is(GroupBuyStatusConst.OrderStatus.PENDING)
                    .and("expiresAt").lt(now));
            Update update = Update.update("status", GroupBuyStatusConst.OrderStatus.FAILED)
                    .set("updatedAt", now);
            if (mongoTemplate.updateFirst(cas, update,
                    KylinGroupBuyOrderVo.class, KylinGroupBuyOrderVo.class.getSimpleName()).getModifiedCount() == 1) {
                order.setStatus(GroupBuyStatusConst.OrderStatus.FAILED);
                order.setUpdatedAt(now);
                sendOrderStatusSql(order.getGroupOrderId(), GroupBuyStatusConst.OrderStatus.PENDING,
                        GroupBuyStatusConst.OrderStatus.FAILED, LocalDateTime.now());
                cacheOrder(order);
                refundMembers(order, "拼团超时未成团，全额退款");
            }
        }
        return true;
    }

    public boolean activitySchedule() {
        String now = now();
        List<KylinGroupBuyActivityVo> dueStartList = mongoTemplate.find(Query.query(Criteria.where("status").is(GroupBuyStatusConst.ActivityStatus.PENDING)
                        .and("launchType").is(GroupBuyStatusConst.LaunchType.SCHEDULED)
                        .and("scheduledTime").lte(now)),
                KylinGroupBuyActivityVo.class, KylinGroupBuyActivityVo.class.getSimpleName());
        mongoTemplate.updateMulti(Query.query(Criteria.where("status").is(GroupBuyStatusConst.ActivityStatus.PENDING)
                        .and("launchType").is(GroupBuyStatusConst.LaunchType.SCHEDULED)
                        .and("scheduledTime").lte(now)),
                Update.update("status", GroupBuyStatusConst.ActivityStatus.ACTIVE).set("updatedAt", now),
                "KylinGroupBuyActivityVo");
        for (KylinGroupBuyActivityVo activity : dueStartList) {
            activity.setStatus(GroupBuyStatusConst.ActivityStatus.ACTIVE);
            activity.setUpdatedAt(now);
            sendActivityStatusSql(activity.getActivityId(), GroupBuyStatusConst.ActivityStatus.PENDING,
                    GroupBuyStatusConst.ActivityStatus.ACTIVE, LocalDateTime.now());
            cacheActivity(activity);
        }
        List<KylinGroupBuyActivityVo> dueEndList = mongoTemplate.find(Query.query(Criteria.where("status")
                        .in(GroupBuyStatusConst.ActivityStatus.ACTIVE, GroupBuyStatusConst.ActivityStatus.CLOSED)
                        .and("endTime").lte(now)),
                KylinGroupBuyActivityVo.class, KylinGroupBuyActivityVo.class.getSimpleName());
        mongoTemplate.updateMulti(Query.query(Criteria.where("status")
                        .in(GroupBuyStatusConst.ActivityStatus.ACTIVE, GroupBuyStatusConst.ActivityStatus.CLOSED)
                        .and("endTime").lte(now)),
                Update.update("status", GroupBuyStatusConst.ActivityStatus.ENDED).set("updatedAt", now),
                "KylinGroupBuyActivityVo");
        for (KylinGroupBuyActivityVo activity : dueEndList) {
            Integer fromStatus = activity.getStatus();
            activity.setStatus(GroupBuyStatusConst.ActivityStatus.ENDED);
            activity.setUpdatedAt(now);
            sendActivityStatusSql(activity.getActivityId(), fromStatus,
                    GroupBuyStatusConst.ActivityStatus.ENDED, LocalDateTime.now());
            cacheActivity(activity);
        }
        return true;
    }

    private void cacheOrder(KylinGroupBuyOrderVo order) {
        redisUtil.del(KylinRedisConst.GROUP_BUY_ORDER.concat(order.getGroupOrderId()));
        redisUtil.del(KylinRedisConst.GROUP_BUY_ORDER_SHARE.concat(order.getShareToken()));
        redisUtil.set(KylinRedisConst.GROUP_BUY_ORDER.concat(order.getGroupOrderId()), order, RedisKeyExpireConst.KYLIN_GROUP_BUY_EXPIRE);
        redisUtil.set(KylinRedisConst.GROUP_BUY_ORDER_SHARE.concat(order.getShareToken()), order, RedisKeyExpireConst.KYLIN_GROUP_BUY_EXPIRE);
    }

    private void cacheActivity(KylinGroupBuyActivityVo activity) {
        redisUtil.del(KylinRedisConst.GROUP_BUY_ACTIVITY.concat(activity.getActivityId()));
        redisUtil.set(KylinRedisConst.GROUP_BUY_ACTIVITY.concat(activity.getActivityId()), activity, RedisKeyExpireConst.KYLIN_GROUP_BUY_EXPIRE);
    }

    private void sendOrderStatusSql(String groupOrderId, Integer fromStatus, Integer toStatus, LocalDateTime updatedAt) {
        queueUtils.sendMsgByRedis(MQConst.KylinQueue.SQL_ORDER_OVERTIME_REFUND.getKey(),
                SqlMapping.get("kylin_group_buy_order.update_status", toStatus, updatedAt, groupOrderId, fromStatus));
    }

    private void sendActivityStatusSql(String activityId, Integer fromStatus, Integer toStatus, LocalDateTime updatedAt) {
        queueUtils.sendMsgByRedis(MQConst.SlimeQueue.SQL_SLIME_FIELD.getKey(),
                SqlMapping.get("kylin_group_buy_activity.update_status", toStatus, updatedAt, activityId, fromStatus));
    }

    private void refundMembers(KylinGroupBuyOrderVo order, String reason) {
        if (CollectionUtils.isEmpty(order.getMembers())) {
            return;
        }
        for (KylinGroupBuyMemberVo member : order.getMembers()) {
            KylinOrderTicketVo orderTicketVo = mongoTemplate.findOne(
                    Query.query(Criteria.where("orderTicketsId").is(member.getOrderTicketsId())),
                    KylinOrderTicketVo.class, KylinOrderTicketVo.class.getSimpleName());
            if (orderTicketVo == null) {
                continue;
            }
            List<KylinOrderTicketEntitiesVo> entities = mongoTemplate.find(
                    Query.query(Criteria.where("orderId").is(orderTicketVo.getOrderTicketsId())),
                    KylinOrderTicketEntitiesVo.class, KylinOrderTicketEntitiesVo.class.getSimpleName());
            for (KylinOrderTicketEntitiesVo entity : entities) {
                autoRefund(orderTicketVo.getOrderTicketsId(), entity.getOrderTicketEntitiesId(), reason);
            }
        }
    }

    private void autoRefund(String orderId, String orderTicketEntitiesId, String reason) {
        try {
            MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
            params.add("orderId", orderId);
            params.add("orderTicketEntitiesId", orderTicketEntitiesId);
            params.add("reason", reason);
            params.add("sendExpressType", "0");
            params.add("skipCharges", "true");
            params.add("autoApproved", "true");
            MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
            headers.add("Accept", "application/json;charset=UTF-8");
            HttpUtil.post(kylinUrl + "/orderRefund/sendOrderRefunds", params, headers);
        } catch (Exception e) {
            log.error("拼团自动退款失败 orderId={}, entityId={}", orderId, orderTicketEntitiesId, e);
        }
    }

    private String now() {
        return DateUtil.Formatter.yyyyMMddHHmmss.format(LocalDateTime.now());
    }
}
