package com.liquidnet.service.platform.utils;

import com.liquidnet.common.cache.redis.util.RedisDataSourceUtil;
import com.liquidnet.commons.lang.util.DateUtil;
import com.liquidnet.commons.lang.util.IDGenerator;
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.groupbuy.KylinGroupBuyRobotTaskVo;
import com.liquidnet.service.kylin.utils.GroupBuyRobotUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
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.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.time.Duration;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;

/**
 * 拼团机器人：开关、预约规则、到点入团、成团通知。
 */
@Component
@Slf4j
public class GroupBuyRobotHelper {

    private static final int WAIT_HOURS = 6;
    private static final int ACTIVITY_SHORT_HOURS = 24;
    private static final LocalTime NIGHT_START = LocalTime.of(1, 0);
    private static final LocalTime NIGHT_END = LocalTime.of(9, 0);

    public static final String DEF_URL_AVATAR = "https://img.zhengzai.tv/user/2021/07/27/a4cc2a4e6dcd44d1812dc60e079086b4.png";

    @Autowired
    private MongoTemplate mongoTemplate;
    @Autowired
    private RedisDataSourceUtil redisDataSourceUtil;
    @Autowired
    private GroupBuyPlatformHelper groupBuyPlatformHelper;
    @Autowired
    private GroupBuySmsSender groupBuySmsSender;

    public boolean isEnabled() {
        Object v = redisDataSourceUtil.getRedisKylinUtil().get(KylinRedisConst.GROUP_BUY_ROBOT_ENABLED);
        if (v == null) {
            return true;
        }
        String s = String.valueOf(v).trim();
        return !("0".equals(s) || "false".equalsIgnoreCase(s));
    }

    public List<KylinGroupBuyOrderVo> findScheduleCandidates() {
        Criteria planNone = new Criteria().orOperator(
                Criteria.where("robotPlanStatus").exists(false),
                Criteria.where("robotPlanStatus").is(null),
                Criteria.where("robotPlanStatus").is(GroupBuyStatusConst.RobotPlanStatus.NONE)
        );
        Query query = Query.query(Criteria.where("status").is(GroupBuyStatusConst.OrderStatus.PENDING)
                .and("isPublic").is(GroupBuyStatusConst.PublicFlag.PUBLIC)
                .andOperator(planNone));
        return mongoTemplate.find(query, KylinGroupBuyOrderVo.class, KylinGroupBuyOrderVo.class.getSimpleName());
    }

    public List<KylinGroupBuyOrderVo> findExecuteCandidates(String now) {
        Query query = Query.query(Criteria.where("status").is(GroupBuyStatusConst.OrderStatus.PENDING)
                .and("robotPlanStatus").is(GroupBuyStatusConst.RobotPlanStatus.SCHEDULED)
                .and("robotTasks").elemMatch(
                        Criteria.where("status").is(GroupBuyStatusConst.RobotTaskStatus.PENDING)
                                .and("executeAt").lte(now)));
        return mongoTemplate.find(query, KylinGroupBuyOrderVo.class, KylinGroupBuyOrderVo.class.getSimpleName());
    }

