package com.starry.admin.api;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart;
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.common.oss.service.IOssFileService;
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.service.IPlayMediaService;
import com.starry.admin.modules.weichat.entity.PlayClerkUserLoginResponseVo;
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 com.starry.common.enums.ClerkReviewState;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
import javax.imageio.ImageIO;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.test.web.servlet.MvcResult;

/**
 * 覆盖店员端媒资上传与相册审核的关键业务路径（图片/视频 + 删除后不复活）。
 */
class WxClerkMediaControllerApiTest extends AbstractApiTest {

    private final ObjectMapper objectMapper = new ObjectMapper();

    @Autowired
    private WxTokenService wxTokenService;

    @Autowired
    private IPlayClerkUserInfoService clerkUserInfoService;

    @Autowired
    private IPlayMediaService mediaService;

    @Autowired
    private IPlayClerkDataReviewInfoService dataReviewInfoService;

    @Autowired
    private IPlayClerkMediaAssetService mediaAssetService;

    @MockBean
    private IOssFileService ossFileService;

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

        String ossUrl = "https://oss.mock/apitest/avatar.png";
        when(ossFileService.upload(any(), eq(ApiTestDataSeeder.DEFAULT_TENANT_ID), anyString()))
                .thenReturn(ossUrl);

        BufferedImage image = new BufferedImage(2, 2, BufferedImage.TYPE_INT_RGB);
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ImageIO.write(image, "png", baos);
        MockMultipartFile file = new MockMultipartFile(
                "file",
                "avatar.png",
                "image/png",
                baos.toByteArray());

        MvcResult result = mockMvc.perform(multipart("/wx/clerk/media/upload")
                        .file(file)
                        .param("usage", "profile")
                        .header(USER_HEADER, DEFAULT_USER)
                        .header(TENANT_HEADER, DEFAULT_TENANT)
                        .header(Constants.CLERK_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + clerkToken)
                        .contentType(MediaType.MULTIPART_FORM_DATA))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.code").value(200))
                .andExpect(jsonPath("$.data.url").value(ossUrl))
                .andExpect(jsonPath("$.data.kind").value("image"))
                .andExpect(jsonPath("$.data.usage").value("profile"))
                .andReturn();

        String body = result.getResponse().getContentAsString();
        JsonNode root = objectMapper.readTree(body);
        JsonNode data = root.path("data");
        String mediaId = data.path("mediaId").asText(null);
        assertThat(mediaId).isNotBlank();

