package com.starry.admin.modules.blindbox.service;

import com.starry.admin.common.exception.CustomException;
import com.starry.admin.modules.blindbox.mapper.BlindBoxPoolMapper;
import com.starry.admin.modules.blindbox.mapper.BlindBoxRewardMapper;
import com.starry.admin.modules.blindbox.module.constant.BlindBoxRewardStatus;
import com.starry.admin.modules.blindbox.module.dto.BlindBoxCandidate;
import com.starry.admin.modules.blindbox.module.entity.BlindBoxConfigEntity;
import com.starry.admin.modules.blindbox.module.entity.BlindBoxRewardEntity;
import com.starry.admin.modules.order.module.dto.OrderPlacementResult;
import com.starry.common.utils.IdUtils;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.Clock;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ThreadLocalRandom;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

@Service
public class BlindBoxService {

    private final BlindBoxPoolMapper poolMapper;
    private final BlindBoxRewardMapper rewardMapper;
    private final BlindBoxInventoryService inventoryService;
    private final BlindBoxDispatchService dispatchService;
    private final BlindBoxConfigService configService;
    private final Clock clock;
    private final Duration rewardValidity;
    private final RandomAdapter randomAdapter;

    @Autowired
    public BlindBoxService(
            BlindBoxPoolMapper poolMapper,
            BlindBoxRewardMapper rewardMapper,
            BlindBoxInventoryService inventoryService,
            BlindBoxDispatchService dispatchService,
            BlindBoxConfigService configService) {
        this(poolMapper, rewardMapper, inventoryService, dispatchService, configService, Clock.systemDefaultZone(), Duration.ofDays(30), new DefaultRandomAdapter());
    }

    public BlindBoxService(
            BlindBoxPoolMapper poolMapper,
            BlindBoxRewardMapper rewardMapper,
            BlindBoxInventoryService inventoryService,
            BlindBoxDispatchService dispatchService,
            BlindBoxConfigService configService,
            Clock clock,
            Duration rewardValidity,
            RandomAdapter randomAdapter) {
        this.poolMapper = Objects.requireNonNull(poolMapper, "poolMapper");
        this.rewardMapper = Objects.requireNonNull(rewardMapper, "rewardMapper");
        this.inventoryService = Objects.requireNonNull(inventoryService, "inventoryService");
        this.dispatchService = Objects.requireNonNull(dispatchService, "dispatchService");
        this.configService = Objects.requireNonNull(configService, "configService");
        this.clock = Objects.requireNonNull(clock, "clock");
        this.rewardValidity = Objects.requireNonNull(rewardValidity, "rewardValidity");
        this.randomAdapter = Objects.requireNonNull(randomAdapter, "randomAdapter");
    }

    @Transactional(rollbackFor = Exception.class)
    public BlindBoxRewardEntity drawReward(
            String tenantId,
            String orderId,
            String customerId,
            String blindBoxId,
            String seed) {
        LocalDateTime now = LocalDateTime.now(clock);
        BlindBoxConfigEntity config = configService.requireById(blindBoxId);
        if (!tenantId.equals(config.getTenantId())) {
            throw new CustomException("盲盒不存在或已下架");
        }
        List<BlindBoxCandidate> candidates = poolMapper.listActiveEntries(tenantId, blindBoxId, now);
        if (CollectionUtils.isEmpty(candidates)) {
            throw new CustomException("盲盒奖池暂无可用奖励");
        }

        BlindBoxCandidate selected = selectCandidate(candidates);
        inventoryService.reserveRewardStock(tenantId, selected.getPoolId(), selected.getRewardGiftId());

        BlindBoxRewardEntity entity = new BlindBoxRewardEntity();
        entity.setId(IdUtils.getUuid());
        entity.setTenantId(tenantId);
        entity.setCustomerId(customerId);
        entity.setBlindBoxId(blindBoxId);
        entity.setRewardGiftId(selected.getRewardGiftId());
        entity.setRewardPrice(normalizeMoney(selected.getRewardPrice()));
        entity.setBoxPrice(normalizeMoney(config.getPrice()));
        entity.setSubsidyAmount(entity.getRewardPrice().subtract(entity.getBoxPrice()));
        Integer remainingStock = selected.getRemainingStock();
        entity.setRewardStockSnapshot(remainingStock == null ? null : Math.max(remainingStock - 1, 0));
        entity.setSeed(seed);
        entity.setStatus(BlindBoxRewardStatus.UNUSED.getCode());
        entity.setCreatedByOrder(orderId);
        entity.setExpiresAt(now.plus(rewardValidity));
        entity.setCreatedTime(java.sql.Timestamp.valueOf(now));
        entity.setUpdatedTime(java.sql.Timestamp.valueOf(now));

        rewardMapper.insert(entity);
        return entity;
    }

    @Transactional(rollbackFor = Exception.class)
    public OrderPlacementResult dispatchReward(String rewardId, String clerkId, String customerId) {
        BlindBoxRewardEntity reward = rewardMapper.lockByIdForUpdate(rewardId);
        if (reward == null) {
            throw new CustomException("盲盒奖励不存在");
        }
        if (customerId != null && !customerId.equals(reward.getCustomerId())) {
            throw new CustomException("无权操作该盲盒奖励");
        }
        LocalDateTime now = LocalDateTime.now(clock);
        if (!BlindBoxRewardStatus.UNUSED.getCode().equals(reward.getStatus())) {
            throw new CustomException("盲盒奖励已使用");
        }
        if (reward.getExpiresAt() != null && reward.getExpiresAt().isBefore(now)) {
            throw new CustomException("盲盒奖励已过期");
        }

        OrderPlacementResult result = dispatchService.dispatchRewardOrder(reward, clerkId);
        String orderId = result != null && result.getOrder() != null ? result.getOrder().getId() : null;
        int affected = rewardMapper.markUsed(rewardId, clerkId, orderId, now);
        if (affected <= 0) {
            throw new CustomException("盲盒奖励已使用");
        }
        reward.setStatus(BlindBoxRewardStatus.USED.getCode());
        reward.setUsedClerkId(clerkId);
        reward.setUsedOrderId(orderId);
        reward.setUsedTime(now);
        return result;
    }

    private BlindBoxCandidate selectCandidate(List<BlindBoxCandidate> candidates) {
        int totalWeight = candidates.stream()
                .mapToInt(BlindBoxCandidate::getWeight)
                .sum();
        if (totalWeight <= 0) {
            throw new CustomException("盲盒奖池权重配置异常");
        }
        double threshold = randomAdapter.nextDouble() * totalWeight;
        int cumulative = 0;
        for (BlindBoxCandidate candidate : candidates) {
            cumulative += Math.max(candidate.getWeight(), 0);
            if (threshold < cumulative) {
                return candidate;
            }
        }
        return candidates.get(candidates.size() - 1);
    }

    private BigDecimal normalizeMoney(BigDecimal value) {
        return value == null ? BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP)
                : value.setScale(2, RoundingMode.HALF_UP);
    }

    public java.util.List<BlindBoxRewardEntity> listRewards(String tenantId, String customerId, String status) {
        return rewardMapper.listByCustomer(tenantId, customerId, status);
    }

    public interface RandomAdapter {
        double nextDouble();
    }

    private static class DefaultRandomAdapter implements RandomAdapter {

        @Override
        public double nextDouble() {
            return ThreadLocalRandom.current().nextDouble();
        }
    }
}
