package com.starry.admin.api;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.starry.admin.common.apitest.ApiTestDataSeeder;
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.BlindBoxConfigStatus;
import com.starry.admin.modules.blindbox.module.constant.BlindBoxPoolStatus;
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.BlindBoxPoolEntity;
import com.starry.admin.modules.blindbox.module.entity.BlindBoxRewardEntity;
import com.starry.admin.modules.blindbox.service.BlindBoxConfigService;
import com.starry.admin.modules.blindbox.service.BlindBoxService;
import com.starry.admin.modules.shop.module.constant.GiftHistory;
import com.starry.admin.modules.shop.module.constant.GiftState;
import com.starry.admin.modules.shop.module.constant.GiftType;
import com.starry.admin.modules.shop.module.entity.PlayGiftInfoEntity;
import com.starry.admin.modules.shop.service.IPlayGiftInfoService;
import com.starry.admin.utils.SecurityUtils;
import com.starry.common.context.CustomSecurityContextHolder;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;

class BlindBoxServiceWeightTest extends WxCustomOrderApiTestSupport {

    private static final String TEST_BLIND_BOX_ID = "blindbox-apitest";
    private static final String PRIMARY_GIFT_ID = ApiTestDataSeeder.DEFAULT_GIFT_ID;
    private static final String SECONDARY_GIFT_ID = "gift-blindbox-secondary";
    private static final String MIXED_GIFT_A_ID = "gift-blindbox-mixed-a";
    private static final String MIXED_GIFT_B_ID = "gift-blindbox-mixed-b";
    private static final String MIXED_GIFT_C_ID = "gift-blindbox-mixed-c";
    private static final String MIXED_GIFT_D_ID = "gift-blindbox-mixed-d";
    private static final String MIXED_GIFT_E_ID = "gift-blindbox-mixed-e";
    private static final int DRAW_ATTEMPT_COUNT = 1_000;
    private static final int PRIMARY_WEIGHT = 80;
    private static final int SECONDARY_WEIGHT = 20;
    private static final double PRIMARY_RATIO_MIN = 0.68;
    private static final double PRIMARY_RATIO_MAX = 0.88;
    private static final double SECONDARY_RATIO_MIN = 0.12;
    private static final double SECONDARY_RATIO_MAX = 0.32;
    private static final int MIXED_TOTAL_DRAWS = 400;
    private static final int MIXED_GIFT_A_STOCK = 10;
    private static final int MIXED_GIFT_B_STOCK = 5;
    private static final int MIXED_GIFT_C_STOCK = 0;
    private static final int MIXED_GIFT_E_STOCK = 2;

    @Autowired
    private BlindBoxService blindBoxService;

    @Autowired
    private BlindBoxConfigService blindBoxConfigService;

    @Autowired
    private BlindBoxPoolMapper blindBoxPoolMapper;

    @Autowired
    private BlindBoxRewardMapper blindBoxRewardMapper;

    @Autowired
    private IPlayGiftInfoService giftInfoService;

    @BeforeEach
    void setUpTenant() {
        SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
    }

    @AfterEach
    void clearContext() {
        CustomSecurityContextHolder.remove();
    }

    @Test
    // 测试用例：在默认租户下补齐盲盒配置以及主、副礼物后，以 80/20 的权重写入奖池，
    // 连续执行 1000 次盲盒抽奖，既校验每次抽奖都有奖励返回且两种礼物都被抽中，
    // 也验证主礼物命中率位于 68%~88%、副礼物命中率位于 12%~32%，确保抽奖概率符合权重设定。
    void blindBoxDrawRespectsWeightDistribution() {
        ensureTenantContext();
        ensureBlindBoxConfig();
        ensurePrimaryGift();
        ensureSecondaryGift();
        resetCustomerBalance();

        purgeRewards();
        purgePool();
        insertPoolEntry(PRIMARY_GIFT_ID, PRIMARY_WEIGHT);
        insertPoolEntry(SECONDARY_GIFT_ID, SECONDARY_WEIGHT);

        Map<String, Integer> frequency = new HashMap<>();
        for (int i = 0; i < DRAW_ATTEMPT_COUNT; i++) {
            BlindBoxRewardEntity reward = blindBoxService.drawReward(
                    ApiTestDataSeeder.DEFAULT_TENANT_ID,
                    "order-" + i,
                    ApiTestDataSeeder.DEFAULT_CUSTOMER_ID,
                    TEST_BLIND_BOX_ID,
                    "seed-" + i);
            frequency.merge(reward.getRewardGiftId(), 1, Integer::sum);
        }

        int primaryCount = frequency.getOrDefault(PRIMARY_GIFT_ID, 0);
        int secondaryCount = frequency.getOrDefault(SECONDARY_GIFT_ID, 0);
        int total = primaryCount + secondaryCount;

        Assertions.assertThat(total).isEqualTo(DRAW_ATTEMPT_COUNT);
        Assertions.assertThat(primaryCount).isGreaterThan(0);
        Assertions.assertThat(secondaryCount).isGreaterThan(0);

        double primaryRatio = primaryCount / (double) total;
        double secondaryRatio = secondaryCount / (double) total;

        Assertions.assertThat(primaryRatio).isBetween(PRIMARY_RATIO_MIN, PRIMARY_RATIO_MAX);
        Assertions.assertThat(secondaryRatio).isBetween(SECONDARY_RATIO_MIN, SECONDARY_RATIO_MAX);

        purgeRewards();
        purgePool();
    }

