package com.starry.admin.api;

import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.hasItem;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.starry.admin.common.apitest.ApiTestDataSeeder;
import com.starry.admin.modules.blindbox.mapper.BlindBoxPoolMapper;
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.entity.BlindBoxConfigEntity;
import com.starry.admin.modules.blindbox.module.entity.BlindBoxPoolEntity;
import com.starry.admin.modules.blindbox.service.BlindBoxConfigService;
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.utils.IdUtils;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
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;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MvcResult;

class BlindBoxPoolControllerApiTest extends AbstractApiTest {

    private static final String TEST_BLIND_BOX_ID = "blindbox-admin-api";
    private static final DateTimeFormatter DATE_TIME_FORMATTER =
            DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    @Autowired
    private BlindBoxConfigService blindBoxConfigService;

    @Autowired
    private IPlayGiftInfoService giftInfoService;

    @Autowired
    private BlindBoxPoolMapper blindBoxPoolMapper;

    private final ObjectMapper objectMapper = new ObjectMapper();
    private final List<Long> poolIdsToCleanup = new ArrayList<>();
    private final List<String> giftIdsToCleanup = new ArrayList<>();

    @BeforeEach
    void setUp() {
        ensureTenantContext();
        ensureBlindBoxConfig();
    }

    @AfterEach
    void tearDown() {
        ensureTenantContext();
        if (!poolIdsToCleanup.isEmpty()) {
            blindBoxPoolMapper.deleteBatchIds(poolIdsToCleanup);
            poolIdsToCleanup.clear();
        }
        if (!giftIdsToCleanup.isEmpty()) {
            giftInfoService.removeByIds(giftIdsToCleanup);
            giftIdsToCleanup.clear();
        }
    }

