package com.microservices.zone.member.service.points.handler;

import com.microservices.zone.member.domain.points.ZonePointsAccount;
import com.microservices.zone.member.mapper.ZonePointsAccountMapper;
import com.microservices.zone.member.service.points.IPointsTaskHandler;
import com.microservices.zone.member.service.points.PointsAccountOperations;
import com.microservices.zone.member.service.points.PointsTaskContext;
import com.microservices.zone.utils.ZoneConstants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

/**
 * 每日首次访问专区 — 积分奖励
 *
 * @author otto
 */
@Component
public class DailyVisitPointsHandler implements IPointsTaskHandler {
    private static final Logger logger = LoggerFactory.getLogger(DailyVisitPointsHandler.class);

    @Autowired
    private PointsAccountOperations pointsAccountOperations;

    @Autowired
    private ZonePointsAccountMapper zonePointsAccountMapper;

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Override
    public String getSourceType() {
        return ZoneConstants.DailyVisit.SOURCE_TYPE;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void handle(PointsTaskContext ctx) {
        String redisKey = ZoneConstants.ZONE_DAILY_FIRST_VISIT_KEY + ctx.getZoneId() + ":" + ctx.getUserId();
        String counterKey = redisKey + ":counter";

        // 幂等检查：counter 必须存在（由 addDailyVisitPointsAsync 的 setIfAbsent 设置）
        Object counter = redisTemplate.opsForValue().get(counterKey);
        if (counter == null) {
            logger.info("积分-每日登录：防重key不存在，跳过处理 userId={} zoneId={}", ctx.getUserId(), ctx.getZoneId());
            return;
        }
        // counter > 1 表示本任务已成功处理过（参见下方 increment），防止重试时重复发放积分
        if (Long.parseLong(counter.toString()) > 1) {
            logger.info("积分-每日登录：任务已处理，跳过重试 userId={} zoneId={}", ctx.getUserId(), ctx.getZoneId());
            return;
        }

        ZonePointsAccount account = pointsAccountOperations.getOrCreateAccount(ctx.getUserId(), ctx.getZoneId());
        if (account == null) {
            return;
        }

        // 锁定行并读取最新值
        ZonePointsAccount locked = zonePointsAccountMapper.selectZonePointsAccountByIdForUpdate(account.getId());
        int availableAfter = locked.getAvailablePoints() + ZoneConstants.DailyVisit.POINTS;
        int frozenAfter = locked.getFrozenPoints();

        // 原子增加积分
        zonePointsAccountMapper.addPointsAtomic(account.getId(), ZoneConstants.DailyVisit.POINTS);

        // 记录积分流水
        pointsAccountOperations.recordPointsTransaction(account.getId(),
                ZoneConstants.TransactionType.ADD,
                ZoneConstants.DailyVisit.POINTS,
                availableAfter, frozenAfter,
                String.format(ZoneConstants.DailyVisit.REASON_TEMPLATE, ZoneConstants.DailyVisit.POINTS),
                0L, null, ZoneConstants.DailyVisit.SOURCE_TYPE, null,
                ZoneConstants.DailyVisit.SUBJECT, ZoneConstants.DailyVisit.SUBJECT_DESC, null);

        // 递增 counter 实现幂等标记，防止任务重试时重复发放积分。
        // 放在所有 DB 操作之后：若此前任一步骤抛异常，事务回滚，counter 保持 "1"，
        // 重试时仍可正常发放积分；只有全部成功后才将 counter 变为 "2"，后续重试会被上方 >1 检查拦截。
        // increment 保留 key 原有的 TTL，次日零点自动过期，不影响新一天的积分领取。
        try {
            redisTemplate.opsForValue().increment(counterKey);
            logger.info("积分-每日登录：积分发放成功 userId={} zoneId={} accountId={} points={}",
                    ctx.getUserId(), ctx.getZoneId(), account.getId(), ZoneConstants.DailyVisit.POINTS);
        } catch (Exception e) {
            // Redis increment 失败不影响主流程（积分已发放），仅记录日志。
            // 极端情况下若任务被重试且 counter 未递增，可能会重复发放积分，
            // 但概率极低且 DB 流水可审计追溯。
            logger.warn("积分-每日登录：counter递增失败，不影响积分发放。若任务重试可能重复发放 userId={} zoneId={}",
                    ctx.getUserId(), ctx.getZoneId(), e);
        }
    }
}
