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.get;
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.fasterxml.jackson.databind.node.ObjectNode;
import com.starry.admin.common.apitest.ApiTestDataSeeder;
import com.starry.admin.modules.clerk.module.entity.PlayClerkCommodityEntity;
import com.starry.admin.modules.clerk.module.entity.PlayClerkLevelInfoEntity;
import com.starry.admin.modules.clerk.service.IPlayClerkCommodityService;
import com.starry.admin.modules.clerk.service.IPlayClerkLevelInfoService;
import com.starry.admin.modules.order.module.constant.OrderConstant;
import com.starry.admin.modules.shop.module.entity.PlayCommodityAndLevelInfoEntity;
import com.starry.admin.modules.shop.module.entity.PlayCommodityInfoEntity;
import com.starry.admin.modules.shop.service.IPlayCommodityAndLevelInfoService;
import com.starry.admin.modules.shop.service.IPlayCommodityInfoService;
import com.starry.admin.utils.SecurityUtils;
import com.starry.common.constant.Constants;
import com.starry.common.utils.IdUtils;
import java.math.BigDecimal;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
import org.junit.jupiter.api.AfterEach;
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 PlayCommodityInfoApiTest extends WxCustomOrderApiTestSupport {

    private static final String ROOT_PARENT_ID = "00";
    private static final long DEFAULT_PRICE_SORT = 1L;
    private static final int SINGLE_COMMODITY_QUANTITY = 1;

    private enum SwitchState {
        ENABLED("1"),
        DISABLED("0");

        private final String code;

        SwitchState(String code) {
            this.code = code;
        }

        String getCode() {
            return code;
        }
    }

    private enum HistoryFilter {
        INCLUDE("0"),
        EXCLUDE("1");

        private final String code;

        HistoryFilter(String code) {
            this.code = code;
        }

        String getCode() {
            return code;
        }
    }

    private enum SortPriority {
        PRIMARY(1),
        SECONDARY(2);

        private final int value;

        SortPriority(int value) {
            this.value = value;
        }

        int getValue() {
            return value;
        }
    }

    private enum AutomaticSettlementPolicy {
        DISABLED(-1),
        TEN_MINUTES(600);

        private final int seconds;

        AutomaticSettlementPolicy(int seconds) {
            this.seconds = seconds;
        }

        int getSeconds() {
            return seconds;
        }
    }

    @Autowired
    private IPlayCommodityInfoService commodityInfoService;

    @Autowired
    private IPlayCommodityAndLevelInfoService commodityAndLevelInfoService;

    @Autowired
    private IPlayClerkCommodityService clerkCommodityService;

    @Autowired
    private IPlayClerkLevelInfoService clerkLevelInfoService;

    private final Deque<String> commodityIdsToCleanup = new ArrayDeque<>();
    private final List<String> priceIdsToCleanup = new ArrayList<>();
    private final List<String> clerkCommodityIdsToCleanup = new ArrayList<>();

    @AfterEach
    void tearDown() {
        ensureTenantContext();
        if (!priceIdsToCleanup.isEmpty()) {
            commodityAndLevelInfoService.removeByIds(priceIdsToCleanup);
            priceIdsToCleanup.clear();
        }
        if (!clerkCommodityIdsToCleanup.isEmpty()) {
            clerkCommodityService.removeByIds(clerkCommodityIdsToCleanup);
            clerkCommodityIdsToCleanup.clear();
        }
        while (!commodityIdsToCleanup.isEmpty()) {
            String commodityId = commodityIdsToCleanup.removeFirst();
            commodityInfoService.removeById(commodityId);
        }
    }

    @Test
    // 测试用例：调用商品表头接口，验证默认店员等级会出现在表头列表中，确保管理端可正确展示等级价格列。
    void tableNameEndpointReturnsClerkLevels() throws Exception {
        ensureTenantContext();
        PlayClerkLevelInfoEntity level =
                clerkLevelInfoService.getById(ApiTestDataSeeder.DEFAULT_CLERK_LEVEL_ID);

        mockMvc.perform(get("/shop/commodity/getTableName")
                        .header(USER_HEADER, DEFAULT_USER)
                        .header(TENANT_HEADER, DEFAULT_TENANT))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.code").value(200))
                .andExpect(jsonPath("$.data[*].prop", hasItem(ApiTestDataSeeder.DEFAULT_CLERK_LEVEL_ID)))
                .andExpect(jsonPath("$.data[*].name", hasItem(level.getName())));
    }

    @Test
    // 测试用例：查询所有商品树，验证种子数据的父子节点与对应等级价格一起返回，确保层级结构与定价展示正确。
    void listAllIncludesSeedCommodityWithLevelPrice() throws Exception {
        ensureTenantContext();
        MvcResult result = mockMvc.perform(get("/shop/commodity/listAll")
                        .header(USER_HEADER, DEFAULT_USER)
                        .header(TENANT_HEADER, DEFAULT_TENANT))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.code").value(200))
                .andReturn();

        JsonNode root = objectMapper.readTree(result.getResponse().getContentAsString());
        JsonNode data = root.get("data");
        assertThat(data.isArray()).isTrue();

        JsonNode parentNode = findNodeById(data, ApiTestDataSeeder.DEFAULT_COMMODITY_PARENT_ID);
        assertThat(parentNode).isNotNull();

        JsonNode children = parentNode.get("children");
        JsonNode childNode = findNodeById(children, ApiTestDataSeeder.DEFAULT_COMMODITY_ID);
        assertThat(childNode).isNotNull();

        BigDecimal price = new BigDecimal(childNode.get(ApiTestDataSeeder.DEFAULT_CLERK_LEVEL_ID).asText());
        assertThat(price).isEqualByComparingTo(ApiTestDataSeeder.DEFAULT_COMMODITY_PRICE);
    }

    @Test
    // 测试用例：调用价格更新接口，为新创建的子商品配置等级价格，验证价格落库并可复查，确保后台配置生效。
    void updateInfoAssignsLevelPricing() throws Exception {
        ensureTenantContext();
        String parentId = generateId("svc-parent-");
        String childId = generateId("svc-child-");

        PlayCommodityInfoEntity parent = createCommodity(
                parentId,
                ROOT_PARENT_ID,
                "API测试父类目",
                "N/A",
                SwitchState.ENABLED,
                SortPriority.PRIMARY,
                AutomaticSettlementPolicy.DISABLED);
        PlayCommodityInfoEntity child = createCommodity(
                childId,
                parent.getId(),
                "API测试子项目",
                "45min",
                SwitchState.ENABLED,
                SortPriority.PRIMARY,
                AutomaticSettlementPolicy.DISABLED);

        ObjectNode payload = objectMapper.createObjectNode();
        payload.put("id", child.getId());
        payload.put(ApiTestDataSeeder.DEFAULT_CLERK_LEVEL_ID, "188.50");

        mockMvc.perform(post("/shop/commodity/updateInfo")
                        .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));

        ensureTenantContext();
        PlayCommodityAndLevelInfoEntity pricing = commodityAndLevelInfoService.lambdaQuery()
                .eq(PlayCommodityAndLevelInfoEntity::getCommodityId, child.getId())
                .eq(PlayCommodityAndLevelInfoEntity::getLevelId, ApiTestDataSeeder.DEFAULT_CLERK_LEVEL_ID)
                .one();

        assertThat(pricing).isNotNull();
        priceIdsToCleanup.add(pricing.getId());
        assertThat(pricing.getPrice()).isEqualByComparingTo(new BigDecimal("188.50"));
    }

    @Test
    // 测试用例：调用价格更新接口时若某个等级价格传入null，接口应忽略该列并保持原价，确保支持分步维护价格。
    void updateInfoSkipsNullLevelPrices() throws Exception {
        ensureTenantContext();
        PlayCommodityAndLevelInfoEntity before = commodityAndLevelInfoService.lambdaQuery()
                .eq(PlayCommodityAndLevelInfoEntity::getCommodityId, ApiTestDataSeeder.DEFAULT_COMMODITY_ID)
                .eq(PlayCommodityAndLevelInfoEntity::getLevelId, ApiTestDataSeeder.DEFAULT_CLERK_LEVEL_ID)
                .one();

        assertThat(before).as("种子数据应具备默认等级价格").isNotNull();

        ObjectNode payload = objectMapper.createObjectNode();
        payload.put("id", ApiTestDataSeeder.DEFAULT_COMMODITY_ID);
        payload.putNull(ApiTestDataSeeder.DEFAULT_CLERK_LEVEL_ID);

        mockMvc.perform(post("/shop/commodity/updateInfo")
                        .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));

        ensureTenantContext();
        PlayCommodityAndLevelInfoEntity after = commodityAndLevelInfoService.lambdaQuery()
                .eq(PlayCommodityAndLevelInfoEntity::getCommodityId, ApiTestDataSeeder.DEFAULT_COMMODITY_ID)
                .eq(PlayCommodityAndLevelInfoEntity::getLevelId, ApiTestDataSeeder.DEFAULT_CLERK_LEVEL_ID)
                .one();

        assertThat(after).isNotNull();
        assertThat(after.getPrice()).as("空价格不应覆盖原值").isEqualByComparingTo(before.getPrice());
    }

    @Test
    // 测试用例：使用商品修改接口把自动结算等待时长从不限时（-1）调整为10分钟，验证更新后查询返回新的配置。
    void updateEndpointSwitchesAutomaticSettlement() throws Exception {
        ensureTenantContext();
        String parentId = generateId("svc-parent-");
        String childId = generateId("svc-child-");

        PlayCommodityInfoEntity parent = createCommodity(
                parentId,
                ROOT_PARENT_ID,
                "自动结算父类目",
                "N/A",
                SwitchState.ENABLED,
                SortPriority.PRIMARY,
                AutomaticSettlementPolicy.DISABLED);
        PlayCommodityInfoEntity child = createCommodity(
                childId,
                parent.getId(),
                "自动结算子项",
                "30min",
                SwitchState.ENABLED,
                SortPriority.PRIMARY,
                AutomaticSettlementPolicy.DISABLED);

        PlayCommodityInfoEntity beforeUpdate = commodityInfoService.getById(child.getId());
        assertThat(beforeUpdate.getAutomaticSettlementDuration())
                .isEqualTo(AutomaticSettlementPolicy.DISABLED.getSeconds());

        PlayCommodityInfoEntity updateBody = new PlayCommodityInfoEntity();
        updateBody.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
        updateBody.setPId(parent.getId());
        updateBody.setItemType(child.getItemType());
        updateBody.setItemName(child.getItemName());
        updateBody.setServiceDuration("30min");
        updateBody.setAutomaticSettlementDuration(AutomaticSettlementPolicy.TEN_MINUTES.getSeconds());
        updateBody.setEnableStace(SwitchState.ENABLED.getCode());
        updateBody.setSort(SortPriority.PRIMARY.getValue());

        mockMvc.perform(post("/shop/commodity/update/" + child.getId())
                        .header(USER_HEADER, DEFAULT_USER)
                        .header(TENANT_HEADER, DEFAULT_TENANT)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(updateBody)))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.code").value(200));

        ensureTenantContext();
        PlayCommodityInfoEntity updated = commodityInfoService.getById(child.getId());
        assertThat(updated.getAutomaticSettlementDuration())
                .isEqualTo(AutomaticSettlementPolicy.TEN_MINUTES.getSeconds());
    }

    @Test
    // 测试用例：为店员构造包含定价与未定价子项的类目，调用按等级查询接口，验证未设置价格的子项被过滤，只返回可售商品。
    void queryClerkAllCommodityByLevelSkipsUnpricedChildren() throws Exception {
        ensureTenantContext();
        String parentId = generateId("svc-parent-");
        String pricedChildId = generateId("svc-child-priced-");
        String unpricedChildId = generateId("svc-child-unpriced-");

        PlayCommodityInfoEntity parent = createCommodity(
                parentId,
                ROOT_PARENT_ID,
                "等级过滤父类目",
                "N/A",
                SwitchState.ENABLED,
                SortPriority.SECONDARY,
                AutomaticSettlementPolicy.DISABLED);
        PlayCommodityInfoEntity pricedChild = createCommodity(
                pricedChildId,
                parent.getId(),
                "有定价子项",
                "60min",
                SwitchState.ENABLED,
                SortPriority.PRIMARY,
                AutomaticSettlementPolicy.DISABLED);
        createCommodity(
                unpricedChildId,
                parent.getId(),
                "无定价子项",
                "60min",
                SwitchState.ENABLED,
                SortPriority.SECONDARY,
                AutomaticSettlementPolicy.DISABLED);

        PlayCommodityAndLevelInfoEntity price = createPrice(pricedChild.getId(), new BigDecimal("99.90"));
        linkCommodityToClerk(parent, SwitchState.ENABLED, SortPriority.PRIMARY);

        MvcResult result = mockMvc.perform(get("/wx/commodity/custom/queryClerkAllCommodityByLevel")
                        .param("id", ApiTestDataSeeder.DEFAULT_CLERK_LEVEL_ID)
                        .header(USER_HEADER, DEFAULT_USER)
                        .header(TENANT_HEADER, DEFAULT_TENANT))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.code").value(200))
                .andReturn();

        JsonNode root = objectMapper.readTree(result.getResponse().getContentAsString());
        JsonNode data = root.get("data");
        JsonNode parentNode = findNodeById(data, parent.getId());
        assertThat(parentNode).isNotNull();

        JsonNode children = parentNode.get("child");
        assertThat(children.isArray()).isTrue();
        JsonNode pricedNode = findNodeById(children, pricedChild.getId());
        assertThat(pricedNode).isNotNull();
        assertThat(pricedNode.get("price").decimalValue()).isEqualByComparingTo(new BigDecimal("99.90"));

        JsonNode removedNode = findNodeById(children, unpricedChildId);
        assertThat(removedNode).isNull();
    }

    @Test
    // 测试用例：为店员同时配置上架与下架的类目，调用顾客查询接口，确认停用类目被隐藏，避免顾客看到不可售项目。
    void customerCommodityQueryFiltersDisabledParents() throws Exception {
        ensureTenantContext();
        String enabledParentId = generateId("svc-enabled-parent-");
        String disabledParentId = generateId("svc-disabled-parent-");

        PlayCommodityInfoEntity enabledParent = createCommodity(
                enabledParentId,
                ROOT_PARENT_ID,
                "顾客可见父类目",
                "N/A",
                SwitchState.ENABLED,
                SortPriority.PRIMARY,
                AutomaticSettlementPolicy.DISABLED);
        PlayCommodityInfoEntity enabledChild = createCommodity(
                generateId("svc-enabled-child-"),
                enabledParent.getId(),
                "顾客可见子项",
                "30min",
                SwitchState.ENABLED,
                SortPriority.PRIMARY,
                AutomaticSettlementPolicy.DISABLED);
        PlayCommodityAndLevelInfoEntity enabledPrice = createPrice(enabledChild.getId(), new BigDecimal("45.00"));

        PlayCommodityInfoEntity disabledParent = createCommodity(
                disabledParentId,
                ROOT_PARENT_ID,
                "顾客屏蔽父类目",
                "N/A",
                SwitchState.ENABLED,
                SortPriority.SECONDARY,
                AutomaticSettlementPolicy.DISABLED);
        PlayCommodityInfoEntity disabledChild = createCommodity(
                generateId("svc-disabled-child-"),
                disabledParent.getId(),
                "顾客屏蔽子项",
                "30min",
                SwitchState.ENABLED,
                SortPriority.PRIMARY,
                AutomaticSettlementPolicy.DISABLED);
        PlayCommodityAndLevelInfoEntity disabledPrice = createPrice(disabledChild.getId(), new BigDecimal("55.00"));

        linkCommodityToClerk(enabledParent, SwitchState.ENABLED, SortPriority.PRIMARY);
        linkCommodityToClerk(disabledParent, SwitchState.DISABLED, SortPriority.SECONDARY);

        MvcResult result = mockMvc.perform(get("/wx/commodity/custom/queryClerkAllCommodity")
                        .param("id", ApiTestDataSeeder.DEFAULT_CLERK_ID)
                        .header(USER_HEADER, DEFAULT_USER)
                        .header(TENANT_HEADER, DEFAULT_TENANT))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.code").value(200))
                .andReturn();

        JsonNode root = objectMapper.readTree(result.getResponse().getContentAsString());
        JsonNode data = root.get("data");

        JsonNode enabledNode = findNodeById(data, enabledParent.getId());
        assertThat(enabledNode).isNotNull();

        JsonNode disabledNode = findNodeById(data, disabledParent.getId());
        assertThat(disabledNode).isNull();
    }

    @Test
    // 测试用例：顾客随机下单选择未配置价格的商品时，接口应返回“服务项目不存在”的业务错误，验证错误商品被拒单。
    void randomOrderRejectsCommodityWithoutPricing() throws Exception {
        ensureTenantContext();
        resetCustomerBalance();
        String customerToken = wxTokenService.createWxUserToken(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID);
        customUserInfoService.updateTokenById(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID, customerToken);

        PlayCommodityInfoEntity parent = createCommodity(
                generateId("svc-parent-"),
                ROOT_PARENT_ID,
                "未定价父类目",
                "N/A",
                SwitchState.ENABLED,
                SortPriority.PRIMARY,
                AutomaticSettlementPolicy.DISABLED);
        PlayCommodityInfoEntity child = createCommodity(
                generateId("svc-child-"),
                parent.getId(),
                "未定价子项",
                "45min",
                SwitchState.ENABLED,
                SortPriority.PRIMARY,
                AutomaticSettlementPolicy.DISABLED);

        ObjectNode payload = objectMapper.createObjectNode();
        payload.put("sex", OrderConstant.Gender.FEMALE.getCode());
        payload.put("levelId", ApiTestDataSeeder.DEFAULT_CLERK_LEVEL_ID);
        payload.put("commodityId", child.getId());
        payload.put("commodityQuantity", SINGLE_COMMODITY_QUANTITY);
        payload.put("weiChatCode", "apitest-customer-wx");
        payload.put("excludeHistory", HistoryFilter.INCLUDE.getCode());
        payload.putArray("couponIds");
        payload.put("remark", "未定价拒单校验");

        mockMvc.perform(post("/wx/custom/order/random")
                        .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.toString()))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.code").value(500))
                .andExpect(jsonPath("$.message").value("服务项目不存在"));
    }

    private JsonNode findNodeById(JsonNode arrayNode, String id) {
        if (arrayNode == null || !arrayNode.isArray()) {
            return null;
        }
        for (JsonNode node : arrayNode) {
            if (node.has("id") && id.equals(node.get("id").asText())) {
                return node;
            }
        }
        return null;
    }

    private PlayCommodityInfoEntity createCommodity(
            String id,
            String parentId,
            String name,
            String duration,
            SwitchState switchState,
            SortPriority sortPriority,
            AutomaticSettlementPolicy settlementPolicy) {
        ensureTenantContext();
        PlayCommodityInfoEntity entity = new PlayCommodityInfoEntity();
        entity.setId(id);
        entity.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
        entity.setPId(parentId);
        entity.setItemType("api-test");
        entity.setItemName(name);
        entity.setServiceDuration(duration);
        entity.setAutomaticSettlementDuration(settlementPolicy.getSeconds());
        entity.setEnableStace(switchState.getCode());
        entity.setSort(sortPriority.getValue());
        commodityInfoService.save(entity);
        commodityIdsToCleanup.addFirst(entity.getId());
        return entity;
    }

    private PlayCommodityAndLevelInfoEntity createPrice(String commodityId, BigDecimal price) {
        ensureTenantContext();
        PlayCommodityAndLevelInfoEntity entity = new PlayCommodityAndLevelInfoEntity();
        entity.setId(IdUtils.getUuid());
        entity.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
        entity.setCommodityId(commodityId);
        entity.setLevelId(ApiTestDataSeeder.DEFAULT_CLERK_LEVEL_ID);
        entity.setPrice(price);
        entity.setSort(DEFAULT_PRICE_SORT);
        commodityAndLevelInfoService.save(entity);
        priceIdsToCleanup.add(entity.getId());
        return entity;
    }

    private PlayClerkCommodityEntity linkCommodityToClerk(
            PlayCommodityInfoEntity parent,
            SwitchState switchState,
            SortPriority sortPriority) {
        ensureTenantContext();
        PlayClerkCommodityEntity entity = new PlayClerkCommodityEntity();
        entity.setId(IdUtils.getUuid());
        entity.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
        entity.setClerkId(ApiTestDataSeeder.DEFAULT_CLERK_ID);
        entity.setCommodityId(parent.getId());
        entity.setCommodityName(parent.getItemName());
        entity.setEnablingState(switchState.getCode());
        entity.setSort(sortPriority.getValue());
        clerkCommodityService.save(entity);
        clerkCommodityIdsToCleanup.add(entity.getId());
        return entity;
    }

    private String generateId(String prefix) {
        return prefix + IdUtils.getUuid().replace("-", "");
    }

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