package com.starry.admin.api;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
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.starry.admin.common.apitest.ApiTestDataSeeder;
import com.starry.admin.modules.blindbox.mapper.BlindBoxPoolMapper;
import com.starry.admin.modules.blindbox.mapper.BlindBoxRewardMapper;
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.order.module.constant.OrderConstant;
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
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.PlayClerkGiftInfoEntity;
import com.starry.admin.modules.shop.module.entity.PlayGiftInfoEntity;
import com.starry.admin.modules.shop.service.IPlayClerkGiftInfoService;
import com.starry.admin.modules.shop.service.IPlayGiftInfoService;
import com.starry.admin.utils.SecurityUtils;
import com.starry.common.constant.Constants;
import com.starry.common.context.CustomSecurityContextHolder;
import com.starry.common.utils.IdUtils;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Objects;
import org.assertj.core.api.Assertions;
import org.assertj.core.api.SoftAssertions;
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 WxBlindBoxOrderApiTest extends WxCustomOrderApiTestSupport {

    @Autowired
    private BlindBoxConfigService blindBoxConfigService;
    @Autowired
    private BlindBoxPoolMapper blindBoxPoolMapper;
    @Autowired
    private IPlayGiftInfoService giftInfoService;
    @Autowired
    private BlindBoxRewardMapper blindBoxRewardMapper;
    @Autowired
    private IPlayClerkGiftInfoService clerkGiftInfoService;

    @Test
    void blindBoxPurchaseFailsWhenBalanceInsufficient() throws Exception {
        SecurityUtils.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
        String configId = "blind-" + IdUtils.getUuid();
        try {
            BlindBoxConfigEntity config = new BlindBoxConfigEntity();
            config.setId(configId);
            config.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
            config.setName("API盲盒-" + IdUtils.getUuid().substring(0, 6));
            config.setPrice(new BigDecimal("66.00"));
            config.setStatus(1);
            blindBoxConfigService.save(config);

            setCustomerBalance(BigDecimal.ZERO);
            String customerToken = wxTokenService.createWxUserToken(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID);
            customUserInfoService.updateTokenById(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID, customerToken);

            ensureTenantContext();
            long beforeCount = playOrderInfoService.lambdaQuery()
                    .eq(PlayOrderInfoEntity::getPurchaserBy, ApiTestDataSeeder.DEFAULT_CUSTOMER_ID)
                    .eq(PlayOrderInfoEntity::getOrderType, OrderConstant.OrderType.BLIND_BOX_PURCHASE.getCode())
                    .count();

            String payload = "{" +
                    "\"blindBoxId\":\"" + configId + "\"," +
                    "\"clerkId\":\"" + ApiTestDataSeeder.DEFAULT_CLERK_ID + "\"," +
                    "\"weiChatCode\":\"apitest-customer-wx\"" +
                    "}";

            mockMvc.perform(post("/wx/blind-box/order/purchase")
                            .header(USER_HEADER, DEFAULT_USER)
                            .header(TENANT_HEADER, DEFAULT_TENANT)
                            .header(Constants.CUSTOM_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + customerToken)
                            .contentType(MediaType.APPLICATION_JSON)
                            .content(payload))
                    .andExpect(status().isOk())
                    .andExpect(jsonPath("$.code").value(998));

            ensureTenantContext();
            long afterCount = playOrderInfoService.lambdaQuery()
                    .eq(PlayOrderInfoEntity::getPurchaserBy, ApiTestDataSeeder.DEFAULT_CUSTOMER_ID)
                    .eq(PlayOrderInfoEntity::getOrderType, OrderConstant.OrderType.BLIND_BOX_PURCHASE.getCode())
                    .count();
            Assertions.assertThat(afterCount).isEqualTo(beforeCount);
        } finally {
            blindBoxConfigService.removeById(configId);
            CustomSecurityContextHolder.remove();
        }
    }

    @Test
    void blindBoxPurchaseAndDispatchSucceedWhenGiftInactive() throws Exception {
        String configId = "blind-inactive-" + IdUtils.getUuid().substring(0, 6);
        String giftId = "gift-inactive-" + IdUtils.getUuid().substring(0, 6);
        Long poolId = null;
        String rewardId = null;
        try {
            ensureTenantContext();
            BlindBoxConfigEntity config = new BlindBoxConfigEntity();
            config.setId(configId);
            config.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
            config.setName("下架礼物测试盲盒");
            config.setPrice(new BigDecimal("19.90"));
            config.setStatus(1);
            blindBoxConfigService.save(config);

            PlayGiftInfoEntity gift = new PlayGiftInfoEntity();
            gift.setId(giftId);
            gift.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
            gift.setName("盲盒下架礼物");
            gift.setHistory(GiftHistory.CURRENT.getCode());
            gift.setState(GiftState.ACTIVE.getCode());
            gift.setType(GiftType.NORMAL.getCode());
            gift.setUrl("https://example.com/apitest/blindbox-off.png");
            gift.setPrice(new BigDecimal("9.99"));
            giftInfoService.save(gift);

            BlindBoxPoolEntity entry = new BlindBoxPoolEntity();
            entry.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
            entry.setBlindBoxId(configId);
            entry.setRewardGiftId(giftId);
            entry.setRewardPrice(gift.getPrice());
            entry.setWeight(100);
            entry.setRemainingStock(1);
            entry.setStatus(1);
            entry.setValidFrom(LocalDateTime.now().minusDays(1));
            entry.setValidTo(LocalDateTime.now().plusDays(1));
            blindBoxPoolMapper.insert(entry);
            poolId = entry.getId();

            gift.setState(GiftState.OFF_SHELF.getCode());
            giftInfoService.updateById(gift);

            resetCustomerBalance();
            String customerToken = wxTokenService.createWxUserToken(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID);
            customUserInfoService.updateTokenById(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID, customerToken);

            String payload = objectMapper.createObjectNode()
                    .put("blindBoxId", configId)
                    .put("clerkId", ApiTestDataSeeder.DEFAULT_CLERK_ID)
                    .put("weiChatCode", "apitest-customer-wx")
                    .toString();

            MvcResult purchaseResult = mockMvc.perform(post("/wx/blind-box/order/purchase")
                            .header(USER_HEADER, DEFAULT_USER)
                            .header(TENANT_HEADER, DEFAULT_TENANT)
                            .header(Constants.CUSTOM_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + customerToken)
                            .contentType(MediaType.APPLICATION_JSON)
                            .content(payload))
                    .andExpect(status().isOk())
                    .andExpect(jsonPath("$.code").value(200))
                    .andExpect(jsonPath("$.data.reward.rewardId").isString())
                    .andReturn();

            JsonNode rewardNode = objectMapper.readTree(purchaseResult.getResponse().getContentAsString())
                    .path("data").path("reward");
            rewardId = rewardNode.path("rewardId").asText();
            Assertions.assertThat(rewardId).isNotBlank();

            SoftAssertions softly = new SoftAssertions();
            softly.assertThat(rewardNode.path("rewardGiftId").asText()).isEqualTo(giftId);
            softly.assertThat(rewardNode.path("status").asText()).isEqualTo("UNUSED");
            softly.assertAll();

            PlayClerkGiftInfoEntity before = clerkGiftInfoService.selectByGiftIdAndClerkId(giftId, ApiTestDataSeeder.DEFAULT_CLERK_ID);
            long beforeCount = before != null && before.getGiffNumber() != null ? before.getGiffNumber() : 0L;

            mockMvc.perform(post("/wx/blind-box/reward/" + rewardId + "/dispatch")
                            .header(USER_HEADER, DEFAULT_USER)
                            .header(TENANT_HEADER, DEFAULT_TENANT)
                            .header(Constants.CUSTOM_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + customerToken)
                            .contentType(MediaType.APPLICATION_JSON)
                            .content(objectMapper.createObjectNode()
                                    .put("clerkId", ApiTestDataSeeder.DEFAULT_CLERK_ID)
                                    .toString()))
                    .andExpect(status().isOk())
                    .andExpect(jsonPath("$.code").value(200))
                    .andExpect(jsonPath("$.data.status").value("USED"));

            ensureTenantContext();
            BlindBoxRewardEntity storedReward = blindBoxRewardMapper.selectById(rewardId);
            Assertions.assertThat(storedReward).isNotNull();
            Assertions.assertThat(storedReward.getStatus()).isEqualTo("USED");
            Assertions.assertThat(storedReward.getUsedClerkId()).isEqualTo(ApiTestDataSeeder.DEFAULT_CLERK_ID);

            PlayClerkGiftInfoEntity after = clerkGiftInfoService.selectByGiftIdAndClerkId(giftId, ApiTestDataSeeder.DEFAULT_CLERK_ID);
            long afterCount = after != null && after.getGiffNumber() != null ? after.getGiffNumber() : 0L;
            Assertions.assertThat(afterCount).isEqualTo(beforeCount + 1);
        } finally {
            ensureTenantContext();
            if (Objects.nonNull(poolId)) {
                blindBoxPoolMapper.deleteById(poolId);
            }
            blindBoxConfigService.removeById(configId);
            giftInfoService.removeById(giftId);
            if (rewardId != null) {
                blindBoxRewardMapper.deleteById(rewardId);
            }
            CustomSecurityContextHolder.remove();
        }
    }
}
