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.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

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.PlayClerkMediaAssetEntity;
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
import com.starry.admin.modules.clerk.service.IPlayClerkDataReviewInfoService;
import com.starry.admin.modules.clerk.service.IPlayClerkMediaAssetService;
import com.starry.admin.modules.clerk.service.IPlayClerkUserInfoService;
import com.starry.admin.modules.media.entity.PlayMediaEntity;
import com.starry.admin.modules.media.enums.MediaOwnerType;
import com.starry.admin.modules.media.enums.MediaStatus;
import com.starry.admin.modules.media.service.IPlayMediaService;
import com.starry.admin.modules.weichat.entity.clerk.MediaVo;
import com.starry.admin.modules.weichat.entity.clerk.PlayClerkUserInfoResultVo;
import com.starry.admin.modules.weichat.service.WxTokenService;
import com.starry.admin.utils.SecurityUtils;
import com.starry.common.constant.Constants;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
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;

/**
 * 专门验证 /wx/clerk/user/updateAlbum 在“只有删除/排序”时不会创建新的审核记录，
 * 并且会立即更新顾客端视图。
 */
class WxClerkAlbumUpdateApiTest extends AbstractApiTest {

    private final ObjectMapper objectMapper = new ObjectMapper();

    @Autowired
    private WxTokenService wxTokenService;

    @Autowired
    private IPlayClerkUserInfoService clerkUserInfoService;

    @Autowired
    private IPlayClerkDataReviewInfoService dataReviewInfoService;

    @Autowired
    private IPlayClerkMediaAssetService mediaAssetService;

    @Autowired
    private IPlayMediaService mediaService;

    @Test
    void reorderExistingApprovedMediaDoesNotCreateNewReviewAndUpdatesOrder() throws Exception {
        ensureTenantContext();
        String clerkId = ApiTestDataSeeder.DEFAULT_CLERK_ID;
        String clerkToken = wxTokenService.createWxUserToken(clerkId);
        clerkUserInfoService.updateTokenById(clerkId, clerkToken);

        PlayMediaEntity media1 = seedMedia(clerkId);
        PlayMediaEntity media2 = seedMedia(clerkId);
        PlayMediaEntity media3 = seedMedia(clerkId);

        seedApprovedAsset(clerkId, media1.getId(), 0);
        seedApprovedAsset(clerkId, media2.getId(), 1);
        seedApprovedAsset(clerkId, media3.getId(), 2);

        long reviewCountBefore = dataReviewInfoService.lambdaQuery()
                .eq(com.starry.admin.modules.clerk.module.entity.PlayClerkDataReviewInfoEntity::getClerkId, clerkId)
                .eq(com.starry.admin.modules.clerk.module.entity.PlayClerkDataReviewInfoEntity::getDataType, "2")
                .count();

        // 仅调整顺序：album 只包含已审核通过的媒资 id，不引入任何新媒资
        List<String> reordered = List.of(media3.getId(), media1.getId(), media2.getId());
        ObjectNode payload = objectMapper.createObjectNode();
        com.fasterxml.jackson.databind.node.ArrayNode albumArray = payload.putArray("album");
        reordered.forEach(albumArray::add);

        mockMvc.perform(post("/wx/clerk/user/updateAlbum")
                        .header(USER_HEADER, DEFAULT_USER)
                        .header(TENANT_HEADER, DEFAULT_TENANT)
                        .header(Constants.CLERK_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + clerkToken)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(payload.toString()))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.code").value(200));

        ensureTenantContext();
        long reviewCountAfter = dataReviewInfoService.lambdaQuery()
                .eq(com.starry.admin.modules.clerk.module.entity.PlayClerkDataReviewInfoEntity::getClerkId, clerkId)
                .eq(com.starry.admin.modules.clerk.module.entity.PlayClerkDataReviewInfoEntity::getDataType, "2")
                .count();
        assertThat(reviewCountAfter)
                .as("reordering without introducing new media should not create review records")
                .isEqualTo(reviewCountBefore);

        List<PlayClerkMediaAssetEntity> assets = mediaAssetService
                .listByState(clerkId, ClerkMediaUsage.PROFILE,
                        Collections.singletonList(ClerkMediaReviewState.APPROVED));
        PlayClerkMediaAssetEntity asset1 = assets.stream()
                .filter(a -> media1.getId().equals(a.getMediaId()))
                .max(java.util.Comparator.comparing(PlayClerkMediaAssetEntity::getCreatedTime))
                .orElse(null);
        PlayClerkMediaAssetEntity asset2 = assets.stream()
                .filter(a -> media2.getId().equals(a.getMediaId()))
                .max(java.util.Comparator.comparing(PlayClerkMediaAssetEntity::getCreatedTime))
                .orElse(null);
        PlayClerkMediaAssetEntity asset3 = assets.stream()
                .filter(a -> media3.getId().equals(a.getMediaId()))
                .max(java.util.Comparator.comparing(PlayClerkMediaAssetEntity::getCreatedTime))
                .orElse(null);

        assertThat(asset3).as("asset for media3 should exist").isNotNull();
        assertThat(asset1).as("asset for media1 should exist").isNotNull();
        assertThat(asset2).as("asset for media2 should exist").isNotNull();

        assertThat(asset3.getOrderIndex()).isEqualTo(0);
        assertThat(asset1.getOrderIndex()).isEqualTo(1);
        assertThat(asset2.getOrderIndex()).isEqualTo(2);

        PlayClerkUserInfoResultVo detail = clerkUserInfoService.buildCustomerDetail(clerkId, "");
        List<String> customerMediaIds = detail.getMediaList().stream()
                .map(MediaVo::getMediaId)
                .collect(Collectors.toList());

        int index3 = customerMediaIds.indexOf(media3.getId());
        int index1 = customerMediaIds.indexOf(media1.getId());
        int index2 = customerMediaIds.indexOf(media2.getId());

        assertThat(index3).isGreaterThanOrEqualTo(0);
        assertThat(index1).isGreaterThanOrEqualTo(0);
        assertThat(index2).isGreaterThanOrEqualTo(0);
        assertThat(index3).isLessThan(index1);
        assertThat(index1).isLessThan(index2);
    }