    /**
     * 对单个团尝试预约；立即成团场景会直接补人。
     */
    public void tryScheduleOne(KylinGroupBuyOrderVo locked) {
        if (locked == null || !GroupBuyStatusConst.OrderStatus.PENDING.equals(locked.getStatus())) {
            return;
        }
        if (!GroupBuyStatusConst.PublicFlag.PUBLIC.equals(locked.getIsPublic())) {
            return;
        }
        if (!GroupBuyRobotUtil.isRobotPlanPending(locked)) {
            return;
        }
        int remaining = remainingSeats(locked);
        if (remaining <= 0) {
            return;
        }
        KylinGroupBuyActivityVo activity = loadActivity(locked.getActivityId());
        if (activity == null || StringUtils.isBlank(activity.getEndTime())) {
            log.warn("拼团机器人预约跳过：活动不可用 groupOrderId={}, activityId={}",
                    locked.getGroupOrderId(), locked.getActivityId());
            return;
        }
        LocalDateTime nowLdt = LocalDateTime.now();
        LocalDateTime activityEnd = DateUtil.Formatter.yyyyMMddHHmmss.parse(activity.getEndTime());
        if (!activityEnd.isAfter(nowLdt)) {
            return;
        }
        LocalDateTime expiresAt = parseOrNull(locked.getExpiresAt());
        if (expiresAt == null || !expiresAt.isAfter(nowLdt)) {
            return;
        }
        LocalDateTime hardDeadline = activityEnd.isBefore(expiresAt) ? activityEnd : expiresAt;
        if (!hardDeadline.isAfter(nowLdt)) {
            return;
        }
        LocalDateTime openAt = expiresAt.minusHours(24);
        Duration activityRemainNow = Duration.between(nowLdt, activityEnd);
        Duration activityRemainAtOpen = Duration.between(openAt, activityEnd);
        Integer target = locked.getTargetCount() == null ? 0 : locked.getTargetCount();

        if (target == 2) {
            scheduleDual(locked, nowLdt, openAt, hardDeadline, activityRemainNow, activityRemainAtOpen);
        } else if (target >= 3) {
            scheduleMulti(locked, nowLdt, openAt, hardDeadline, activityRemainNow, remaining);
        }
    }

    private void scheduleDual(KylinGroupBuyOrderVo group, LocalDateTime nowLdt, LocalDateTime openAt,
                              LocalDateTime hardDeadline, Duration activityRemainNow, Duration activityRemainAtOpen) {
        boolean earlyByActivityOpen = activityRemainAtOpen.compareTo(Duration.ofHours(WAIT_HOURS)) < 0;
        boolean waited6h = !nowLdt.isBefore(openAt.plusHours(WAIT_HOURS));
        if (!earlyByActivityOpen && !waited6h) {
            return;
        }
        // 启动后：活动剩余 < 24h → 立即成团
        if (activityRemainNow.compareTo(Duration.ofHours(ACTIVITY_SHORT_HOURS)) < 0) {
            joinRobotsUntilFull(group, 1, true);
            return;
        }
        LocalDateTime windowStart = nowLdt;
        if (isInNightWindow(windowStart)) {
            LocalDateTime after9 = windowStart.toLocalDate().atTime(NIGHT_END);
            if (after9.isBefore(hardDeadline)) {
                windowStart = after9;
            }
        }
        List<LocalDateTime> points = randomPoints(windowStart, hardDeadline, 1, true);
        if (points.isEmpty()) {
            points = randomPoints(nowLdt, hardDeadline, 1, false);
        }
        if (points.isEmpty()) {
            joinRobotsUntilFull(group, 1, true);
            return;
        }
        saveScheduledTasks(group, points);
    }

    private void scheduleMulti(KylinGroupBuyOrderVo group, LocalDateTime nowLdt, LocalDateTime openAt,
                               LocalDateTime hardDeadline, Duration activityRemainNow, int remaining) {
        boolean activityShort = activityRemainNow.compareTo(Duration.ofHours(ACTIVITY_SHORT_HOURS)) < 0;
        if (!activityShort) {
            boolean waited6h = !nowLdt.isBefore(openAt.plusHours(WAIT_HOURS));
            if (!waited6h) {
                return;
            }
        }
        LocalDateTime windowStart = nowLdt;
        if (isInNightWindow(windowStart)) {
            LocalDateTime after9 = windowStart.toLocalDate().atTime(NIGHT_END);
            if (after9.isBefore(nowLdt) || after9.isEqual(nowLdt)) {
                after9 = after9.plusDays(1);
            }
            if (after9.isBefore(hardDeadline)) {
                windowStart = after9;
            }
        }
        List<LocalDateTime> points = randomPoints(windowStart, hardDeadline, remaining, true);
        if (points.isEmpty()) {
            points = randomPoints(nowLdt, hardDeadline, remaining, false);
        }
        if (points.isEmpty()) {
            joinRobotsUntilFull(group, remaining, true);
            return;
        }
        saveScheduledTasks(group, points);
    }