        ensureTenantContext();
        PlayMediaEntity persisted = mediaService.getById(mediaId);
        assertThat(persisted).isNotNull();
        assertThat(persisted.getOwnerType()).isEqualTo(MediaOwnerType.CLERK);
        assertThat(persisted.getOwnerId()).isEqualTo(ApiTestDataSeeder.DEFAULT_CLERK_ID);
        assertThat(persisted.getUrl()).isEqualTo(ossUrl);
    }

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

        String videoUrl = "https://oss.mock/apitest/video.mp4";
        String coverUrl = "https://oss.mock/apitest/video-cover.jpg";
        when(ossFileService.upload(any(), eq(ApiTestDataSeeder.DEFAULT_TENANT_ID), anyString()))
                .thenReturn(videoUrl, coverUrl);

        byte[] videoBytes = Files.readAllBytes(Paths.get("/Volumes/main/code/yunpei/sample_data/sample_video.mp4"));
        MockMultipartFile file = new MockMultipartFile(
                "file",
                "sample_video.mp4",
                "video/mp4",
                videoBytes);

        MvcResult result = mockMvc.perform(multipart("/wx/clerk/media/upload")
                        .file(file)
                        .param("usage", "profile")
                        .header(USER_HEADER, DEFAULT_USER)
                        .header(TENANT_HEADER, DEFAULT_TENANT)
                        .header(Constants.CLERK_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + clerkToken)
                        .contentType(MediaType.MULTIPART_FORM_DATA))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.code").value(200))
                .andExpect(jsonPath("$.data.kind").value("video"))
                .andExpect(jsonPath("$.data.usage").value("profile"))
                .andReturn();

        String body = result.getResponse().getContentAsString();
        JsonNode root = objectMapper.readTree(body);
        JsonNode data = root.path("data");
        String mediaId = data.path("mediaId").asText(null);
        assertThat(mediaId).isNotBlank();

        ensureTenantContext();
        PlayMediaEntity persisted = mediaService.getById(mediaId);
        assertThat(persisted).isNotNull();
        assertThat(persisted.getKind()).isEqualTo("video");
        assertThat(persisted.getUrl()).isEqualTo(videoUrl);
        assertThat(persisted.getCoverUrl()).isEqualTo(coverUrl);
    }

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

        when(ossFileService.upload(any(), eq(ApiTestDataSeeder.DEFAULT_TENANT_ID), anyString()))
                .thenReturn(
                        "https://oss.mock/apitest/album-a.png",
                        "https://oss.mock/apitest/album-b.png",
                        "https://oss.mock/apitest/album-c.png",
                        "https://oss.mock/apitest/album-d.png");

        String mediaIdA = extractMediaIdFromUpload(buildTinyPng("album-a.png"), clerkToken);
        String mediaIdB = extractMediaIdFromUpload(buildTinyPng("album-b.png"), clerkToken);
        String mediaIdC = extractMediaIdFromUpload(buildTinyPng("album-c.png"), clerkToken);
        String mediaIdD = extractMediaIdFromUpload(buildTinyPng("album-d.png"), clerkToken);

        List<String> allMediaIds = List.of(mediaIdA, mediaIdB, mediaIdC, mediaIdD);

        submitAlbumUpdate(allMediaIds, clerkToken);
        ensureTenantContext();
        approveLatestAlbumReview();

        List<PlayClerkMediaAssetEntity> assetsAfterFirstApprove = mediaAssetService
                .listActiveByUsage(ApiTestDataSeeder.DEFAULT_CLERK_ID, ClerkMediaUsage.PROFILE);
        assertThat(assetsAfterFirstApprove)
                .extracting(PlayClerkMediaAssetEntity::getMediaId)
                .containsAll(allMediaIds);
        List<String> reviewStatesForNewMedia = assetsAfterFirstApprove.stream()
                .filter(asset -> allMediaIds.contains(asset.getMediaId()))
                .map(PlayClerkMediaAssetEntity::getReviewState)
                .collect(Collectors.toList());
        assertThat(reviewStatesForNewMedia).containsOnly(ClerkMediaReviewState.APPROVED.getCode());

        List<String> clerkVisibleMediaIdsAfterFirst = assetsAfterFirstApprove.stream()
                .filter(asset -> !ClerkMediaReviewState.REJECTED.getCode().equals(asset.getReviewState()))
                .map(PlayClerkMediaAssetEntity::getMediaId)
                .collect(Collectors.toList());
        assertThat(clerkVisibleMediaIdsAfterFirst).containsAll(allMediaIds);

        PlayClerkUserInfoResultVo customerDetailAfterFirst =
                clerkUserInfoService.buildCustomerDetail(ApiTestDataSeeder.DEFAULT_CLERK_ID, "");
        List<String> customerMediaIdsAfterFirst = customerDetailAfterFirst.getMediaList().stream()
                .map(MediaVo::getMediaId)
                .collect(Collectors.toList());
        assertThat(customerMediaIdsAfterFirst).containsAll(allMediaIds);

        List<String> keptMedia = List.of(mediaIdA, mediaIdC);

        // 第二次提交：只删除与重新排序，不再生成新的资料审核记录，应直接生效
        long reviewCountBeforeSecond = dataReviewInfoService.lambdaQuery()
                .eq(com.starry.admin.modules.clerk.module.entity.PlayClerkDataReviewInfoEntity::getClerkId,
                        ApiTestDataSeeder.DEFAULT_CLERK_ID)
                .eq(com.starry.admin.modules.clerk.module.entity.PlayClerkDataReviewInfoEntity::getDataType, "2")
                .count();
        submitAlbumUpdate(keptMedia, clerkToken);
        ensureTenantContext();
        long reviewCountAfterSecond = dataReviewInfoService.lambdaQuery()
                .eq(com.starry.admin.modules.clerk.module.entity.PlayClerkDataReviewInfoEntity::getClerkId,
                        ApiTestDataSeeder.DEFAULT_CLERK_ID)
                .eq(com.starry.admin.modules.clerk.module.entity.PlayClerkDataReviewInfoEntity::getDataType, "2")
                .count();
        assertThat(reviewCountAfterSecond)
                .as("deleting/reordering album should not create another review record")
                .isEqualTo(reviewCountBeforeSecond);

        List<PlayClerkMediaAssetEntity> assetsAfterSecondApprove = mediaAssetService
                .listActiveByUsage(ApiTestDataSeeder.DEFAULT_CLERK_ID, ClerkMediaUsage.PROFILE);

        PlayClerkMediaAssetEntity assetA = assetsAfterSecondApprove.stream()
                .filter(a -> mediaIdA.equals(a.getMediaId()))
                .max(Comparator.comparing(PlayClerkMediaAssetEntity::getCreatedTime))
                .orElse(null);
        PlayClerkMediaAssetEntity assetC = assetsAfterSecondApprove.stream()
                .filter(a -> mediaIdC.equals(a.getMediaId()))
                .max(Comparator.comparing(PlayClerkMediaAssetEntity::getCreatedTime))
                .orElse(null);
        assertThat(assetA.getReviewState()).isEqualTo(ClerkMediaReviewState.APPROVED.getCode());
        assertThat(assetA.getOrderIndex()).isEqualTo(0);
        assertThat(assetC.getReviewState()).isEqualTo(ClerkMediaReviewState.APPROVED.getCode());
        assertThat(assetC.getOrderIndex()).isEqualTo(1);

        PlayClerkUserInfoResultVo customerDetailAfterSecond =
                clerkUserInfoService.buildCustomerDetail(ApiTestDataSeeder.DEFAULT_CLERK_ID, "");
        List<String> customerMediaIdsAfterSecond = customerDetailAfterSecond.getMediaList().stream()
                .map(MediaVo::getMediaId)
                .collect(Collectors.toList());
        assertThat(customerMediaIdsAfterSecond)
                .contains(mediaIdA, mediaIdC)
                .doesNotContain(mediaIdB, mediaIdD);

        List<String> clerkVisibleMediaIdsAfterSecond = assetsAfterSecondApprove.stream()
                .filter(asset -> !ClerkMediaReviewState.REJECTED.getCode().equals(asset.getReviewState()))
                .map(PlayClerkMediaAssetEntity::getMediaId)
                .collect(Collectors.toList());
        assertThat(clerkVisibleMediaIdsAfterSecond)
                .contains(mediaIdA, mediaIdC)
                .doesNotContain(mediaIdB, mediaIdD);
    }

    private MockMultipartFile buildTinyPng(String filename) throws Exception {
        BufferedImage image = new BufferedImage(2, 2, BufferedImage.TYPE_INT_RGB);
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ImageIO.write(image, "png", baos);
        return new MockMultipartFile("file", filename, "image/png", baos.toByteArray());
    }

    private String extractMediaIdFromUpload(MockMultipartFile file, String clerkToken) throws Exception {
        MvcResult result = mockMvc.perform(multipart("/wx/clerk/media/upload")
                        .file(file)
                        .param("usage", "profile")
                        .header(USER_HEADER, DEFAULT_USER)
                        .header(TENANT_HEADER, DEFAULT_TENANT)
                        .header(Constants.CLERK_USER_LOGIN_TOKEN, Constants.TOKEN_PREFIX + clerkToken)
                        .contentType(MediaType.MULTIPART_FORM_DATA))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.code").value(200))
                .andReturn();
        String body = result.getResponse().getContentAsString();
        JsonNode root = objectMapper.readTree(body);
        return root.path("data").path("mediaId").asText();
    }

    private void submitAlbumUpdate(List<String> mediaIds, String clerkToken) throws Exception {
        ObjectNode payload = objectMapper.createObjectNode();
        com.fasterxml.jackson.databind.node.ArrayNode albumArray = payload.putArray("album");
        mediaIds.forEach(albumArray::add);

        mockMvc.perform(org.springframework.test.web.servlet.request.MockMvcRequestBuilders.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));
    }

    private void approveLatestAlbumReview() {
        List<com.starry.admin.modules.clerk.module.entity.PlayClerkDataReviewInfoEntity> reviews = dataReviewInfoService
                .lambdaQuery()
                .eq(com.starry.admin.modules.clerk.module.entity.PlayClerkDataReviewInfoEntity::getClerkId,
                        ApiTestDataSeeder.DEFAULT_CLERK_ID)
                .eq(com.starry.admin.modules.clerk.module.entity.PlayClerkDataReviewInfoEntity::getDataType, "2")
                .orderByDesc(com.starry.admin.modules.clerk.module.entity.PlayClerkDataReviewInfoEntity::getAddTime)
                .list();
        assertThat(reviews).isNotEmpty();
        com.starry.admin.modules.clerk.module.entity.PlayClerkDataReviewInfoEntity latest = reviews.get(0);

        com.starry.admin.modules.clerk.module.vo.PlayClerkDataReviewStateEditVo vo =
                new com.starry.admin.modules.clerk.module.vo.PlayClerkDataReviewStateEditVo();
        vo.setId(latest.getId());
        vo.setReviewState(ClerkReviewState.APPROVED);
        vo.setReviewCon("ok");
        vo.setReviewTime(LocalDateTime.now());
        dataReviewInfoService.updateDataReviewState(vo);
    }

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