    @Test
    // 测试用例：混合 5 种礼物（部分限量、部分不限量），验证限量礼物被抽满后不再出现，
    // 不限量礼物可继续抽取，且库存为 0 的礼物永远不会返回。
    void blindBoxDrawHandlesMixedInventory() {
        ensureTenantContext();
        ensureBlindBoxConfig();
        ensureMixedGifts();
        resetCustomerBalance();

        purgeRewards();
        purgePool();
        insertPoolEntry(MIXED_GIFT_A_ID, 40, MIXED_GIFT_A_STOCK);
        insertPoolEntry(MIXED_GIFT_B_ID, 25, MIXED_GIFT_B_STOCK);
        insertPoolEntry(MIXED_GIFT_C_ID, 15, MIXED_GIFT_C_STOCK);
        insertPoolEntry(MIXED_GIFT_D_ID, 10, null);
        insertPoolEntry(MIXED_GIFT_E_ID, 10, MIXED_GIFT_E_STOCK);

        Map<String, Integer> frequency = new HashMap<>();
        for (int i = 0; i < MIXED_TOTAL_DRAWS; i++) {
            BlindBoxRewardEntity reward = blindBoxService.drawReward(
                    ApiTestDataSeeder.DEFAULT_TENANT_ID,
                    "mixed-order-" + i,
                    ApiTestDataSeeder.DEFAULT_CUSTOMER_ID,
                    TEST_BLIND_BOX_ID,
                    "mixed-seed-" + i);
            frequency.merge(reward.getRewardGiftId(), 1, Integer::sum);
        }

        Assertions.assertThat(frequency.getOrDefault(MIXED_GIFT_C_ID, 0))
                .as("库存为 0 的礼物永远不应被抽中")
                .isEqualTo(0);
        Assertions.assertThat(frequency.getOrDefault(MIXED_GIFT_A_ID, 0))
                .as("限量礼物 A 应被精确抽完")
                .isEqualTo(MIXED_GIFT_A_STOCK);
        Assertions.assertThat(frequency.getOrDefault(MIXED_GIFT_B_ID, 0))
                .as("限量礼物 B 应被精确抽完")
                .isEqualTo(MIXED_GIFT_B_STOCK);
        Assertions.assertThat(frequency.getOrDefault(MIXED_GIFT_E_ID, 0))
                .as("限量礼物 E 应被精确抽完")
                .isEqualTo(MIXED_GIFT_E_STOCK);

        int unlimitedCount = frequency.getOrDefault(MIXED_GIFT_D_ID, 0);
        int finiteTotal = MIXED_GIFT_A_STOCK + MIXED_GIFT_B_STOCK + MIXED_GIFT_E_STOCK;
        Assertions.assertThat(unlimitedCount)
                .as("不限量礼物承担剩余抽奖次数")
                .isEqualTo(MIXED_TOTAL_DRAWS - finiteTotal)
                .isGreaterThan(0);

        // 抽完后，奖池中仅剩不限量礼物
        java.util.List<BlindBoxCandidate> remaining = blindBoxPoolMapper.listActiveEntries(
                ApiTestDataSeeder.DEFAULT_TENANT_ID, TEST_BLIND_BOX_ID, LocalDateTime.now());
        Assertions.assertThat(remaining)
                .as("只剩不限量礼物可用")
                .hasSize(1);
        Assertions.assertThat(remaining.get(0).getRewardGiftId()).isEqualTo(MIXED_GIFT_D_ID);

        purgeRewards();
        purgePool();
    }

    private void ensureBlindBoxConfig() {
        BlindBoxConfigEntity config = blindBoxConfigService.getById(TEST_BLIND_BOX_ID);
        if (config != null) {
            return;
        }
        BlindBoxConfigEntity entity = new BlindBoxConfigEntity();
        entity.setId(TEST_BLIND_BOX_ID);
        entity.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
        entity.setName("API盲盒");
        entity.setCoverUrl("https://example.com/apitest/blindbox-cover.png");
        entity.setDescription("Seeded blind box for integration tests");
        entity.setPrice(new BigDecimal("19.90"));
        entity.setStatus(BlindBoxConfigStatus.ENABLED.getCode());
        blindBoxConfigService.save(entity);
    }

