package com.njcn.msgpush.module.push.service.retry;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.njcn.msgpush.framework.common.pojo.PageResult;
import com.njcn.msgpush.module.push.constant.MessageStatusConstant;
import com.njcn.msgpush.module.push.controller.admin.retry.vo.MessageRetryQueueReqVO;
import com.njcn.msgpush.module.push.dal.dataobject.message.MessageRecordDO;
import com.njcn.msgpush.module.push.dal.dataobject.retry.MessageRetryQueueDO;
import com.njcn.msgpush.module.push.dal.mysql.retry.MessageRetryQueueMapper;
import com.njcn.msgpush.module.push.dal.redis.MessageRetryRedisDAO;
import com.njcn.msgpush.module.push.enums.RetryStrategyEnum;
import com.njcn.msgpush.module.push.service.channel.ChannelProviderConfigService;
import com.njcn.msgpush.module.push.service.message.MessageRecordService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
public class MessageRetryQueueServiceImpl extends ServiceImpl<MessageRetryQueueMapper, MessageRetryQueueDO> implements MessageRetryQueueService {
    @Autowired
    private MessageRetryRedisDAO messageRetryRedisDAO;
    @Autowired
    private MessageRecordService messageRecordService;
    @Autowired
    public ChannelProviderConfigService channelProviderConfigService;

    public final ThreadPoolExecutor MSG_RETRY_THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(
            5,
            5,
            1000,
            TimeUnit.MILLISECONDS,
            new java.util.concurrent.ArrayBlockingQueue<>(1000),
            r -> {
                Thread thread = new Thread(r);
                thread.setName("msgRetryThreadPool-" + thread.getId());
                thread.setDaemon(false);
                return thread;
            },
            new ThreadPoolExecutor.CallerRunsPolicy()
    );

    /**
     * 默认每次处理的消息数量
     */
    private static final int DEFAULT_BATCH_SIZE = 100;

    /**
     * 默认重试间隔（秒）
     */
    private static final int DEFAULT_RETRY_INTERVAL = 300; // 5分钟

    /**
     * 默认最大重试次数
     */
    private static final int DEFAULT_MAX_RETRY_COUNT = 3;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addRetryMessage(MessageRecordDO message, RetryStrategyEnum strategy) {
        // 检查是否已存在重试记录
        MessageRetryQueueDO existing = super.baseMapper.selectOne(
                new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<MessageRetryQueueDO>()
                        .eq(MessageRetryQueueDO::getMessageId, message.getMessageId())
        );

        // 消息不在重试队列中
        if (ObjectUtil.isNull(existing)) {
            // 创建重试记录
            MessageRetryQueueDO retryRecord = new MessageRetryQueueDO();
            retryRecord.setMessageId(message.getMessageId());
            retryRecord.setChannel(message.getChannel());
            retryRecord.setReceiver(message.getReceiver());
            retryRecord.setRetryCount(0);
            retryRecord.setMaxRetry(message.getRetryCount());
            retryRecord.setFirstFailTime(LocalDateTime.now());
            retryRecord.setLastErrorMsg(message.getErrorMsg());

            // 计算下次重试时间
            LocalDateTime nextRetryTime = strategy.calculateNextRetryTime(0, DEFAULT_RETRY_INTERVAL);
            retryRecord.setNextRetryTime(nextRetryTime);

            super.baseMapper.insert(retryRecord);

            // 同步到Redis
            messageRetryRedisDAO.addToRetryQueue(message);
        }
    }

