package com.starry.admin.api;

import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
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.clerk.enums.ClerkMediaReviewState;
import com.starry.admin.modules.clerk.enums.ClerkMediaUsage;
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
import com.starry.admin.modules.clerk.service.IPlayClerkUserInfoService;
import com.starry.admin.modules.media.enums.MediaStatus;
import com.starry.admin.modules.weichat.entity.clerk.MediaVo;
import com.starry.admin.modules.weichat.entity.clerk.PlayClerkUserInfoResultVo;
import com.starry.admin.utils.SecurityUtils;
import com.starry.common.utils.IdUtils;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
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;

/**
 * 覆盖微信顾客端店员列表 / 详情在仅存在历史相册数据时的兼容行为。
 *
 * <p>重点校验：
 * <ul>
 *     <li>旧字段 {@code album} 在列表接口中仍然可见；</li>
 *     <li>同时会被折叠进 {@code mediaList}，且不会破坏后续媒资结构（id/url/usage/status/reviewState）；</li>
 *     <li>详情接口中 {@link PlayClerkUserInfoResultVo#mediaList} 也包含这些历史相册。</li>
 * </ul>
 * </p>
 */
class WxClerkUserBackwardCompatApiTest extends AbstractApiTest {

    private final ObjectMapper objectMapper = new ObjectMapper();

    @Autowired
    private IPlayClerkUserInfoService clerkUserInfoService;

    private final List<String> clerkIdsToCleanup = new ArrayList<>();

    @AfterEach
    void tearDown() {
        ensureTenantContext();
        if (!clerkIdsToCleanup.isEmpty()) {
            clerkUserInfoService.removeByIds(clerkIdsToCleanup);
            clerkIdsToCleanup.clear();
        }
    }

    /**
     * 场景：只有旧相册(album) 而没有 play_media / play_clerk_media_asset 记录时，
     * 微信顾客端列表接口仍然返回可用的 album 和 mediaList。
     */
    @Test
    void customerListIncludesLegacyAlbumAsMediaList() throws Exception {
        ensureTenantContext();
        String marker = "legacy-album-" + IdUtils.getUuid().substring(0, 8);
        List<String> legacyAlbum = List.of(
                "https://example.com/apitest/legacy/" + marker + "-a.png",
                "https://example.com/apitest/legacy/" + marker + "-b.png");
        String clerkId = createLegacyAlbumClerk(marker, legacyAlbum);

        ObjectNode payload = objectMapper.createObjectNode();
        payload.put("pageNum", 1);
        payload.put("pageSize", 20);
        payload.put("nickname", marker);
        payload.put("typeId", "");
        payload.put("levelId", "");
        payload.put("sex", "");
        payload.put("province", "");

        MvcResult result = mockMvc.perform(post("/wx/clerk/user/queryByPage")
                        .header(TENANT_HEADER, DEFAULT_TENANT)
                        .header(USER_HEADER, DEFAULT_USER)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(payload.toString()))
                .andExpect(status().isOk())
                .andReturn();

        String body = result.getResponse().getContentAsString();
        JsonNode root = objectMapper.readTree(body);
        assertThat(root.path("code").asInt()).as("response=%s", body).isEqualTo(200);

        JsonNode records = root.path("data").path("records");
        assertThat(records.isArray()).as("records should be array, response=%s", body).isTrue();

        JsonNode target = null;
        for (JsonNode record : records) {
            if (clerkId.equals(record.path("id").asText())) {
                target = record;
                break;
            }
        }
        assertThat(target)
                .withFailMessage("列表中未找到目标店员 %s, response=%s", clerkId, body)
                .isNotNull();

        // album 字段仍然保留原有的历史值
        JsonNode albumNode = target.path("album");
        assertThat(albumNode.isArray()).isTrue();
        Set<String> albumUrls = new HashSet<>();
        albumNode.forEach(node -> albumUrls.add(node.asText()));
        assertThat(albumUrls).containsExactlyInAnyOrderElementsOf(legacyAlbum);

        // mediaList 中应当包含折叠后的 Legacy 媒资，并为其补齐 usage/status/reviewState 等字段
        JsonNode mediaListNode = target.path("mediaList");
        assertThat(mediaListNode.isArray()).isTrue();
        assertThat(mediaListNode.size()).isEqualTo(legacyAlbum.size());

        Set<String> mediaUrls = new HashSet<>();
        for (JsonNode media : mediaListNode) {
            String url = media.path("url").asText();
            mediaUrls.add(url);
            if (!legacyAlbum.contains(url)) {
                continue;
            }
            // 对于从 album 折叠出来的条目，我们要求：
            // id 与 url 相同；usage/status/reviewState 均为兼容值。
            assertThat(media.path("id").asText()).isEqualTo(url);
            assertThat(media.path("usage").asText()).isEqualTo(ClerkMediaUsage.PROFILE.getCode());
            assertThat(media.path("status").asText()).isEqualTo(MediaStatus.READY.getCode());
            assertThat(media.path("reviewState").asText())
                    .isEqualTo(ClerkMediaReviewState.APPROVED.getCode());
        }
        assertThat(mediaUrls).containsAll(legacyAlbum);
    }

