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

import com.red.circle.common.business.core.enums.SysOriginPlatformEnum;
import com.red.circle.external.inner.endpoint.message.ImGroupClient;
import com.red.circle.external.inner.model.cmd.message.BroadcastGroupMsgBodyCmd;
import com.red.circle.external.inner.model.cmd.message.CustomGroupMsgBodyCmd;
import com.red.circle.external.inner.model.enums.message.GroupMessageTypeEnum;
import com.red.circle.framework.core.asserts.ResponseAssert;
import com.red.circle.framework.core.response.CommonErrorCode;
import com.red.circle.other.app.convertor.RoomRedPacketAppConvertor;
import com.red.circle.other.app.convertor.user.UserProfileAppConvertor;
import com.red.circle.other.app.dto.clientobject.RoomRedPacketCO;
import com.red.circle.other.app.dto.cmd.SendRoomRedPacketCmd;
import com.red.circle.other.app.enums.RoomRewardLevelEnum;
import com.red.circle.other.app.util.OfficialNoticeUtils;
import com.red.circle.other.domain.gateway.RoomRedPacketGateway;
import com.red.circle.other.domain.gateway.user.UserProfileGateway;
import com.red.circle.other.domain.redpacket.RoomRedPacket;
import com.red.circle.other.domain.redpacket.RoomRedPacketRefundStatus;
import com.red.circle.other.domain.redpacket.RoomRedPacketSourceType;
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.infra.database.mongo.entity.activity.RoomContributionActivityCount;
import com.red.circle.other.infra.database.mongo.service.activity.RoomContributionActivityCountService;
import com.red.circle.other.infra.database.mongo.service.live.RoomProfileManagerService;
import com.red.circle.other.inner.asserts.OtherErrorCode;
import com.red.circle.other.inner.asserts.RoomErrorCode;
import com.red.circle.other.inner.model.dto.user.UserProfileDTO;
import com.red.circle.tool.core.date.ZonedDateTimeAsiaRiyadhUtils;
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.error.WalletErrorCode;
import com.red.circle.wallet.inner.model.cmd.GoldReceiptCmd;
import com.red.circle.wallet.inner.model.cmd.WalletReceiptCmd;
import com.red.circle.wallet.inner.model.enums.GoldOrigin;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.time.Duration;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;

