package com.kym.miniapp.jobs;

import com.baomidou.dynamic.datasource.annotation.DS;
import com.kym.common.constant.ResponseEnum;
import com.kym.common.exception.BusinessException;
import com.kym.common.utils.CommUtil;
import com.kym.entity.miniapp.ChargeOrder;
import com.kym.entity.miniapp.delay.DelayChargeOrder;
import com.kym.service.jobs.DelayService;
import com.kym.service.jobs.DelayedItem;
import com.kym.service.miniapp.ChargeOrderService;
import com.kym.service.miniapp.ChargeService;
import jakarta.annotation.PreDestroy;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @author skyline
 * @description 启动充电延迟任务（预约充电）
 * @date 2023-10-08 22:11
 */
@Component("StartChargeDelayJob")
@Slf4j
@DS("db-miniapp")
@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON) // 设置成单例
public class StartChargeDelayJob implements DelayService<DelayChargeOrder> {

    /**
     * 预约订单队列
     */
    private final static DelayQueue<DelayedItem<DelayChargeOrder>> START_DELAY_QUEUE = new DelayQueue<>();
    private final ChargeOrderService chargeOrderService;
    private final ChargeService chargeService;

    private final static int MAX_THREAD_COUNT = 5;
    /**
     * Java 21 虚拟线程池
     * 替代传统的ThreadPoolExecutor，提供更好的I/O密集型任务性能
     */
    private final ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();

    /**
     * 重试列表
     */
    private ArrayList<String> retryList = new ArrayList<>();

    public StartChargeDelayJob(ChargeOrderService chargeOrderService, ChargeService chargeService) {
        this.chargeOrderService = chargeOrderService;
        this.chargeService = chargeService;
    }

    @DS("db-miniapp")
    // 这里不能使用@PostConstruct，在初始化完成后, bean 进入增强阶段, 所以这个阶段的任何AOP都是无效的,https://www.cnblogs.com/eternityz/p/15330069.html
    @EventListener(classes = {ContextRefreshedEvent.class}, id = "StartChargeDelayJob")
    @Async
    public void init() {
        // 队列加载所有充电状态为预约中的订单，按照开始时间排序
        var orderList = chargeOrderService.lambdaQuery()
                .eq(ChargeOrder::getChargeStatus, ChargeOrder.CHARGE_STATUS_预约中)
                .eq(ChargeOrder::getIsBooking, ChargeOrder.IS_BOOKING_是)
                .orderByAsc(ChargeOrder::getStartTime)
                .list();
        var delayChargeOrderList = orderList.stream().map(o -> new DelayChargeOrder()
                        .setStartChargeSeq(o.getStartChargeSeq())
                        .setUserId(o.getUserId())
                        .setConnectorId(o.getConnectorId())
                        .setStartTime(o.getStartTime())
                        .setEndTime(o.getEndTime())
                        .setChargeStatus(o.getChargeStatus()))
                .toList();
        var delayList = delayChargeOrderList.stream().map(delay -> new DelayedItem<>(delay, delay.getStartTime())).toList();
        START_DELAY_QUEUE.addAll(delayList);
        // 开启线程处理队列消息
        processDelayedOrders();
    }