    /**
     * 场景：只有旧相册(album) 数据时，顾客端店员详情接口 buildCustomerDetail 也会将其折叠进 mediaList，
     * 同时保留 album 字段不变。
     */
    @Test
    void customerDetailMergesLegacyAlbumIntoMediaList() {
        ensureTenantContext();
        String marker = "legacy-detail-" + IdUtils.getUuid().substring(0, 8);
        List<String> legacyAlbum = List.of(
                "https://example.com/apitest/legacy/" + marker + "-a.png",
                "https://example.com/apitest/legacy/" + marker + "-b.png");
        String clerkId = createLegacyAlbumClerk(marker, legacyAlbum);

        PlayClerkUserInfoResultVo detail = clerkUserInfoService.buildCustomerDetail(clerkId, "");

        assertThat(detail.getAlbum()).containsExactlyElementsOf(legacyAlbum);
        List<MediaVo> mediaList = detail.getMediaList();
        assertThat(mediaList).isNotNull();
        assertThat(mediaList).hasSize(legacyAlbum.size());

        Set<String> mediaUrls = new HashSet<>();
        for (MediaVo media : mediaList) {
            String url = media.getUrl();
            mediaUrls.add(url);
            if (!legacyAlbum.contains(url)) {
                continue;
            }
            assertThat(media.getId()).isEqualTo(url);
            assertThat(media.getUsage()).isEqualTo(ClerkMediaUsage.PROFILE.getCode());
            assertThat(media.getStatus()).isEqualTo(MediaStatus.READY.getCode());
            assertThat(media.getReviewState()).isEqualTo(ClerkMediaReviewState.APPROVED.getCode());
        }
        assertThat(mediaUrls).containsAll(legacyAlbum);
    }

    private String createLegacyAlbumClerk(String marker, List<String> album) {
        String clerkId = IdUtils.getUuid();
        PlayClerkUserInfoEntity clerk = new PlayClerkUserInfoEntity();
        clerk.setId(clerkId);
        clerk.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
        clerk.setNickname("兼容测试店员-" + marker);
        clerk.setLevelId(ApiTestDataSeeder.DEFAULT_CLERK_LEVEL_ID);
        clerk.setClerkState("1");
        clerk.setOnboardingState("1");
        clerk.setListingState("1");
        clerk.setDisplayState("1");
        clerk.setRecommendationState("0");
        clerk.setPinToTopState("0");
        clerk.setRandomOrderState("1");
        clerk.setFixingLevel("0");
        clerk.setOnlineState("1");
        clerk.setPhone("138" + clerkId.substring(0, 8));
        clerk.setOpenid("openid-legacy-" + marker);
        clerk.setWeiChatCode("wx-code-legacy-" + marker);
        clerk.setTypeId("api-type-legacy");
        clerk.setProvince("API省");
        clerk.setCity("API市");
        clerk.setEntryTime(LocalDateTime.now());
        clerk.setAddTime(LocalDateTime.now());
        clerk.setAlbum(new ArrayList<>(album));
        clerkUserInfoService.save(clerk);
        clerkIdsToCleanup.add(clerkId);
        return clerkId;
    }

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