    private void saveScheduledTasks(KylinGroupBuyOrderVo group, List<LocalDateTime> points) {
        List<KylinGroupBuyRobotTaskVo> tasks = new ArrayList<>();
        for (LocalDateTime point : points) {
            KylinGroupBuyRobotTaskVo task = new KylinGroupBuyRobotTaskVo();
            task.setExecuteAt(DateUtil.Formatter.yyyyMMddHHmmss.format(point));
            task.setStatus(GroupBuyStatusConst.RobotTaskStatus.PENDING);
            tasks.add(task);
        }
        group.setRobotTasks(tasks);
        group.setRobotPlanStatus(GroupBuyStatusConst.RobotPlanStatus.SCHEDULED);
        group.setUpdatedAt(now());
        groupBuyPlatformHelper.saveGroup(group);
        log.info("拼团机器人已预约 groupOrderId={}, tasks={}", group.getGroupOrderId(), tasks.size());
    }

    /**
     * 执行到期任务：每次入 1 个机器人；满员成团。
     */
    public void tryExecuteOne(KylinGroupBuyOrderVo locked, String now) {
        if (locked == null) {
            return;
        }
        if (!GroupBuyStatusConst.OrderStatus.PENDING.equals(locked.getStatus())
                || remainingSeats(locked) <= 0
                || !GroupBuyStatusConst.PublicFlag.PUBLIC.equals(locked.getIsPublic())) {
            GroupBuyRobotUtil.cancelPendingPlan(locked);
            locked.setUpdatedAt(now());
            groupBuyPlatformHelper.saveGroup(locked);
            return;
        }
        if (CollectionUtils.isEmpty(locked.getRobotTasks())) {
            return;
        }
        boolean changed = false;
        boolean success = false;
        for (KylinGroupBuyRobotTaskVo task : locked.getRobotTasks()) {
            if (task == null || !GroupBuyStatusConst.RobotTaskStatus.PENDING.equals(task.getStatus())) {
                continue;
            }
            if (StringUtils.isBlank(task.getExecuteAt()) || task.getExecuteAt().compareTo(now) > 0) {
                continue;
            }
            if (remainingSeats(locked) <= 0
                    || !GroupBuyStatusConst.OrderStatus.PENDING.equals(locked.getStatus())) {
                task.setStatus(GroupBuyStatusConst.RobotTaskStatus.CANCELLED);
                changed = true;
                continue;
            }
            addOneRobot(locked);
            task.setStatus(GroupBuyStatusConst.RobotTaskStatus.DONE);
            changed = true;
            if (locked.getCurrentCount() != null && locked.getTargetCount() != null
                    && locked.getCurrentCount() >= locked.getTargetCount()) {
                locked.setStatus(GroupBuyStatusConst.OrderStatus.SUCCESS);
                GroupBuyRobotUtil.cancelPendingTasks(locked);
                locked.setRobotPlanStatus(GroupBuyStatusConst.RobotPlanStatus.DONE);
                success = true;
                break;
            }
        }
        if (!changed) {
            return;
        }
        if (!success && allTasksFinished(locked)) {
            locked.setRobotPlanStatus(GroupBuyStatusConst.RobotPlanStatus.DONE);
        }
        locked.setUpdatedAt(now());
        groupBuyPlatformHelper.saveGroup(locked);
        if (success) {
            try {
                groupBuySmsSender.notifySuccess(locked);
            } catch (Exception e) {
                log.error("拼团机器人成团短信失败 groupOrderId={}", locked.getGroupOrderId(), e);
            }
        }
    }