/**
 * 发送房间红包命令执行器
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class SendRoomRedPacketCmdExe {

    private final RoomRedPacketGateway roomRedPacketGateway;
    private final RoomRedPacketCacheService roomRedPacketCacheService;
    private final WalletGoldClient walletGoldClient;
    private final ImGroupClient imGroupClient;
    private final UserProfileGateway userProfileGateway;
    private final UserProfileAppConvertor userProfileAppConvertor;
    private final RoomProfileManagerService roomProfileManagerService;
    private final RoomContributionActivityCountService roomContributionActivityCountService;

    /**
     * 执行发红包（用户红包）
     */
    @Transactional(rollbackFor = Exception.class)
    public RoomRedPacketCO execute(SendRoomRedPacketCmd cmd) {
        Long userId = cmd.getReqUserId();
        cmd.setPacketType(2);
        
        // 参数校验
        validateParams(cmd);
        
        String packetId = IdWorkerUtils.getIdStr();
        
        // 计算过期时间
        LocalDateTime expireTime = LocalDateTime.now().plusMinutes(cmd.getExpireMinutes());
        
        // 创建红包记录
        RoomRedPacket redPacket = buildRedPacket(cmd, userId, packetId, expireTime, RoomRedPacketSourceType.USER);
        roomRedPacketGateway.save(redPacket);

        // 扣除用户金币（同步调用钱包服务）
        deductUserGold(userId, cmd, packetId);
        
        // 拼手气红包：预生成随机金额列表
        if (RoomRedPacketType.RANDOM.getCode().equals(cmd.getPacketType())) {
            List<Long> amounts = generateRandomAmounts(cmd.getTotalAmount(), cmd.getTotalCount());
            long expireSeconds = cmd.getExpireMinutes() * 60;
            roomRedPacketCacheService.setRandomAmounts(packetId, amounts, expireSeconds);
        }
        
        // 写入Redis（红包主数据）
        cacheRedPacketData(redPacket);
        
        // 添加到房间红包列表
        roomRedPacketCacheService.addToRoomList(
            cmd.getRoomId(), 
            packetId, 
            System.currentTimeMillis()
        );

        // 发送飘窗通知
        sendMessage(cmd, packetId);

        log.info("发红包成功 userId={} packetId={} roomId={} type={} amount={} count={}", 
            userId, packetId, cmd.getRoomId(), cmd.getPacketType(), 
            cmd.getTotalAmount(), cmd.getTotalCount());
        
        return RoomRedPacketAppConvertor.toCO(redPacket);
    }

    /**
     * 执行发平台红包
     */
    @Transactional(rollbackFor = Exception.class)
    public RoomRedPacketCO executePlatform(SendRoomRedPacketCmd cmd) {
        // 平台红包固定参数
        cmd.setPacketType(2);

        // 参数校验
        validateParams(cmd);

        String packetId = IdWorkerUtils.getIdStr();
        
        // 计算过期时间
        LocalDateTime expireTime = LocalDateTime.now().plusMinutes(cmd.getExpireMinutes());
        
        // 创建平台红包记录
        Long systemUserId = 0L;
        RoomRedPacket redPacket = buildRedPacket(cmd, systemUserId, packetId, expireTime, RoomRedPacketSourceType.PLATFORM);
        roomRedPacketGateway.save(redPacket);

        // 查询上周数据
        deductRoomContribution(cmd.getRoomId(), cmd.getTotalAmount());

        // 预生成随机金额列表（拼手气红包）
        List<Long> amounts = generateRandomAmounts(cmd.getTotalAmount(), cmd.getTotalCount());
        long expireSeconds = cmd.getExpireMinutes() * 60;
        roomRedPacketCacheService.setRandomAmounts(packetId, amounts, expireSeconds);
        
        // 写入Redis（红包主数据）
        cacheRedPacketData(redPacket);
        
        // 添加到房间红包列表
        roomRedPacketCacheService.addToRoomList(
            cmd.getRoomId(), 
            packetId, 
            System.currentTimeMillis()
        );

        // 发送飘窗通知（平台红包）
        sendMessage(cmd, packetId);

        log.info("发平台红包成功 packetId={} roomId={} amount={} count={} ",
            packetId, cmd.getRoomId(), cmd.getTotalAmount(), cmd.getTotalCount());
        
        return RoomRedPacketAppConvertor.toCO(redPacket);
    }

    private void deductRoomContribution(Long roomId, Long totalAmount) {
        String lastWeekDateNumber = ZonedDateTimeAsiaRiyadhUtils.getLastWeekMonday().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
        RoomContributionActivityCount lastWeek = findByRoomIdAndDateNumber(roomId, Integer.parseInt(lastWeekDateNumber));
        ResponseAssert.notNull(RoomErrorCode.ROOM_CONTRIBUTION_NOT_FOUND, lastWeek);

        Long amount = lastWeek.getContributionValue().longValue();
        RoomRewardLevelEnum level = RoomRewardLevelEnum.getLevelByAmount(amount);
        ResponseAssert.notNull(RoomErrorCode.ROOM_CONTRIBUTION_NOT_COMPLETED, level);
        ResponseAssert.isTrue(CommonErrorCode.DATA_ERROR, Objects.equals(totalAmount, level.getRewardCoins()));

        boolean updated = roomContributionActivityCountService.updateRewardCoinsSent(lastWeek.getId());
        ResponseAssert.isTrue(CommonErrorCode.OPERATING_FAILURE, updated);
    }

    /**
     * 根据房间ID和日期查询数据
     */
    private RoomContributionActivityCount findByRoomIdAndDateNumber(Long roomId, Integer dateNumber) {
        return roomContributionActivityCountService
                .findByRoomIdAndDateNumber(roomId, dateNumber)
                .orElse(null);
    }

    /**
     * 发送消息通知
     */
    private void sendMessage(SendRoomRedPacketCmd cmd, String packetId) {
        try {
            String roomAccount = roomProfileManagerService.getRoomAccount(cmd.getRoomId());
            UserProfileDTO userProfile = userProfileAppConvertor.toUserProfileDTO(userProfileGateway.getByUserId(cmd.getReqUserId()));
            Map<Object, Object> build = OfficialNoticeUtils.buildUserProfile(userProfile);
            build.put("roomAccount", roomAccount);
            build.put("roomId", String.valueOf(cmd.getRoomId()));
            build.put("packetId", packetId);

            if (cmd.getTotalAmount() >= 10000) {
                imGroupClient.sendMessageBroadcast(
                        BroadcastGroupMsgBodyCmd.builder()
                                .toPlatform(SysOriginPlatformEnum.LIKEI)
                                .type(GroupMessageTypeEnum.ROOM_RED_PACKET)
                                .data(build)
                                .build()
                );
            } else {
                imGroupClient.sendCustomMessage(roomAccount,
                        CustomGroupMsgBodyCmd.builder()
                                .type(GroupMessageTypeEnum.ROOM_RED_PACKET)
                                .data(build)
                                .build());
            }
        } catch (Exception e) {
            log.error("发送红包飘窗推送失败, {}", e.getMessage());
        }
    }

    /**
     * 参数校验
     */
    private void validateParams(SendRoomRedPacketCmd cmd) {
        // 校验红包类型
        if (!Arrays.asList(1, 2).contains(cmd.getPacketType())) {
            throw new RuntimeException(OtherErrorCode.RED_PACKET_TYPE_ERROR.getMessage());
        }
        
        // 校验过期时间
        if (!Arrays.asList(1, 3, 5, 10).contains(cmd.getExpireMinutes())) {
            throw new RuntimeException(OtherErrorCode.RED_PACKET_EXPIRE_TIME_ERROR.getMessage());
        }
        
        // 校验金额
        if (cmd.getTotalAmount() < 500 || cmd.getTotalAmount() > 250000) {
            throw new RuntimeException(OtherErrorCode.RED_PACKET_AMOUNT_ERROR.getMessage());
        }
        
        // 校验个数
        if (cmd.getTotalCount() < 1 || cmd.getTotalCount() > 100) {
            throw new RuntimeException(OtherErrorCode.RED_PACKET_COUNT_ERROR.getMessage());
        }
        
        // 校验平均金额（至少1金币/个）
        if (cmd.getTotalAmount() < cmd.getTotalCount()) {
            throw new RuntimeException(OtherErrorCode.RED_PACKET_AMOUNT_ERROR.getMessage());
        }
    }

    /**
     * 扣除用户金币
     */
    private void deductUserGold(Long userId, SendRoomRedPacketCmd cmd, String packetId) {
        GoldReceiptCmd build = GoldReceiptCmd.builder()
                .appExpenditure()
                .userId(userId)
                .amount(PennyAmount.ofDollar(cmd.getTotalAmount()))
                .eventId(packetId)
                .sysOrigin(cmd.getReqSysOrigin().getOrigin())
                .origin(GoldOrigin.ROOM_RED_PACKET)
                .build();
        
        try {
            walletGoldClient.changeBalance(build);
        } catch (Exception e) {
            log.error("扣除金币失败 userId={} amount={} packetId={}", userId, cmd.getTotalAmount(), packetId, e);
            ResponseAssert.isTrue(WalletErrorCode.INSUFFICIENT_BALANCE, false);
        }
    }

    /**
     * 构建红包对象
     */
    private RoomRedPacket buildRedPacket(SendRoomRedPacketCmd cmd, Long userId, 
                                         String packetId, LocalDateTime expireTime,
                                         RoomRedPacketSourceType sourceType) {
        return RoomRedPacket.builder()
                .packetId(packetId)
                .roomId(cmd.getRoomId())
                .userId(userId)
                .packetType(cmd.getPacketType())
                .sourceType(sourceType.getCode())
                .totalAmount(cmd.getTotalAmount())
                .totalCount(cmd.getTotalCount())
                .remainAmount(cmd.getTotalAmount())
                .remainCount(cmd.getTotalCount())
                .expireMinutes(cmd.getExpireMinutes())
                .expireTime(expireTime)
                .status(RoomRedPacketStatus.ACTIVE.getCode())
                .refundStatus(RoomRedPacketRefundStatus.NOT_REFUNDED.getCode())
                .isBroadcast(cmd.getTotalAmount() >= 10000 ? 1 : 0)
                .createTime(LocalDateTime.now())
                .updateTime(LocalDateTime.now())
                .build();
    }

    /**
     * 生成拼手气红包随机金额列表（二倍均值法）
     */
    private List<Long> generateRandomAmounts(Long totalAmount, Integer totalCount) {
        List<Long> amounts = new ArrayList<>(totalCount);
        long remainAmount = totalAmount;
        int remainCount = totalCount;

        for (int i = 0; i < totalCount - 1; i++) {
            // 二倍均值法：随机金额范围 [1, 剩余平均值 * 2]
            long avgAmount = remainAmount / remainCount;
            long maxAmount = Math.min(avgAmount * 2, remainAmount - (remainCount - 1)); // 保证剩余够分
            
            // 确保至少1金币
            long amount = ThreadLocalRandom.current().nextLong(1, Math.max(2, maxAmount + 1));
            
            amounts.add(amount);
            remainAmount -= amount;
            remainCount--;
        }
        
        // 最后一个红包，分配剩余所有金额
        amounts.add(remainAmount);
        
        // 打乱顺序
        Collections.shuffle(amounts);
        
        log.info("生成拼手气红包金额列表 totalAmount={} totalCount={} amounts={}", 
            totalAmount, totalCount, amounts);
        
        return amounts;
    }

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

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