    private void processDelayedOrders() {
        // 启动线程池中的所有线程处理延迟队列
        for (int i = 0; i < MAX_THREAD_COUNT; i++) {
            executor.execute(() -> {
                ThreadLocal<String> threadLocal = new ThreadLocal<>();
                log.info("预约启动充电处理线程：{}", Thread.currentThread().getName());
                while (true) {
                    DelayedItem<DelayChargeOrder> delayedItem = null;
                    try {
                        // 阻塞等待延迟队列中的任务
                        delayedItem = START_DELAY_QUEUE.take();
                        log.info("出队预约充电订单：{}，队列剩余：{}", delayedItem.data.getStartChargeSeq(), START_DELAY_QUEUE.size());
                        // 启动充电
                        var order = delayedItem.data;
                        threadLocal.set(order.getStartChargeSeq());
                        chargeService.queryStartCharge(order.getUserId(), order.getConnectorId(), null, null, false, null, null);
                        log.info("预约充电启动成功:用户:{}，订单号：{}，预约启动时间：{}", order.getUserId(), order.getStartChargeSeq(), order.getStartTime());
                    } catch (Exception e) {
                        if (e instanceof InterruptedException) {
                            log.error("预约充电队列take异常", e);
                            // 线程被中断，退出循环
                            break;
                        } else {
                            log.error("预约启动充电失败，订单号：{}，异常信息：{}", threadLocal.get(), e.getMessage(), e);
                            if (e instanceof BusinessException && (ResponseEnum.PLATFORM_QUERY_TOKEN_EXCEPTION.getCode().equals(((BusinessException) e).getCode()))) {
                                if (retryList.contains(threadLocal.get())) {
                                    log.info("PlatformToken异常，预约订单:{}已重试忽略", threadLocal.get());
                                    // 启动失败将订单状态修改为充电状态已结束，订单状态已确认，结束原因：预约启动失败
                                    updateOrderStatus(threadLocal.get(), ChargeOrder.CHARGE_STATUS_已结束, ChargeOrder.ORDER_STATUS_失败, ChargeOrder.STOP_REASON_预约启动充电失败);
                                    retryList.remove(threadLocal.get());
                                } else {
                                    log.info("PlatformToken异常，预约订单：{}，重试", threadLocal.get());
                                    // token异常就重新放入队列重试
                                    var success = addToDelayQueue(delayedItem);
                                    if (success) {
                                        retryList.add(threadLocal.get());
                                    }
                                }
                            } else {
                                // 启动失败将订单状态修改为充电状态已结束，订单状态已确认，结束原因：预约启动失败
                                updateOrderStatus(threadLocal.get(), ChargeOrder.CHARGE_STATUS_已结束, ChargeOrder.ORDER_STATUS_失败, ChargeOrder.STOP_REASON_预约启动充电失败);
                            }
                        }
                    } finally {
                        threadLocal.remove();
                    }
                }
            });
        }
    }

    private void updateOrderStatus(String startChargeSeq, int chargeStatus, int orderStatus, int stopReason) {
        chargeOrderService.lambdaUpdate()
                .eq(ChargeOrder::getStartChargeSeq, startChargeSeq)
                .set(ChargeOrder::getChargeStatus, chargeStatus)
                .set(ChargeOrder::getOrderStatus, orderStatus)
                .set(ChargeOrder::getStopReason, stopReason)
                .update();
    }

    @Override
    public boolean addToDelayQueue(DelayedItem<DelayChargeOrder> delayedItem) {
        return START_DELAY_QUEUE.add(delayedItem);
    }

    @Override
    public boolean addToDelayQueue(DelayChargeOrder delayChargeOrder) {
        // 如果预约开始时间小于等于当前时间，则将预约开始时间从当前时间延后5S
        if (delayChargeOrder.getStartTime().isBefore(LocalDateTime.now())) {
            delayChargeOrder.setStartTime(LocalDateTime.now().plusSeconds(5));
        }
        DelayedItem<DelayChargeOrder> orderDelayed = new DelayedItem<>(delayChargeOrder, delayChargeOrder.getStartTime());
        return START_DELAY_QUEUE.add(orderDelayed);
    }

    @Override
    public boolean removeFromDelayQueue(Object startChargeSeq) {
        if (CommUtil.isEmptyOrNull(startChargeSeq)) {
            return false;
        }
        return START_DELAY_QUEUE.removeIf(queue -> queue.data.getStartChargeSeq().equals(startChargeSeq));
    }

    @PreDestroy
    public void destroy() {
        log.info("正在关闭启动充电延迟任务线程池...");
        executor.shutdown();
        try {
            if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
                executor.shutdownNow();
            }
        } catch (InterruptedException e) {
            executor.shutdownNow();
        }
        log.info("启动充电延迟任务线程池已关闭");
        // 清理队列
        START_DELAY_QUEUE.clear();
        // 清理重试列表
        retryList.clear();
    }
}