    private void joinRobotsUntilFull(KylinGroupBuyOrderVo group, int maxAdd, boolean immediate) {
        int add = Math.min(maxAdd, remainingSeats(group));
        if (add <= 0) {
            return;
        }
        List<KylinGroupBuyRobotTaskVo> tasks = new ArrayList<>();
        for (int i = 0; i < add; i++) {
            addOneRobot(group);
            KylinGroupBuyRobotTaskVo task = new KylinGroupBuyRobotTaskVo();
            task.setExecuteAt(now());
            task.setStatus(GroupBuyStatusConst.RobotTaskStatus.DONE);
            tasks.add(task);
        }
        group.setRobotTasks(tasks);
        boolean full = group.getCurrentCount() != null && group.getTargetCount() != null
                && group.getCurrentCount() >= group.getTargetCount();
        if (full) {
            group.setStatus(GroupBuyStatusConst.OrderStatus.SUCCESS);
            group.setRobotPlanStatus(GroupBuyStatusConst.RobotPlanStatus.DONE);
        } else {
            group.setRobotPlanStatus(immediate
                    ? GroupBuyStatusConst.RobotPlanStatus.DONE
                    : GroupBuyStatusConst.RobotPlanStatus.SCHEDULED);
        }
        group.setUpdatedAt(now());
        groupBuyPlatformHelper.saveGroup(group);
        log.info("拼团机器人立即补人 groupOrderId={}, add={}, status={}",
                group.getGroupOrderId(), add, group.getStatus());
        if (full) {
            try {
                groupBuySmsSender.notifySuccess(group);
            } catch (Exception e) {
                log.error("拼团机器人立即成团短信失败 groupOrderId={}", group.getGroupOrderId(), e);
            }
        }
    }

    private void addOneRobot(KylinGroupBuyOrderVo group) {
        String memberId = IDGenerator.nextSnowId();
        String now = now();
        KylinGroupBuyMemberVo member = new KylinGroupBuyMemberVo();
        member.setMemberId(memberId);
        member.setGroupOrderId(group.getGroupOrderId());
        member.setUserId(GroupBuyRobotUtil.USER_ID_PREFIX + IDGenerator.nextSnowId());
        member.setOrderTicketsId(GroupBuyRobotUtil.ORDER_TICKETS_ID_PREFIX + memberId);
        member.setTicketCount(0);
        member.setStatus(GroupBuyStatusConst.MemberStatus.JOINED);
        member.setJoinTime(now);
        member.setCreatedAt(now);
        member.setNickname(NknameUtil.randomNkname());
        member.setAvatar(DEF_URL_AVATAR);
        member.setIsRobot(1);
        if (group.getMembers() == null) {
            group.setMembers(new ArrayList<>());
        }
        group.getMembers().add(member);
        int count = group.getCurrentCount() == null ? 0 : group.getCurrentCount();
        group.setCurrentCount(count + 1);
    }

    private boolean allTasksFinished(KylinGroupBuyOrderVo group) {
        if (CollectionUtils.isEmpty(group.getRobotTasks())) {
            return true;
        }
        for (KylinGroupBuyRobotTaskVo task : group.getRobotTasks()) {
            if (task != null && GroupBuyStatusConst.RobotTaskStatus.PENDING.equals(task.getStatus())) {
                return false;
            }
        }
        return true;
    }

    private int remainingSeats(KylinGroupBuyOrderVo group) {
        int target = group.getTargetCount() == null ? 0 : group.getTargetCount();
        int current = group.getCurrentCount() == null ? 0 : group.getCurrentCount();
        return Math.max(0, target - current);
    }

    private KylinGroupBuyActivityVo loadActivity(String activityId) {
        if (StringUtils.isBlank(activityId)) {
            return null;
        }
        Object cached = redisDataSourceUtil.getRedisKylinUtil().get(KylinRedisConst.GROUP_BUY_ACTIVITY.concat(activityId));
        if (cached instanceof KylinGroupBuyActivityVo) {
            return (KylinGroupBuyActivityVo) cached;
        }
        return mongoTemplate.findOne(
                Query.query(Criteria.where("activityId").is(activityId)),
                KylinGroupBuyActivityVo.class, KylinGroupBuyActivityVo.class.getSimpleName());
    }

