package com.red.circle.other.app.command.redpacket;

import com.red.circle.framework.core.asserts.ResponseAssert;
import com.red.circle.other.app.convertor.RoomRedPacketAppConvertor;
import com.red.circle.other.app.dto.clientobject.GrabRoomRedPacketResultCO;
import com.red.circle.other.app.dto.cmd.GrabRoomRedPacketCmd;
import com.red.circle.other.domain.gateway.RoomRedPacketGateway;
import com.red.circle.other.domain.gateway.RoomRedPacketRecordGateway;
import com.red.circle.other.domain.redpacket.RoomRedPacket;
import com.red.circle.other.domain.redpacket.RoomRedPacketRecord;
import com.red.circle.other.domain.redpacket.RoomRedPacketStatus;
import com.red.circle.other.domain.redpacket.RoomRedPacketType;
import com.red.circle.other.infra.database.cache.service.other.RoomRedPacketCacheService;
import com.red.circle.other.inner.asserts.OtherErrorCode;
import com.red.circle.tool.core.sequence.IdWorkerUtils;
import com.red.circle.tool.core.tuple.PennyAmount;
import com.red.circle.wallet.inner.endpoint.wallet.WalletGoldClient;
import com.red.circle.wallet.inner.model.cmd.GoldReceiptCmd;
import com.red.circle.wallet.inner.model.enums.GoldOrigin;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.time.Duration;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;

