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

import com.red.circle.common.business.core.enums.SysOriginPlatformEnum;
import com.red.circle.common.business.core.level.LevelUtils;
import com.red.circle.framework.core.asserts.ResponseAssert;
import com.red.circle.other.app.convertor.UserRedPacketAppConvertor;
import com.red.circle.other.app.dto.clientobject.UserRedPacketCO;
import com.red.circle.other.app.dto.cmd.SendUserRedPacketCmd;
import com.red.circle.other.domain.gateway.UserRedPacketGateway;
import com.red.circle.other.domain.gateway.user.ability.UserRegionGateway;
import com.red.circle.other.domain.redpacket.RoomRedPacketType;
import com.red.circle.other.domain.redpacket.UserRedPacket;
import com.red.circle.other.domain.redpacket.UserRedPacketStatus;
import com.red.circle.other.infra.database.cache.service.other.UserRedPacketCacheService;
import com.red.circle.other.infra.database.rds.entity.user.user.ConsumptionLevel;
import com.red.circle.other.infra.database.rds.service.user.user.ConsumptionLevelService;
import com.red.circle.other.inner.asserts.OtherErrorCode;
import com.red.circle.other.inner.asserts.user.UserRelationErrorCode;
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.enums.GoldOrigin;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;

/**
 * 发送用户红包命令执行器
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class SendUserRedPacketCmdExe {

    private final UserRedPacketGateway userRedPacketGateway;
    private final UserRedPacketCacheService userRedPacketCacheService;
    private final WalletGoldClient walletGoldClient;
    private final ConsumptionLevelService consumptionLevelService;
    private final UserRegionGateway userRegionGateway;

    /**
     * 手续费比例：10%
     */
    private static final double HANDLING_FEE_RATE = 0.1;

    /**
     * 执行发用户红包
     */
    @Transactional(rollbackFor = Exception.class)
    public UserRedPacketCO execute(SendUserRedPacketCmd cmd) {
        Long senderUserId = cmd.getReqUserId();
        Long receiverUserId = cmd.getReceiverUserId();

        validateParams(cmd);

        ResponseAssert.isFalse(OtherErrorCode.CANNOT_SEND_TO_SELF, senderUserId.equals(receiverUserId));

        String packetId = IdWorkerUtils.getIdStr();

        LocalDateTime expireTime = LocalDateTime.now().plusHours(24);

        Long handlingFee = calculateHandlingFee(cmd.getTotalAmount());

        UserRedPacket redPacket = buildRedPacket(cmd, senderUserId, receiverUserId, packetId, expireTime, handlingFee);
        userRedPacketGateway.save(redPacket);

        deductUserGold(senderUserId, cmd, packetId);

        cacheRedPacketData(redPacket);

        return UserRedPacketAppConvertor.toCO(redPacket);
    }

    /**
     * 计算手续费（10%）
     */
    private Long calculateHandlingFee(Long totalAmount) {
        return (long) Math.ceil(totalAmount * HANDLING_FEE_RATE);
    }

    /**
     * 参数校验
     */
    private void validateParams(SendUserRedPacketCmd cmd) {
        if (cmd.getTotalAmount() < 100 || cmd.getTotalAmount() > 50000) {
            throw new RuntimeException(OtherErrorCode.RED_PACKET_AMOUNT_ERROR.getMessage());
        }

        ConsumptionLevel consumptionLevel = consumptionLevelService.getByUserId(cmd.getReqUserId());
        Integer level = LevelUtils.getWealthLevel(SysOriginPlatformEnum.LIKEI, consumptionLevel.getConsumptionGolds().longValue()).getLevel();
        ResponseAssert.isTrue(OtherErrorCode.USER_WEALTH_NEED_THAN_10, level >= 10);

        boolean eqRegion = userRegionGateway.checkEqRegion(cmd.getReqUserId(), cmd.getReceiverUserId());
        ResponseAssert.isTrue(UserRelationErrorCode.UNAVAILABLE_NOT_REGION, eqRegion);
    }

    /**
     * 扣除用户金币（红包金额 + 手续费）
     */
    private void deductUserGold(Long userId, SendUserRedPacketCmd cmd, String packetId) {
        // 发红包不再扣除手续费
        Long totalDeduct = cmd.getTotalAmount();

        GoldReceiptCmd build = GoldReceiptCmd.builder()
                .appExpenditure()
                .userId(userId)
                .amount(PennyAmount.ofDollar(totalDeduct))
                .eventId(packetId)
                .sysOrigin(cmd.getReqSysOrigin().getOrigin())
                .origin(GoldOrigin.USER_RED_PACKET)
                .build();

        try {
            walletGoldClient.changeBalance(build);
        } catch (Exception e) {
            log.error("扣除金币失败 userId={} totalDeduct={} packetId={}", userId, totalDeduct, packetId, e);
            ResponseAssert.isTrue(WalletErrorCode.INSUFFICIENT_BALANCE, false);
        }
    }

    /**
     * 构建红包对象
     */
    private UserRedPacket buildRedPacket(SendUserRedPacketCmd cmd, Long senderUserId,
                                         Long receiverUserId, String packetId, LocalDateTime expireTime,
                                         Long handlingFee) {
        return UserRedPacket.builder()
                .packetId(packetId)
                .senderUserId(senderUserId)
                .receiverUserId(receiverUserId)
                .packetType(RoomRedPacketType.FIXED.getCode())
                .totalAmount(cmd.getTotalAmount() - handlingFee)
                .handlingFee(handlingFee)
                .expireTime(expireTime)
                .status(UserRedPacketStatus.PENDING.getCode())
                .refundStatus(0)
                .createTime(LocalDateTime.now())
                .updateTime(LocalDateTime.now())
                .build();
    }

    /**
     * 缓存红包数据到Redis
     */
    private void cacheRedPacketData(UserRedPacket redPacket) {
        Map<String, Object> packetData = new HashMap<>();
        packetData.put("senderUserId", redPacket.getSenderUserId());
        packetData.put("receiverUserId", redPacket.getReceiverUserId());
        packetData.put("totalAmount", redPacket.getTotalAmount());
        packetData.put("handlingFee", redPacket.getHandlingFee());
        packetData.put("expireTime", redPacket.getExpireTime().toString());
        packetData.put("status", redPacket.getStatus());

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

}