    @Test
    void deleteAndReorderAlbumDoesNotCreateNewReviewAndIsImmediatelyVisible() throws Exception {
        ensureTenantContext();
        String clerkId = ApiTestDataSeeder.DEFAULT_CLERK_ID;
        String clerkToken = wxTokenService.createWxUserToken(clerkId);
        clerkUserInfoService.updateTokenById(clerkId, clerkToken);

        // 预置两条已审核通过的 PROFILE 媒资
        PlayMediaEntity media1 = seedMedia(clerkId);
        PlayMediaEntity media2 = seedMedia(clerkId);

        seedApprovedAsset(clerkId, media1.getId(), 0);
        seedApprovedAsset(clerkId, media2.getId(), 1);

        // 顾客端初始视图应包含两条媒资
        PlayClerkUserInfoResultVo beforeDetail = clerkUserInfoService.buildCustomerDetail(clerkId, "");
        List<String> beforeIds = beforeDetail.getMediaList().stream()
                .map(MediaVo::getMediaId)
                .collect(Collectors.toList());
        assertThat(beforeIds).contains(media1.getId(), media2.getId());

        // 记录当前相册审核记录数量
        long reviewCountBefore = dataReviewInfoService.lambdaQuery()
                .eq(com.starry.admin.modules.clerk.module.entity.PlayClerkDataReviewInfoEntity::getClerkId, clerkId)
                .eq(com.starry.admin.modules.clerk.module.entity.PlayClerkDataReviewInfoEntity::getDataType, "2")
                .count();

        // 现在通过 updateAlbum 只保留 media2，相当于“删除 media1 + 不引入新媒资”
        ObjectNode payload = objectMapper.createObjectNode();
        payload.putArray("album").add(media2.getId());

        mockMvc.perform(post("/wx/clerk/user/updateAlbum")
                        .header(USER_HEADER, DEFAULT_USER)
                        .header(TENANT_HEADER, DEFAULT_TENANT)
                        .header(Constants.CLERK_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + clerkToken)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(payload.toString()))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.code").value(200));

        ensureTenantContext();
        long reviewCountAfter = dataReviewInfoService.lambdaQuery()
                .eq(com.starry.admin.modules.clerk.module.entity.PlayClerkDataReviewInfoEntity::getClerkId, clerkId)
                .eq(com.starry.admin.modules.clerk.module.entity.PlayClerkDataReviewInfoEntity::getDataType, "2")
                .count();
        assertThat(reviewCountAfter)
                .as("Deleting/reordering without new media should not create new review records")
                .isEqualTo(reviewCountBefore);

        // 资产表中仅剩 media2 且仍为 APPROVED 状态
        List<PlayClerkMediaAssetEntity> assets = mediaAssetService
                .listByState(clerkId, ClerkMediaUsage.PROFILE,
                        Collections.singletonList(ClerkMediaReviewState.APPROVED));
        List<String> assetMediaIds = assets.stream()
                .map(PlayClerkMediaAssetEntity::getMediaId)
                .collect(Collectors.toList());
        assertThat(assetMediaIds)
                .contains(media2.getId())
                .doesNotContain(media1.getId());

        // 顾客端视图应立即反映删除结果
        PlayClerkUserInfoResultVo afterDetail = clerkUserInfoService.buildCustomerDetail(clerkId, "");
        List<String> afterIds = afterDetail.getMediaList().stream()
                .map(MediaVo::getMediaId)
                .collect(Collectors.toList());
        assertThat(afterIds)
                .contains(media2.getId())
                .doesNotContain(media1.getId());
    }