    @Test
    // 测试用例：校验奖池管理 API 的新增、更新（禁用/修改权重时间库存）、删除以及礼物选项查询功能，
    // 确保后台盲盒奖池的所有按钮都能正常调用并持久化。
    void adminCanCreateUpdateToggleAndDeletePoolEntries() throws Exception {
        PlayGiftInfoEntity freshlyAddedGift = seedGift("API盲盒新礼物");
        giftIdsToCleanup.add(freshlyAddedGift.getId());

        mockMvc.perform(get("/play/blind-box/pool/gifts")
                        .header(USER_HEADER, DEFAULT_USER)
                        .header(TENANT_HEADER, DEFAULT_TENANT)
                        .param("keyword", "盲盒新礼物"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.code").value(200))
                .andExpect(jsonPath("$.data[*].id", hasItem(freshlyAddedGift.getId())));

        LocalDateTime now = LocalDateTime.now().withNano(0);
        long configurableEntryId = createPoolEntryViaApi(
                ApiTestDataSeeder.DEFAULT_GIFT_ID,
                new BigDecimal("25.88"),
                40,
                8,
                BlindBoxPoolStatus.ENABLED.getCode(),
                now.minusDays(1),
                now.plusDays(5));

        LocalDateTime updatedFrom = now.minusHours(2);
        LocalDateTime updatedTo = now.plusDays(10);
        ObjectNode updatePayload = objectMapper.createObjectNode();
        updatePayload.put("blindBoxId", TEST_BLIND_BOX_ID);
        updatePayload.put("rewardGiftId", ApiTestDataSeeder.DEFAULT_GIFT_ID);
        updatePayload.put("rewardPrice", "35.66");
        updatePayload.put("weight", 75);
        updatePayload.put("remainingStock", 3);
        updatePayload.put("status", BlindBoxPoolStatus.DISABLED.getCode());
        updatePayload.put("validFrom", updatedFrom.format(DATE_TIME_FORMATTER));
        updatePayload.put("validTo", updatedTo.format(DATE_TIME_FORMATTER));

        mockMvc.perform(put("/play/blind-box/pool/" + configurableEntryId)
                        .header(USER_HEADER, DEFAULT_USER)
                        .header(TENANT_HEADER, DEFAULT_TENANT)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(updatePayload.toString()))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.code").value(200))
                .andExpect(jsonPath("$.data.status").value(BlindBoxPoolStatus.DISABLED.getCode()))
                .andExpect(jsonPath("$.data.weight").value(75))
                .andExpect(jsonPath("$.data.remainingStock").value(3));

        ensureTenantContext();
        BlindBoxPoolEntity updated = blindBoxPoolMapper.selectById(configurableEntryId);
        assertThat(updated).isNotNull();
        assertThat(updated.getStatus()).isEqualTo(BlindBoxPoolStatus.DISABLED.getCode());
        assertThat(updated.getWeight()).isEqualTo(75);
        assertThat(updated.getRemainingStock()).isEqualTo(3);
        assertThat(updated.getValidFrom()).isEqualTo(updatedFrom);
        assertThat(updated.getValidTo()).isEqualTo(updatedTo);

        long reusableEntryId = createPoolEntryViaApi(
                freshlyAddedGift.getId(),
                new BigDecimal("18.50"),
                10,
                1,
                BlindBoxPoolStatus.ENABLED.getCode(),
                now.minusDays(2),
                now.plusDays(3));

        updateGiftState(freshlyAddedGift.getId(), GiftState.OFF_SHELF);

        ObjectNode inactiveUpdate = objectMapper.createObjectNode();
        inactiveUpdate.put("blindBoxId", TEST_BLIND_BOX_ID);
        inactiveUpdate.put("rewardGiftId", freshlyAddedGift.getId());
        inactiveUpdate.put("weight", 15);
        inactiveUpdate.put("status", BlindBoxPoolStatus.ENABLED.getCode());
        inactiveUpdate.putNull("remainingStock");

        mockMvc.perform(put("/play/blind-box/pool/" + reusableEntryId)
                        .header(USER_HEADER, DEFAULT_USER)
                        .header(TENANT_HEADER, DEFAULT_TENANT)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(inactiveUpdate.toString()))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.code").value(200))
                .andExpect(jsonPath("$.data.weight").value(15));

        mockMvc.perform(delete("/play/blind-box/pool/" + reusableEntryId)
                        .header(USER_HEADER, DEFAULT_USER)
                        .header(TENANT_HEADER, DEFAULT_TENANT))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.code").value(200));

        ensureTenantContext();
        BlindBoxPoolEntity deleted = blindBoxPoolMapper.selectById(reusableEntryId);
        assertThat(deleted).isNull();
        poolIdsToCleanup.remove(reusableEntryId);
    }

    private long createPoolEntryViaApi(
            String giftId,
            BigDecimal rewardPrice,
            int weight,
            Integer remainingStock,
            int status,
            LocalDateTime validFrom,
            LocalDateTime validTo) throws Exception {
        ObjectNode payload = objectMapper.createObjectNode();
        payload.put("blindBoxId", TEST_BLIND_BOX_ID);
        payload.put("rewardGiftId", giftId);
        payload.put("rewardPrice", rewardPrice.setScale(2, RoundingMode.HALF_UP).toPlainString());
        payload.put("weight", weight);
        if (remainingStock != null) {
            payload.put("remainingStock", remainingStock);
        } else {
            payload.putNull("remainingStock");
        }
        payload.put("status", status);
        payload.put("validFrom", validFrom.format(DATE_TIME_FORMATTER));
        payload.put("validTo", validTo.format(DATE_TIME_FORMATTER));

        MvcResult result = mockMvc.perform(post("/play/blind-box/pool")
                        .header(USER_HEADER, DEFAULT_USER)
                        .header(TENANT_HEADER, DEFAULT_TENANT)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(payload.toString()))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.code").value(200))
                .andExpect(jsonPath("$.data.id").isNumber())
                .andReturn();

        JsonNode response = objectMapper.readTree(result.getResponse().getContentAsString());
        long id = response.path("data").path("id").asLong();
        poolIdsToCleanup.add(id);
        return id;
    }

    private void ensureBlindBoxConfig() {
        BlindBoxConfigEntity existing = blindBoxConfigService.getById(TEST_BLIND_BOX_ID);
        if (existing != null) {
            return;
        }
        BlindBoxConfigEntity entity = new BlindBoxConfigEntity();
        entity.setId(TEST_BLIND_BOX_ID);
        entity.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
        entity.setName("API盲盒（Admin）");
        entity.setPrice(new BigDecimal("19.90"));
        entity.setStatus(BlindBoxConfigStatus.ENABLED.getCode());
        blindBoxConfigService.save(entity);
    }

    private PlayGiftInfoEntity seedGift(String name) {
        PlayGiftInfoEntity gift = new PlayGiftInfoEntity();
        gift.setId("gift-admin-" + IdUtils.getUuid());
        gift.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
        gift.setHistory(GiftHistory.CURRENT.getCode());
        gift.setName(name);
        gift.setType(GiftType.NORMAL.getCode());
        gift.setUrl("https://example.com/assets/" + gift.getId() + ".png");
        gift.setPrice(new BigDecimal("58.80"));
        gift.setUnit("CNY");
        gift.setState(GiftState.ACTIVE.getCode());
        gift.setListingTime(LocalDateTime.now().minusDays(1));
        gift.setRemark("Seeded for blind box pool admin test");
        giftInfoService.save(gift);
        return gift;
    }

    private void updateGiftState(String giftId, GiftState state) {
        ensureTenantContext();
        PlayGiftInfoEntity gift = giftInfoService.getById(giftId);
        if (gift != null) {
            gift.setState(state.getCode());
            giftInfoService.updateById(gift);
        }
    }

    protected void ensureTenantContext() {
        SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
    }
}