    private void ensureSecondaryGift() {
        ensureGift(
                SECONDARY_GIFT_ID,
                "API盲盒奖励",
                new BigDecimal("9.99"),
                "https://example.com/apitest/blindbox.png",
                "Seeded secondary gift for blind box tests");
    }

    private void ensurePrimaryGift() {
        ensureGift(
                PRIMARY_GIFT_ID,
                ApiTestDataSeeder.DEFAULT_GIFT_NAME,
                new BigDecimal("15.00"),
                "https://example.com/apitest/gift-basic.png",
                "Seeded default gift for blind box tests");
    }

    private void ensureMixedGifts() {
        ensureGift(
                MIXED_GIFT_A_ID,
                "API盲盒混合A",
                new BigDecimal("11.11"),
                "https://example.com/apitest/mixed-a.png",
                "Mixed blind box gift A");
        ensureGift(
                MIXED_GIFT_B_ID,
                "API盲盒混合B",
                new BigDecimal("22.22"),
                "https://example.com/apitest/mixed-b.png",
                "Mixed blind box gift B");
        ensureGift(
                MIXED_GIFT_C_ID,
                "API盲盒混合C",
                new BigDecimal("33.33"),
                "https://example.com/apitest/mixed-c.png",
                "Mixed blind box gift C");
        ensureGift(
                MIXED_GIFT_D_ID,
                "API盲盒混合D",
                new BigDecimal("44.44"),
                "https://example.com/apitest/mixed-d.png",
                "Mixed blind box gift D");
        ensureGift(
                MIXED_GIFT_E_ID,
                "API盲盒混合E",
                new BigDecimal("55.55"),
                "https://example.com/apitest/mixed-e.png",
                "Mixed blind box gift E");
    }

    private void ensureGift(String giftId, String name, BigDecimal price, String imageUrl, String remark) {
        PlayGiftInfoEntity existing = findGift(giftId);
        if (existing != null) {
            return;
        }
        PlayGiftInfoEntity entity = new PlayGiftInfoEntity();
        entity.setId(giftId);
        entity.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
        entity.setHistory(GiftHistory.CURRENT.getCode());
        entity.setName(name);
        entity.setType(GiftType.NORMAL.getCode());
        entity.setUrl(imageUrl);
        entity.setPrice(price);
        entity.setUnit("CNY");
        entity.setState(GiftState.ACTIVE.getCode());
        entity.setListingTime(LocalDateTime.now());
        entity.setRemark(remark);
        giftInfoService.save(entity);
    }

    private void insertPoolEntry(String giftId, int weight) {
        insertPoolEntry(giftId, weight, null);
    }

    private void insertPoolEntry(String giftId, int weight, Integer remainingStock) {
        PlayGiftInfoEntity gift = findGift(giftId);
        if (gift == null) {
            throw new IllegalStateException("Expected gift to be seeded: " + giftId);
        }
        BlindBoxPoolEntity entry = new BlindBoxPoolEntity();
        entry.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
        entry.setBlindBoxId(TEST_BLIND_BOX_ID);
        entry.setRewardGiftId(giftId);
        entry.setRewardPrice(gift.getPrice());
        entry.setWeight(weight);
        entry.setRemainingStock(remainingStock);
        entry.setValidFrom(null);
        entry.setValidTo(null);
        entry.setStatus(BlindBoxPoolStatus.ENABLED.getCode());
        blindBoxPoolMapper.insert(entry);
    }

    private PlayGiftInfoEntity findGift(String giftId) {
        try {
            return giftInfoService.selectPlayGiftInfoById(giftId);
        } catch (CustomException ignored) {
            return null;
        }
    }

    private void purgePool() {
        blindBoxPoolMapper.delete(new LambdaQueryWrapper<BlindBoxPoolEntity>()
                .eq(BlindBoxPoolEntity::getTenantId, ApiTestDataSeeder.DEFAULT_TENANT_ID)
                .eq(BlindBoxPoolEntity::getBlindBoxId, TEST_BLIND_BOX_ID));
    }

    private void purgeRewards() {
        blindBoxRewardMapper.delete(new LambdaQueryWrapper<BlindBoxRewardEntity>()
                .eq(BlindBoxRewardEntity::getTenantId, ApiTestDataSeeder.DEFAULT_TENANT_ID)
                .eq(BlindBoxRewardEntity::getBlindBoxId, TEST_BLIND_BOX_ID)
                .eq(BlindBoxRewardEntity::getCustomerId, ApiTestDataSeeder.DEFAULT_CUSTOMER_ID));
    }
}