    @Test
    void updateAlbumRejectsEmptyAlbumPayload() throws Exception {
        ensureTenantContext();
        String clerkId = ApiTestDataSeeder.DEFAULT_CLERK_ID;
        String clerkToken = wxTokenService.createWxUserToken(clerkId);
        clerkUserInfoService.updateTokenById(clerkId, clerkToken);

        ObjectNode payload = objectMapper.createObjectNode();
        payload.putArray("album"); // 空数组

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

        String body = result.getResponse().getContentAsString();
        com.fasterxml.jackson.databind.JsonNode root = new ObjectMapper().readTree(body);
        assertThat(root.path("code").asInt())
                .as("empty album should be rejected, response=%s", body)
                .isEqualTo(500);
        assertThat(root.path("message").asText())
                .as("error message for empty album should be present, response=%s", body)
                .isNotBlank();
    }

    @Test
    void updateAlbumAllowsMixedLegacyUrlsAndNewMediaIdsForReview() throws Exception {
        ensureTenantContext();
        String clerkId = ApiTestDataSeeder.DEFAULT_CLERK_ID;
        String clerkToken = wxTokenService.createWxUserToken(clerkId);
        clerkUserInfoService.updateTokenById(clerkId, clerkToken);

        // 预置一条已就绪的媒资，模拟“新上传的视频/图片”
        PlayMediaEntity media = seedMedia(clerkId);

        // 模拟老相册中的 URL（未媒资化的历史数据）
        String legacyUrl1 = "https://oss.apitest/legacy-1.png";
        String legacyUrl2 = "https://oss.apitest/legacy-2.png";

        long reviewCountBefore = dataReviewInfoService.lambdaQuery()
                .eq(com.starry.admin.modules.clerk.module.entity.PlayClerkDataReviewInfoEntity::getClerkId, clerkId)
                .eq(com.starry.admin.modules.clerk.module.entity.PlayClerkDataReviewInfoEntity::getDataType, "2")
                .count();

        ObjectNode payload = objectMapper.createObjectNode();
        com.fasterxml.jackson.databind.node.ArrayNode albumArray = payload.putArray("album");
        albumArray.add(legacyUrl1);
        albumArray.add(legacyUrl2);
        albumArray.add(media.getId());

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

        String body = result.getResponse().getContentAsString();
        com.fasterxml.jackson.databind.JsonNode root = new ObjectMapper().readTree(body);
        assertThat(root.path("code").asInt())
                .as("mixed legacy URLs and new media ids should be accepted for review, response=%s", body)
                .isEqualTo(200);

        ensureTenantContext();
        long reviewCountAfter = dataReviewInfoService.lambdaQuery()
                .eq(com.starry.admin.modules.clerk.module.entity.PlayClerkDataReviewInfoEntity::getClerkId, clerkId)
                .eq(com.starry.admin.modules.clerk.module.entity.PlayClerkDataReviewInfoEntity::getDataType, "2")
                .count();
        assertThat(reviewCountAfter)
                .as("mixed legacy URLs and new media ids should create exactly one new review record")
                .isEqualTo(reviewCountBefore + 1);
    }

    private PlayMediaEntity seedMedia(String clerkId) {
        String mediaId = "media-" + java.util.UUID.randomUUID().toString().substring(0, 16);
        PlayMediaEntity entity = new PlayMediaEntity();
        entity.setId(mediaId);
        entity.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
        entity.setOwnerType(MediaOwnerType.CLERK);
        entity.setOwnerId(clerkId);
        entity.setKind("image");
        entity.setStatus(MediaStatus.READY.getCode());
        entity.setUrl("https://oss.apitest/" + mediaId + ".png");
        mediaService.save(entity);
        return entity;
    }

    private void seedApprovedAsset(String clerkId, String mediaId, int orderIndex) {
        PlayClerkMediaAssetEntity asset = new PlayClerkMediaAssetEntity();
        asset.setId("asset-" + mediaId);
        asset.setTenantId(ApiTestDataSeeder.DEFAULT_TENANT_ID);
        asset.setClerkId(clerkId);
        asset.setMediaId(mediaId);
        asset.setUsage(ClerkMediaUsage.PROFILE.getCode());
        asset.setOrderIndex(orderIndex);
        asset.setReviewState(ClerkMediaReviewState.APPROVED.getCode());
        asset.setDeleted(false);
        mediaAssetService.save(asset);
    }

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