/**
 * 抢房间红包命令执行器（高并发核心）
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class GrabRoomRedPacketCmdExe {

    private final RoomRedPacketGateway roomRedPacketGateway;
    private final RoomRedPacketRecordGateway roomRedPacketRecordGateway;
    private final RoomRedPacketCacheService roomRedPacketCacheService;
    private final WalletGoldClient walletGoldClient;
    private final Executor redPacketAsyncExecutor;

    /**
     * 执行抢红包（高并发场景）
     */
    public GrabRoomRedPacketResultCO execute(GrabRoomRedPacketCmd cmd) {
        Long userId = cmd.getReqUserId();
        String packetId = cmd.getPacketId();
        
        // 1. Redis防重：检查用户是否已领取
        boolean grabbed = roomRedPacketCacheService.hasUserGrabbed(packetId, userId);
        ResponseAssert.isFalse(OtherErrorCode.RED_PACKET_ALREADY_GRABBED, grabbed);

        // 2. 从Redis获取红包信息
        Map<Object, Object> packetData = roomRedPacketCacheService.getRedPacket(packetId);
        if (packetData == null || packetData.isEmpty()) {
            // Redis未命中，从数据库加载
            RoomRedPacket redPacket = roomRedPacketGateway.findByPacketId(packetId);
            ResponseAssert.notNull(OtherErrorCode.RED_PACKET_NOT_FOUND, redPacket);

            // 检查状态
            checkRedPacketStatus(redPacket);
            
            // 重新缓存
            cacheRedPacketData(redPacket);
            packetData = roomRedPacketCacheService.getRedPacket(packetId);
        }
        
        // 3. 检查过期时间
        String expireTimeStr = String.valueOf(packetData.get("expireTime"));
        LocalDateTime expireTime = LocalDateTime.parse(expireTimeStr);
        ResponseAssert.isFalse(OtherErrorCode.RED_PACKET_EXPIRED, LocalDateTime.now().isAfter(expireTime));

        // 4. 计算红包金额
        Integer packetType = Integer.valueOf(String.valueOf(packetData.get("packetType")));
        Long amount = calculateAmount(packetId, packetType, packetData);
        
        // 5. Redis原子扣减（Lua脚本保证原子性）
        Long remainCount = roomRedPacketCacheService.decrementRedPacket(packetId, amount);
        ResponseAssert.isFalse(OtherErrorCode.RED_PACKET_FINISHED, remainCount == null || remainCount < 0);

        // 6. 设置防重标记（Redis）
        long expireSeconds = Math.max(60, Duration.between(LocalDateTime.now(), expireTime).getSeconds());
        boolean setSuccess = roomRedPacketCacheService.setUserGrabbedFlag(packetId, userId, expireSeconds);
        ResponseAssert.isTrue(OtherErrorCode.RED_PACKET_ALREADY_GRABBED, setSuccess);

        // 7. 获取总个数（用于返回）
        Integer totalCount = Integer.valueOf(String.valueOf(packetData.get("totalCount")));
        
        // 8. 异步写入数据库 + 钱包加金币（提高响应速度）
        String recordId = IdWorkerUtils.getIdStr();
        CompletableFuture.runAsync(() -> {
            asyncProcessGrab(packetId, cmd.getReqSysOrigin().getOrigin(), userId, amount, remainCount, recordId);
        }, redPacketAsyncExecutor);
        
        log.info("抢红包成功 userId={} packetId={} amount={} remainCount={}", 
            userId, packetId, amount, remainCount);
        
        // 9. 立即返回结果
        return RoomRedPacketAppConvertor.toGrabResultCO(packetId, amount, 
            remainCount.intValue(), totalCount);
    }

    /**
     * 检查红包状态
     */
    private void checkRedPacketStatus(RoomRedPacket redPacket) {
        ResponseAssert.isFalse(OtherErrorCode.RED_PACKET_FINISHED, RoomRedPacketStatus.FINISHED.getCode().equals(redPacket.getStatus()));
        ResponseAssert.isFalse(OtherErrorCode.RED_PACKET_EXPIRED, RoomRedPacketStatus.EXPIRED.getCode().equals(redPacket.getStatus()));
        ResponseAssert.isFalse(OtherErrorCode.RED_PACKET_EXPIRED, LocalDateTime.now().isAfter(redPacket.getExpireTime()));
    }

    /**
     * 计算红包金额
     */
    private Long calculateAmount(String packetId, Integer packetType, Map<Object, Object> packetData) {
        // 固定金额红包
        if (RoomRedPacketType.FIXED.getCode().equals(packetType)) {
            Long totalAmount = Long.valueOf(String.valueOf(packetData.get("totalAmount")));
            Integer totalCount = Integer.valueOf(String.valueOf(packetData.get("totalCount")));
            return totalAmount / totalCount;
        }
        
        // 拼手气红包：从Redis List弹出预生成的金额
        if (RoomRedPacketType.RANDOM.getCode().equals(packetType)) {
            Long amount = roomRedPacketCacheService.popRandomAmount(packetId);
            if (amount == null) {
                // Redis中没有了，从剩余金额计算
                Long remainAmount = Long.valueOf(String.valueOf(packetData.get("remainAmount")));
                Integer remainCount = Integer.valueOf(String.valueOf(packetData.get("remainCount")));
                amount = remainAmount / Math.max(1, remainCount);
            }
            return amount;
        }
        
        throw new RuntimeException(OtherErrorCode.RED_PACKET_TYPE_ERROR.getMessage());
    }

    /**
     * 异步处理抢红包（写库 + 加金币）
     */
    private void asyncProcessGrab(String packetId, String sysOrigin, Long userId, Long amount,
                                   Long remainCount, String recordId) {
        try {
            // 1. 写入抢红包记录
            RoomRedPacketRecord record = RoomRedPacketRecord.builder()
                    .recordId(recordId)
                    .packetId(packetId)
                    .userId(userId)
                    .amount(amount)
                    .grabTime(LocalDateTime.now())
                    .createTime(LocalDateTime.now())
                    .build();
            roomRedPacketRecordGateway.save(record);
            
            // 2. 更新红包主表
            boolean updated = roomRedPacketGateway.decrementPacket(packetId, amount);
            if (!updated) {
                log.warn("更新红包主表失败（乐观锁） packetId={} userId={} amount={}", 
                    packetId, userId, amount);
            }
            
            // 3. 增加用户金币
            addUserGold(userId, sysOrigin,  amount, recordId);
            
            // 4. 检查是否抢完
            if (remainCount == 0) {
                roomRedPacketGateway.updateStatus(packetId, RoomRedPacketStatus.FINISHED);
                log.info("红包已抢完 packetId={}", packetId);
            }
            
            log.info("异步处理抢红包成功 userId={} packetId={} amount={}", 
                userId, packetId, amount);
            
        } catch (Exception e) {
            log.error("异步处理抢红包失败 userId={} packetId={} amount={}", 
                userId, packetId, amount, e);
            // TODO: 记录到异常日志表，定时任务补偿
        }
    }

    /**
     * 增加用户金币
     */
    private void addUserGold(Long userId, String sysOrigin, Long amount, String recordId) {
        GoldReceiptCmd build = GoldReceiptCmd.builder()
                .appIncome()
                .userId(userId)
                .amount(PennyAmount.ofDollar(amount))
                .eventId(recordId)
                .sysOrigin(sysOrigin)
                .origin(GoldOrigin.ROOM_RED_PACKET)
                .build();
        
        try {
            walletGoldClient.changeBalance(build);
        } catch (Exception e) {
            log.error("增加金币失败 userId={} amount={} recordId={}", userId, amount, recordId, e);
            throw new RuntimeException("Failed to increase gold coins");
        }
    }

    /**
     * 缓存红包数据到Redis
     */
    private void cacheRedPacketData(RoomRedPacket redPacket) {
        Map<String, Object> packetData = Map.of(
            "packetType", redPacket.getPacketType(),
            "totalAmount", redPacket.getTotalAmount(),
            "totalCount", redPacket.getTotalCount(),
            "remainAmount", redPacket.getRemainAmount(),
            "remainCount", redPacket.getRemainCount(),
            "expireTime", redPacket.getExpireTime().toString(),
            "status", redPacket.getStatus()
        );

        long expireSeconds = Duration.between(LocalDateTime.now(), redPacket.getExpireTime()).getSeconds();
        roomRedPacketCacheService.setRedPacket(redPacket.getPacketId(), packetData, expireSeconds);
    }
}
