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

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
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.constant.MsgPushConstant;
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.dataobject.retry.RetryStrategyConfigDO;
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.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.Collections;
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;
    @Autowired
    public RetryStrategyConfigService retryStrategyConfigService;


    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 = 5;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void saveOrUpdateRetryMessage(MessageRecordDO message) {
        // 检查是否已存在重试记录
        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(1);
            RetryStrategyConfigDO strategyConfig = retryStrategyConfigService.getStrategyConfig(message.getChannel());
            if (ObjectUtil.isNull(strategyConfig)) {
                retryRecord.setMaxRetry(DEFAULT_MAX_RETRY_COUNT);
            } else {
                retryRecord.setMaxRetry(strategyConfig.getMaxRetryCount());
            }
            retryRecord.setFirstFailTime(message.getSendTime());
            retryRecord.setLastErrorMsg(message.getErrorMsg());
            // 计算下次重试时间
            LocalDateTime nextRetryTime = this.calculateNextRetryTime(message.getChannel(), retryRecord.getRetryCount());
            retryRecord.setNextRetryTime(nextRetryTime);
            message.setNextRetryTime(nextRetryTime);
            this.save(retryRecord);
        } else {
            existing.setRetryCount(existing.getRetryCount() + 1);
            // 计算下次重试时间
            LocalDateTime nextRetryTime = this.calculateNextRetryTime(message.getChannel(), existing.getRetryCount());
            existing.setNextRetryTime(nextRetryTime);
            message.setNextRetryTime(nextRetryTime);
            this.updateById(existing);
        }
        // 同步到Redis
        messageRetryRedisDAO.addToRetryQueue(message);
    }

    /**
     * 计算下次重试时间
     *
     * @param channel
     * @param retryCount
     * @return
     */
    private LocalDateTime calculateNextRetryTime(String channel, int retryCount) {
        RetryStrategyConfigDO strategyConfig = retryStrategyConfigService.getStrategyConfig(channel);
        long plusSeconds = 0;
        if (ObjectUtil.isNull(strategyConfig)) {
            // 默认策略
            switch (channel) {
                case MsgPushConstant.CHANNEL_SMS: {
                    if (retryCount == 1) {
                        plusSeconds = 60 * 5;
                    } else if (retryCount == 2) {
                        plusSeconds = 60 * 10;
                    } else {
                        plusSeconds = 60 * 30;
                    }
                }
                break;
                case MsgPushConstant.CHANNEL_EMAIL: {
                    if (retryCount == 1) {
//                        plusSeconds = 60 * 10;
                        plusSeconds = 60 * 2;
                    } else if (retryCount == 2) {
                        plusSeconds = 60 * 30;
                    } else if (retryCount == 3) {
                        plusSeconds = 60 * 60;
                    } else if (retryCount == 4) {
                        plusSeconds = 60 * 60 * 2;
                    } else {
                        plusSeconds = 60 * 60 * 4;
                    }
                }
                break;
                case MsgPushConstant.CHANNEL_APP_PUSH: {
                    if (retryCount == 1) {
                        plusSeconds = 60 * 1;
                    } else {
                        plusSeconds = 60 * 60;
                    }
                }
                break;
                default:
                    plusSeconds = 60 * 5;
            }
        } else {
            String retryIntervals = strategyConfig.getRetryIntervals();
            String[] split = retryIntervals.split(String.valueOf(StrUtil.C_COMMA));
            if (retryCount >= split.length) {
                plusSeconds = Long.parseLong(split[split.length - 1]);
            } else {
                plusSeconds = Long.parseLong(split[retryCount - 1]);
            }
        }
        return LocalDateTime.now().plusSeconds(plusSeconds);
    }

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

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

        List<MessageRecordDO> messageRecordDOList = messageRecordService.listByIds(needRetryMessageIds);
        System.out.println("messageRecordDOList.size()=:" + messageRecordDOList.size());

        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
    public boolean updateRetryInfo(String messageId, int retryCount, LocalDateTime nextRetryTime, LocalDateTime lastRetryTime, String s) {
        return this.lambdaUpdate()
                .set(MessageRetryQueueDO::getRetryCount, retryCount)
                .set(MessageRetryQueueDO::getNextRetryTime, nextRetryTime)
                .set(MessageRetryQueueDO::getLastRetryTime, lastRetryTime)
                .eq(MessageRetryQueueDO::getMessageId, messageId)
                .update();
    }

    @Override
    public boolean deleteByMessageIds(List<String> messageIds) {
        return this.lambdaUpdate()
                .set(MessageRetryQueueDO::getDeleted, true)
                .in(CollUtil.isNotEmpty(messageIds), MessageRetryQueueDO::getMessageId, messageIds)
                .update();
    }

    /**
     * 处理单个消息的重试逻辑
     */
    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 -> {
                    messageRecordService.updateRetryCount(messageRecordDO.getMessageId());
                    if (sendResult) {
                        log.info("处理消息重试成功逻辑：messageId={}", messageRecordDO.getMessageId());
                        messageRetryRedisDAO.removeFromRetryQueue(messageRecordDO.getChannel(), messageRecordDO.getMessageId());
                        this.deleteByMessageIds(Collections.singletonList(messageRecordDO.getMessageId()));
                    } else {
                        // 重试失败，更新重试信息
                        log.error("处理消息重试失败逻辑：messageId={}", messageRecordDO.getMessageId());
                        channelProviderConfigService.failureUpdate(messageRecordDO.getProviderType(), messageRecordDO.getChannel());
                        handleRetryFailure(messageRecordDO);
                    }
                }).exceptionally(ex -> {
                    messageRecordService.updateRetryCount(messageRecordDO.getMessageId());
                    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.lambdaQuery().eq(MessageRetryQueueDO::getMessageId, messageRecordDO.getMessageId()).one();
        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 {
            // 还可以继续重试，更新重试信息
            LocalDateTime nextRetryTime = this.calculateNextRetryTime(messageRecordDO.getChannel(), newRetryCount);

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

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