package jnpf.attendance.event;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import com.alibaba.fastjson.JSONObject;
import jnpf.attendance.service.AttendanceClockInService;
import jnpf.base.UserInfo;
import jnpf.constants.MessageTopicConstants;
import jnpf.constants.RedisConstant;
import jnpf.database.util.TenantDataSourceUtil;
import jnpf.model.attendance.vo.UserDayVo;
import jnpf.util.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.utils.Lists;
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.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

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

import static jnpf.constants.RedisConstant.ATTENDANCE_NOTIFICATION_CLOCK_USER;

/**
 * 考勤统计-单条消息监听，监听生成用户日统计消息
 * @author 石头
 */
@Slf4j
@Component
@RocketMQMessageListener(
        topic = MessageTopicConstants.ATTENDANCE_NOTIFICATION_CLOCK_TOPIC,
        consumerGroup = MessageTopicConstants.ATTENDANCE_NOTIFICATION_CLOCK_CONSUMER_GROUP,
        consumeMode = ConsumeMode.CONCURRENTLY,
        maxReconsumeTimes = 5)
public class NotificationClockMQListener implements RocketMQListener<List<UserDayVo>>, RocketMQPushConsumerLifecycleListener {

    @Resource
    private RedissonClient redissonClient;
    @Autowired
    private AttendanceClockInService attendanceClockInService;

    @Override
    public void onMessage(List<UserDayVo> userDayList) {
        if (CollUtil.isEmpty(userDayList)) {
            return;
        }
        log.error("接受到排班通知打卡消息，{}", JSONObject.toJSONString(userDayList));
        UserDayVo userDayVo1 = userDayList.get(0);
        Assert.isFalse(StringUtil.isEmpty(userDayVo1.getTenantId()), "排班通知打卡tenantId不能为空");
        List<RLock> locks = Lists.newArrayList();
        try {
            for (UserDayVo userDayVo : userDayList) {
                String lockKey = String.format(ATTENDANCE_NOTIFICATION_CLOCK_USER, userDayVo.getUserId(), userDayVo.getDay());
                RLock lock = redissonClient.getLock(lockKey);
                RLock lock1 = redissonClient.getLock(String.format(RedisConstant.ATTENDANCE_USER_SET_SCHEDULES, userDayVo.getTenantId(), userDayVo.getUserId(), userDayVo.getUserId()));
                // 立即尝试获取锁（不等待），失败直接抛异常
                if (lock.isLocked() || lock1.isLocked() || !lock.tryLock(5, 60, TimeUnit.SECONDS)) {
                    throw new RuntimeException(userDayVo.getUserId() + "通知打卡获取锁失败");
                }
                locks.add(lock);
            }
            TenantDataSourceUtil.switchTenant(userDayVo1.getTenantId());
            UserInfo user = new UserInfo();
            user.setUserId(userDayVo1.getUserId());
            user.setTenantId(userDayVo1.getTenantId());
            // 执行业务（异常直接向上抛出）
            attendanceClockInService.changeAttendanceRuleBatch(userDayList, user);

        } catch (Exception ex) {
            log.error("处理排班通知打卡消息失败，触发重试,消息 {} ", JSONObject.toJSONString(userDayList), ex);
            throw new RuntimeException("处理排班通知打卡消息失败，触发重试", ex);
        } finally {
            // 安全释放：仅当前线程持有时释放，异常仅记录不抛出
            locks.forEach(lock -> {
                if (lock != null && lock.isHeldByCurrentThread()) {
                    lock.unlock();
                }
            });
        }
    }

    @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);
    }
}