package jnpf.attendance.event;

import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import jnpf.attendance.service.AttendanceDayStatisticsService;
import jnpf.constants.MessageTopicConstants;
import jnpf.database.model.TenantVO;
import jnpf.database.util.TenantDataSourceUtil;
import jnpf.enums.attendance.StatisticsEnumUtil;
import jnpf.model.attendance.event.StatisticsSingleHistoryDto;
import jnpf.util.DateUtil;
import jnpf.util.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.common.consumer.ConsumeFromWhere;
import org.apache.rocketmq.spring.annotation.ConsumeMode;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.apache.rocketmq.spring.core.RocketMQPushConsumerLifecycleListener;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * 考勤统计-单条消息监听，监听生成历史数据处理
 * @author 石头
 */
@Slf4j
@Component
@RocketMQMessageListener(
        topic = MessageTopicConstants.ATTENDANCE_STATISTICS_SINGLE_HISTORY_TOPIC,
        consumerGroup = MessageTopicConstants.ATTENDANCE_STATISTICS_SINGLE_HISTORY_CONSUMER_GROUP,
        consumeMode = ConsumeMode.CONCURRENTLY,
        maxReconsumeTimes = 5)
public class StatisticsSingleHistoryMQListener implements RocketMQListener<StatisticsSingleHistoryDto>, RocketMQPushConsumerLifecycleListener {

    @Resource
    private RedissonClient redissonClient;
    @Resource
    private AttendanceDayStatisticsService attendanceDayStatisticsService;

    @Override
    public void onMessage(StatisticsSingleHistoryDto singleDto) {
        if (Objects.isNull(singleDto)) {
            log.warn("接收到空消息，忽略处理");
            return;
        }
        log.info("接受到一条历史数据处理消息，{}", JSONObject.toJSONString(singleDto));
        // 校验必要字段
        if (ObjectUtil.isNull(singleDto) || StringUtil.isEmpty(singleDto.getUserId()) ||
                StringUtil.isEmpty(singleDto.getTenantId()) || StringUtil.isEmpty(singleDto.getGroupId()) ||
                ObjectUtil.isNull(singleDto.getDay())) {
            log.error("生成历史数据处理消费失败：消息格式无效，内容: {}", JSONObject.toJSONString(singleDto));
            return;
        }
        try {
            String lockKey = String.format(StatisticsEnumUtil.AttendanceClockEnum.DAY_STATISTICS.getKey(), singleDto.getTenantId(), singleDto.getGroupId(),
                    singleDto.getUserId(), DateUtil.daFormat(singleDto.getDay()));
            RLock lock = redissonClient.getLock(lockKey);
            try {
                // 立即尝试获取锁（不等待），失败直接抛异常
                if (!lock.tryLock(5, 20, TimeUnit.SECONDS)) {
                    throw new RuntimeException("获取锁失败");
                }
                // 租户不为空就需要切库
                TenantVO tenantVO = TenantDataSourceUtil.switchTenant(singleDto.getTenantId());
                log.error("切换tenant对象{}", JSON.toJSONString(tenantVO));
                attendanceDayStatisticsService.statisticDataChangeHistory(singleDto);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                throw new RuntimeException("获取锁时线程被中断: " + lockKey, e);
            } finally {
                // 安全释放：仅当前线程持有时释放，异常仅记录不抛出
                if (lock.isHeldByCurrentThread() && lock.isHeldByThread(Thread.currentThread().getId())) {
                    lock.unlock();
                }
            }
        } catch (Exception ex) {
            log.info("处理历史数据处理失败，触发重试,消息 {}, 错误:{} ", JSONObject.toJSONString(singleDto), ex.getMessage());
            throw new RuntimeException("处理历史数据处理失败，触发重试", ex);
        }
    }

    @Override
    public void prepareStart(DefaultMQPushConsumer consumer) {
        // 优化消费参数 每次拉取的消息数量
        consumer.setPullBatchSize(32);
        // 设置消费间隔，避免过于频繁拉取
        consumer.setPullInterval(200);
        // 设置消费超时时间（分钟）
        consumer.setConsumeTimeout(10);
        // 设置消费起始位置（从上次消费的位置继续）
        consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET);
        // 调整消费线程池最小线程数
        consumer.setConsumeThreadMin(10);
        // 调整消费线程池最大线程数
        consumer.setConsumeThreadMax(20);
    }
}