    /**
     * @param avoidNight 优先避开夜窗；无合法区间时由调用方再试 avoidNight=false
     */
    private List<LocalDateTime> randomPoints(LocalDateTime start, LocalDateTime hardDeadline,
                                             int count, boolean avoidNight) {
        if (count <= 0 || start == null || hardDeadline == null || !hardDeadline.isAfter(start)) {
            return Collections.emptyList();
        }
        List<long[]> ranges = buildRangesSeconds(start, hardDeadline, avoidNight);
        long total = 0;
        for (long[] r : ranges) {
            total += Math.max(0, r[1] - r[0]);
        }
        if (total <= 0) {
            return Collections.emptyList();
        }
        List<LocalDateTime> points = new ArrayList<>();
        ThreadLocalRandom random = ThreadLocalRandom.current();
        for (int i = 0; i < count; i++) {
            long pick = random.nextLong(total);
            long cursor = 0;
            LocalDateTime chosen = null;
            for (long[] r : ranges) {
                long len = r[1] - r[0];
                if (pick < cursor + len) {
                    chosen = start.plusSeconds(r[0] + (pick - cursor));
                    break;
                }
                cursor += len;
            }
            if (chosen == null) {
                chosen = hardDeadline.minusSeconds(1);
            }
            if (!chosen.isBefore(hardDeadline)) {
                chosen = hardDeadline.minusSeconds(1);
            }
            if (chosen.isBefore(start)) {
                chosen = start;
            }
            points.add(chosen);
        }
        points.sort(Comparator.naturalOrder());
        // 保证严格递增且落在窗口内
        LocalDateTime prev = null;
        List<LocalDateTime> adjusted = new ArrayList<>();
        for (LocalDateTime p : points) {
            LocalDateTime cur = p;
            if (prev != null && !cur.isAfter(prev)) {
                cur = prev.plusSeconds(1);
            }
            if (!cur.isBefore(hardDeadline)) {
                break;
            }
            adjusted.add(cur);
            prev = cur;
        }
        return adjusted;
    }

    private List<long[]> buildRangesSeconds(LocalDateTime start, LocalDateTime hardDeadline, boolean avoidNight) {
        List<long[]> ranges = new ArrayList<>();
        long totalSeconds = Duration.between(start, hardDeadline).getSeconds();
        if (totalSeconds <= 0) {
            return ranges;
        }
        if (!avoidNight) {
            ranges.add(new long[]{0, totalSeconds});
            return ranges;
        }
        LocalDateTime cursor = start;
        int guard = 0;
        while (cursor.isBefore(hardDeadline) && guard++ < 48) {
            LocalTime t = cursor.toLocalTime();
            if (!t.isBefore(NIGHT_START) && t.isBefore(NIGHT_END)) {
                cursor = cursor.toLocalDate().atTime(NIGHT_END);
                continue;
            }
            LocalDateTime nextNightStart = t.isBefore(NIGHT_START)
                    ? cursor.toLocalDate().atTime(NIGHT_START)
                    : cursor.toLocalDate().plusDays(1).atTime(NIGHT_START);
            LocalDateTime segmentEnd = nextNightStart.isBefore(hardDeadline) ? nextNightStart : hardDeadline;
            if (cursor.isBefore(segmentEnd)) {
                ranges.add(new long[]{
                        Duration.between(start, cursor).getSeconds(),
                        Duration.between(start, segmentEnd).getSeconds()
                });
            }
            if (segmentEnd.equals(hardDeadline)) {
                break;
            }
            cursor = segmentEnd.toLocalDate().atTime(NIGHT_END);
        }
        return ranges;
    }

    private boolean isInNightWindow(LocalDateTime ldt) {
        LocalTime t = ldt.toLocalTime();
        return !t.isBefore(NIGHT_START) && t.isBefore(NIGHT_END);
    }

    private LocalDateTime parseOrNull(String time) {
        if (StringUtils.isBlank(time)) {
            return null;
        }
        try {
            return DateUtil.Formatter.yyyyMMddHHmmss.parse(time);
        } catch (Exception e) {
            return null;
        }
    }

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