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

import com.liquidnet.common.cache.redis.util.RedisDataSourceUtil;
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.kylin.utils.GroupBuyRobotUtil;
import com.liquidnet.service.platform.utils.GroupBuyPlatformHelper;
import com.liquidnet.service.platform.utils.GroupBuyRobotHelper;
import com.liquidnet.service.platform.utils.GroupBuySmsSender;
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.ArrayList;
import java.util.List;

@Service
@Slf4j
public class PlatformGroupBuyTaskServiceImpl {

    @Autowired
    private MongoTemplate mongoTemplate;
    @Autowired
    private RedisDataSourceUtil redisDataSourceUtil;
    @Autowired
    private QueueUtils queueUtils;
    @Autowired
    private GroupBuyPlatformHelper groupBuyPlatformHelper;
    @Autowired
    private GroupBuySmsSender groupBuySmsSender;
    @Autowired
    private GroupBuyRobotHelper groupBuyRobotHelper;

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

    /**
     * 拼团机器人：先预约，再到点执行。
     */
    public boolean robot() {
        if (!groupBuyRobotHelper.isEnabled()) {
            log.info("拼团机器人任务跳过：开关关闭");
            return true;
        }
        int scheduled = 0;
        int executed = 0;
        int skipped = 0;
        List<KylinGroupBuyOrderVo> scheduleList = groupBuyRobotHelper.findScheduleCandidates();
        for (KylinGroupBuyOrderVo order : scheduleList) {
            String groupOrderId = order.getGroupOrderId();
            if (!groupBuyPlatformHelper.tryLock(groupOrderId)) {
                skipped++;
                continue;
            }
            try {
                KylinGroupBuyOrderVo fresh = groupBuyPlatformHelper.loadFromMongo(groupOrderId);
                if (fresh == null) {
                    skipped++;
                    continue;
                }
                Integer before = fresh.getRobotPlanStatus();
                groupBuyRobotHelper.tryScheduleOne(fresh);
                KylinGroupBuyOrderVo after = groupBuyPlatformHelper.loadFromMongo(groupOrderId);
                if (after != null && after.getRobotPlanStatus() != null
                        && !after.getRobotPlanStatus().equals(before == null
                        ? GroupBuyStatusConst.RobotPlanStatus.NONE : before)) {
                    scheduled++;
                }
            } catch (Exception e) {
                log.error("拼团机器人预约失败 groupOrderId={}", groupOrderId, e);
            } finally {
                groupBuyPlatformHelper.unlock(groupOrderId);
            }
        }
        String now = now();
        List<KylinGroupBuyOrderVo> executeList = groupBuyRobotHelper.findExecuteCandidates(now);
        for (KylinGroupBuyOrderVo order : executeList) {
            String groupOrderId = order.getGroupOrderId();
            if (!groupBuyPlatformHelper.tryLock(groupOrderId)) {
                skipped++;
                continue;
            }
            try {
                KylinGroupBuyOrderVo fresh = groupBuyPlatformHelper.loadFromMongo(groupOrderId);
                groupBuyRobotHelper.tryExecuteOne(fresh, now);
                executed++;
            } catch (Exception e) {
                log.error("拼团机器人执行失败 groupOrderId={}", groupOrderId, e);
            } finally {
                groupBuyPlatformHelper.unlock(groupOrderId);
            }
        }
        log.info("拼团机器人任务结束 scheduleHit={}, scheduled={}, executeHit={}, executed={}, skipped={}",
                scheduleList == null ? 0 : scheduleList.size(), scheduled,
                executeList == null ? 0 : executeList.size(), executed, skipped);
        return true;
    }

    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());
        int processed = 0;
        int skipped = 0;
        for (KylinGroupBuyOrderVo order : orders) {
            String groupOrderId = order.getGroupOrderId();
            if (!groupBuyPlatformHelper.tryLock(groupOrderId)) {
                log.warn("拼团超时任务获取锁失败 groupOrderId={}", groupOrderId);
                skipped++;
                continue;
            }
            try {
                KylinGroupBuyOrderVo fresh = groupBuyPlatformHelper.loadFromMongo(groupOrderId);
                if (fresh == null || !GroupBuyStatusConst.OrderStatus.PENDING.equals(fresh.getStatus())) {
                    skipped++;
                    continue;
                }
                if (fresh.getExpiresAt() == null
                        || !DateUtil.Formatter.yyyyMMddHHmmss.parse(fresh.getExpiresAt()).isBefore(LocalDateTime.now())) {
                    skipped++;
                    continue;
                }
                fresh.setStatus(GroupBuyStatusConst.OrderStatus.FAILED);
                fresh.setUpdatedAt(now);
                GroupBuyRobotUtil.cancelPendingPlan(fresh);
                // 失败短信需在 JOINED 改成 REFUNDED 之前发送
                try {
                    groupBuySmsSender.notifyFailed(fresh);
                } catch (Exception e) {
                    log.error("拼团失败短信通知失败 groupOrderId={}", groupOrderId, e);
                }
                List<String> joinedOrderTicketIds = new ArrayList<>();
                if (!CollectionUtils.isEmpty(fresh.getMembers())) {
                    for (KylinGroupBuyMemberVo member : fresh.getMembers()) {
                        if (GroupBuyStatusConst.MemberStatus.PENDING_PAY.equals(member.getStatus())) {
                            member.setStatus(GroupBuyStatusConst.MemberStatus.CANCELLED);
                        } else if (GroupBuyStatusConst.MemberStatus.JOINED.equals(member.getStatus())) {
                            if (!GroupBuyRobotUtil.isRobot(member)) {
                                joinedOrderTicketIds.add(member.getOrderTicketsId());
                            }
                            member.setStatus(GroupBuyStatusConst.MemberStatus.REFUNDED);
                        }
                    }
                }
                fresh.setCurrentCount(0);
                groupBuyPlatformHelper.saveGroup(fresh);
                for (String orderTicketsId : joinedOrderTicketIds) {
                    log.info("[timeout] 拼团超时，订单退款: {}", orderTicketsId);
                    refundJoinedTicket(orderTicketsId, "拼团超时未成团，全额退款");
                }
                processed++;
            } catch (Exception e) {
                log.error("拼团超时处理失败 groupOrderId={}", groupOrderId, e);
            } finally {
                groupBuyPlatformHelper.unlock(groupOrderId);
            }
        }
        log.info("拼团超时任务结束 now={}, hit={}, processed={}, skipped={}",
                now, orders == null ? 0 : orders.size(), processed, skipped);
        return true;
    }

    /**
     * 到期前 1 小时提醒：每个团只发一次（remind1hSent + Redis 防重）。
     */
    public boolean remind1h() {
        LocalDateTime nowLdt = LocalDateTime.now();
        String now = DateUtil.Formatter.yyyyMMddHHmmss.format(nowLdt);
        String deadline = DateUtil.Formatter.yyyyMMddHHmmss.format(nowLdt.plusHours(1));
        Query query = Query.query(Criteria.where("status").is(GroupBuyStatusConst.OrderStatus.PENDING)
                .and("expiresAt").gt(now).lte(deadline)
                .and("remind1hSent").ne(1));
        List<KylinGroupBuyOrderVo> orders = mongoTemplate.find(query,
                KylinGroupBuyOrderVo.class, KylinGroupBuyOrderVo.class.getSimpleName());
        int processed = 0;
        int skipped = 0;
        for (KylinGroupBuyOrderVo order : orders) {
            String groupOrderId = order.getGroupOrderId();
            if (!groupBuyPlatformHelper.tryLock(groupOrderId)) {
                log.warn("拼团1h提醒获取锁失败 groupOrderId={}", groupOrderId);
                skipped++;
                continue;
            }
            try {
                KylinGroupBuyOrderVo fresh = groupBuyPlatformHelper.loadFromMongo(groupOrderId);
                if (fresh == null || !GroupBuyStatusConst.OrderStatus.PENDING.equals(fresh.getStatus())) {
                    skipped++;
                    continue;
                }
                if (groupBuySmsSender.isRemind1hAlreadySent(fresh)) {
                    skipped++;
                    continue;
                }
                if (fresh.getExpiresAt() == null) {
                    skipped++;
                    continue;
                }
                LocalDateTime expiresAt = DateUtil.Formatter.yyyyMMddHHmmss.parse(fresh.getExpiresAt());
                if (!expiresAt.isAfter(nowLdt) || expiresAt.isAfter(nowLdt.plusHours(1))) {
                    skipped++;
                    continue;
                }
                try {
                    groupBuySmsSender.notifyRemind1h(fresh);
                } catch (Exception e) {
                    log.error("拼团1h提醒短信失败 groupOrderId={}", groupOrderId, e);
                }
                fresh.setRemind1hSent(1);
                fresh.setUpdatedAt(now);
                groupBuyPlatformHelper.saveGroup(fresh);
                processed++;
            } catch (Exception e) {
                log.error("拼团1h提醒处理失败 groupOrderId={}", groupOrderId, e);
            } finally {
                groupBuyPlatformHelper.unlock(groupOrderId);
            }
        }
        log.info("拼团1h提醒任务结束 now={}, deadline={}, hit={}, processed={}, skipped={}",
                now, deadline, orders == null ? 0 : orders.size(), processed, skipped);
        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);
        }
        log.info("拼团活动调度结束 now={}, started={}, ended={}", now,
                dueStartList.size(),
                dueEndList.size());
        return true;
    }

    private void cacheActivity(KylinGroupBuyActivityVo activity) {
        if (activity == null || activity.getActivityId() == null) {
            return;
        }
        String key = KylinRedisConst.GROUP_BUY_ACTIVITY.concat(activity.getActivityId());
        redisDataSourceUtil.getRedisKylinUtil().del(key);
        redisDataSourceUtil.getRedisKylinUtil().set(key, activity, RedisKeyExpireConst.KYLIN_GROUP_BUY_EXPIRE);
    }

    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 refundJoinedTicket(String orderTicketsId, String reason) {
        KylinOrderTicketVo orderTicketVo = mongoTemplate.findOne(
                Query.query(Criteria.where("orderTicketsId").is(orderTicketsId)),
                KylinOrderTicketVo.class, KylinOrderTicketVo.class.getSimpleName());
        if (orderTicketVo == null) {
            return;
        }
        List<KylinOrderTicketEntitiesVo> entities = orderTicketVo.getEntitiesVoList();
        if (CollectionUtils.isEmpty(entities)) {
            entities = mongoTemplate.find(
                    Query.query(Criteria.where("orderId").is(orderTicketVo.getOrderTicketsId())),
                    KylinOrderTicketEntitiesVo.class, KylinOrderTicketEntitiesVo.class.getSimpleName());
        }
        if (CollectionUtils.isEmpty(entities)) {
            return;
        }
        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);
            MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
            headers.add("Accept", "application/json;charset=UTF-8");
            // 走 kylin /inner/**（鉴权白名单），避免 C 端 sendOrderRefunds 无登录态被拒
            String resp = HttpUtil.post(kylinUrl + "/inner/orderRefund/systemFullRefund", params, headers);
            log.info("拼团自动退款返回 orderId={}, entityId={}, resp={}", orderId, orderTicketEntitiesId, resp);
            // ResponseDto 成功码为 "0"
            if (resp == null || !(resp.contains("\"code\":\"0\"") || resp.contains("\"code\": \"0\""))) {
                log.error("拼团自动退款业务失败 orderId={}, entityId={}, resp={}", orderId, orderTicketEntitiesId, resp);
            }
        } catch (Exception e) {
            log.error("拼团自动退款失败 orderId={}, entityId={}", orderId, orderTicketEntitiesId, e);
        }
    }

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