    @Override
    public void processRetryBatch(String channel) {
        // 从数据库查询需要重试的消息
        // List<MessageRetryQueueDO> retryMessages = retryQueueMapper.selectNeedRetryMessages(LocalDateTime.now(), DEFAULT_BATCH_SIZE);
        // 从redis中查询需要重试的消息
        Set<String> needRetryMessageIds = messageRetryRedisDAO.getNeedRetryMessageIds(channel, LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli(), DEFAULT_BATCH_SIZE);

        // 没有需要重试的消息
        if (needRetryMessageIds.isEmpty()) {
            return;
        }

        List<MessageRecordDO> messageRecordDOList = messageRecordService.listByIds(needRetryMessageIds);

        for (MessageRecordDO messageRecordDO : messageRecordDOList) {
            processSingleRetry(messageRecordDO);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void manualRetry(List<String> messageIds) {
        if (CollUtil.isNotEmpty(messageIds)) {
            List<MessageRecordDO> messageRecordDOList = messageRecordService.listByIds(messageIds);
            for (MessageRecordDO messageRecordDO : messageRecordDOList) {
                // 立即重试（设置下次重试时间为现在）
                //retryRecord.setNextRetryTime(LocalDateTime.now());
                //retryQueueMapper.updateById(retryRecord);

                // 更新redis中的分数
                //messageRetryRedisDAO.addToRetryQueue(retryRecord.getChannel(), messageId, LocalDateTime.now());
                processSingleRetry(messageRecordDO);
            }
        }
    }

    @Override
    public PageResult<MessageRetryQueueDO> getPage(MessageRetryQueueReqVO reqVO) {
        return null;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean removeRetryRecord(String messageId) {
        MessageRetryQueueDO retryRecord = super.baseMapper.selectOne(
                new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<MessageRetryQueueDO>()
                        .eq(MessageRetryQueueDO::getMessageId, messageId)
        );

        if (retryRecord == null) {
            log.warn("未找到重试记录: messageId={}", messageId);
            return false;
        }

        // 从数据库删除
        super.baseMapper.deleteByMessageId(messageId);

        // 从Redis删除
        messageRetryRedisDAO.removeFromRetryQueue(retryRecord.getChannel(), messageId);

        log.info("删除重试记录成功: messageId={}", messageId);
        return true;

    }

    /**
     * 处理单个消息的重试逻辑
     */
    private void processSingleRetry(MessageRecordDO messageRecordDO) {
        // 异步调用消息发送接口进行重试
        CompletableFuture<Boolean> sendFuture = CompletableFuture.supplyAsync(() ->
                messageRecordService.processSendMsg(messageRecordDO), MSG_RETRY_THREAD_POOL_EXECUTOR
        );

        sendFuture.orTimeout(5, TimeUnit.SECONDS)
                .thenAccept(sendResult -> {
                    if (sendResult) {
                        log.info("处理消息重试成功逻辑：messageId={}", messageRecordDO.getMessageId());
                        super.baseMapper.deleteByMessageId(messageRecordDO.getMessageId());
                        messageRetryRedisDAO.removeFromRetryQueue(messageRecordDO.getChannel(), messageRecordDO.getMessageId());
                    } else {
                        // 重试失败，更新重试信息
                        log.error("处理消息重试失败逻辑：messageId={}", messageRecordDO.getMessageId());
                        channelProviderConfigService.failureUpdate(messageRecordDO.getProviderType(), messageRecordDO.getChannel());
                        handleRetryFailure(messageRecordDO);
                    }
                }).exceptionally(ex -> {
                    log.error("异步执行消息重试发生异常：messageId={}", messageRecordDO.getMessageId(), ex);
                    // 发生异常时也尝试处理失败逻辑，避免消息丢失
                    try {
                        handleRetryFailure(messageRecordDO);
                    } catch (Exception e) {
                        log.error("异常处理后再次失败：messageId={}", messageRecordDO.getMessageId(), e);
                    }
                    return null;
                });
    }

    /**
     * 模拟重试过程（实际应用中应替换为真实的消息发送逻辑）
     */
    private boolean simulateRetryProcess(String messageId) {
        // 这里应该是调用实际的消息发送服务
        // 暂时随机返回成功或失败用于演示
        return Math.random() > 0.3; // 70%成功率
    }

    /**
     * 处理重试失败的情况
     */
    private void handleRetryFailure(MessageRecordDO messageRecordDO) {
        MessageRetryQueueDO messageRetryQueueDO = this.getById(messageRecordDO.getMessageId());
        int newRetryCount = messageRetryQueueDO.getRetryCount() + 1;

        if (newRetryCount >= messageRetryQueueDO.getMaxRetry()) {
            // 达到最大重试次数，标记为最终失败

            // 更新消息的状态为final_failed
            messageRecordService.updateStatus(messageRecordDO.getMessageId(), MessageStatusConstant.FINALFAILED);

            // 数据库中不能删除
            // retryQueueMapper.deleteByMessageId(messageRecordDO.getMessageId());
            // redis中可以删除
            messageRetryRedisDAO.removeFromRetryQueue(messageRecordDO.getChannel(), messageRecordDO.getMessageId());
        } else {
            // 还可以继续重试，更新重试信息
            RetryStrategyEnum strategy = RetryStrategyEnum.EXPONENTIAL_BACKOFF;
            LocalDateTime nextRetryTime = strategy.calculateNextRetryTime(newRetryCount, DEFAULT_RETRY_INTERVAL);

            super.baseMapper.updateRetryInfo(
                    messageRecordDO.getMessageId(),
                    newRetryCount,
                    nextRetryTime,
                    LocalDateTime.now(),
                    "重试失败：" + LocalDateTime.now()
            );

            // 更新Redis中的下次重试时间
            messageRetryRedisDAO.addToRetryQueue(messageRecordDO);
        }
    }
}
