package com.liquidnet.service.goblin.test;

import com.liquidnet.commons.lang.util.CurrentUtil;
import com.liquidnet.service.base.PagedResult;
import com.liquidnet.service.base.ResponseDto;
import com.liquidnet.service.goblin.constant.GoblinArtistConst;
import com.liquidnet.service.goblin.dto.manage.GoblinStoreMgtArtistApplyRevokeParams;
import com.liquidnet.service.goblin.dto.manage.GoblinStoreMgtArtistApplySubmitParams;
import com.liquidnet.service.goblin.dto.manage.GoblinStoreMgtArtistTopicCreateParams;
import com.liquidnet.service.goblin.dto.manage.GoblinStoreMgtArtistTopicDeleteParams;
import com.liquidnet.service.goblin.dto.manage.GoblinStoreMgtArtistTopicGoodsItemParams;
import com.liquidnet.service.goblin.dto.manage.GoblinStoreMgtArtistTopicStatusParams;
import com.liquidnet.service.goblin.dto.manage.GoblinStoreMgtArtistTopicUpdateParams;
import com.liquidnet.service.goblin.dto.manage.vo.GoblinStoreMgtArtistApplyListItemVo;
import com.liquidnet.service.goblin.dto.manage.vo.GoblinStoreMgtArtistAuthListVo;
import com.liquidnet.service.goblin.dto.manage.vo.GoblinStoreMgtArtistSearchItemVo;
import com.liquidnet.service.goblin.dto.manage.vo.GoblinStoreMgtArtistTopicDetailVo;
import com.liquidnet.service.goblin.dto.manage.vo.GoblinStoreMgtArtistTopicGoodsSearchItemVo;
import com.liquidnet.service.goblin.dto.manage.vo.GoblinStoreMgtArtistTopicPageVo;
import com.liquidnet.service.goblin.service.manage.IGoblinStoreMgtArtistService;
import com.liquidnet.service.goblin.util.GoblinRedisUtils;
import com.liquidnet.service.goblin.dto.vo.GoblinStoreInfoVo;
import lombok.extern.slf4j.Slf4j;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.util.ArrayList;
import java.util.List;

/**
 * B 端艺人专题接口 集成测试
 * <p>
 * 覆盖 12 个接口的完整调用链路，验证 MongoDB 读写、Redis 缓存、MQ 异步 SQL 队列。
 * </p>
 *
 * <h3>前置条件</h3>
 * <ul>
 *   <li>需连接 dev 环境 MongoDB + Redis</li>
 *   <li>CurrentUtil 通过 MockHttpServletRequest 注入测试 UID</li>
 * </ul>
 *
 * <h3>运行方式</h3>
 * <pre>
 *   IDE VM 参数: -Dtest.uid=你的店铺UID
 *   然后 run 整个类或单独 @Test 方法
 * </pre>
 *
 * <h3>数据源验证矩阵</h3>
 * <table>
 *   <tr><th>测试场景</th><th>读</th><th>写</th></tr>
 *   <tr><td>搜索艺人</td><td>MySQL (kylin_artist)</td><td>—</td></tr>
 *   <tr><td>提交/撤销申请</td><td>MongoDB + Kylin</td><td>MongoDB 同步 + MySQL MQ 异步</td></tr>
 *   <tr><td>申请/授权列表</td><td>MongoDB（不缓存）</td><td>—</td></tr>
 *   <tr><td>专题 CRUD</td><td>MongoDB + Redis 缓存</td><td>MongoDB 同步 + Redis 更新 + MySQL MQ 异步</td></tr>
 *   <tr><td>搜索商品</td><td>MongoDB (GoblinGoodsInfoVo)</td><td>—</td></tr>
 * </table>
 *
 * @see IGoblinStoreMgtArtistService
 * @see com.liquidnet.service.goblin.service.impl.manage.GoblinStoreMgtArtistServiceImpl
 */
@Slf4j
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class GoblinStoreMgtArtistServiceImplTest {

    @Autowired
    private IGoblinStoreMgtArtistService artistService;

    @Autowired
    private GoblinRedisUtils redisUtils;

    /** 从 Redis 加载的测试店铺 ID，所有写操作作用于此店铺 */
    private String storeId;

    /**
     * 初始化测试上下文：通过 MockHttpServletRequest 注入测试 UID，
     * 使后续 service 调用中的 {@code CurrentUtil.getCurrentUid()} 能正确获取。
     *
     * @throws AssertionError 如果测试 UID 未在 Redis 中关联店铺
     */
    @Before
    public void setUp() {
        String testUid = "937724050260131847782985";

        MockHttpServletRequest request = new MockHttpServletRequest();
        request.setAttribute(CurrentUtil.TOKEN_SUB, testUid);
        RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request));

        GoblinStoreInfoVo storeInfoVo = redisUtils.getStoreInfoVoByUid(testUid);
        Assert.assertNotNull("请设置有效测试 UID（-Dtest.uid=xxx），并确保该 UID 已在 Redis 中关联店铺", storeInfoVo);
        this.storeId = storeInfoVo.getStoreId();
        log.info("测试店铺 storeId={}", storeId);
    }

    // ======================================== 4.1 艺人搜索（读 MySQL kylin_artist） ========================================

    /**
     * 搜索可申请艺人 — 无关键词，验证返回分页列表。
     *
     * <pre>
     * 接口: GET /apply/searchArtist?page=1&size=10
     * 验证: ResponseDto.isSuccess() == true, data.list 不为 null
     * </pre>
     */
    @Test
    public void searchArtist_shouldReturnList() {
        ResponseDto<PagedResult<GoblinStoreMgtArtistSearchItemVo>> resp = artistService.searchArtist(null, 1, 10);
        Assert.assertTrue("searchArtist: " + resp.getMessage(), resp.isSuccess());
        Assert.assertNotNull(resp.getData());
        log.info("searchArtist total={} listSize={}",
                resp.getData().getTotal(),
                resp.getData().getList() == null ? 0 : resp.getData().getList().size());
    }

    /**
     * 搜索可申请艺人 — 带关键词，验证返回结果匹配。
     *
     * <pre>
     * 接口: GET /apply/searchArtist?keyword=测试&page=1&size=10
     * 验证: ResponseDto.isSuccess() == true
     * </pre>
     */
    @Test
    public void searchArtist_byKeyword() {
        ResponseDto<PagedResult<GoblinStoreMgtArtistSearchItemVo>> resp = artistService.searchArtist("测试", 1, 10);
        Assert.assertTrue("searchArtist keyword: " + resp.getMessage(), resp.isSuccess());
        log.info("searchArtist keyword=测试 total={}", resp.getData().getTotal());
    }

    // ======================================== 4.1 申请 + 撤销（MongoDB 同步写 + MySQL MQ 异步） ========================================

    /**
     * 提交艺人关联申请 → 撤销申请 的完整链路。
     *
     * <pre>
     * 提交: POST /apply/submit → MongoDB insert GoblinArtistAuthApplyVo → MQ SQL insert goblin_artist_auth_apply
     * 撤销: POST /apply/revoke → MongoDB update status=3 → MQ SQL update goblin_artist_auth_apply
     *
     * 注意:
     *   该艺人已有待审核申请时，submit 会返回 failure（符合业务规则），此时跳过撤销验证。
     *   撤销仅对 status=0(待审核) 的申请有效。
     * </pre>
     */
    @Test
    public void submitApply_and_revoke() {
        ResponseDto<PagedResult<GoblinStoreMgtArtistSearchItemVo>> searchResp = artistService.searchArtist(null, 1, 1);
        Assert.assertTrue("需有可用艺人: " + searchResp.getMessage(), searchResp.isSuccess());
        if (searchResp.getData().getList().isEmpty()) {
            log.warn("skip: 无可用艺人");
            return;
        }
        String artistId = searchResp.getData().getList().get(0).getArtistId();

        GoblinStoreMgtArtistApplySubmitParams submitParams = new GoblinStoreMgtArtistApplySubmitParams();
        submitParams.setArtistId(artistId);
        submitParams.setAttachmentUrls(new ArrayList<>());
        submitParams.getAttachmentUrls().add("https://example.com/test.jpg");

        ResponseDto<String> submitResp = artistService.submitApply(submitParams);
        log.info("submitApply: success={} msg={} data={}",
                submitResp.isSuccess(), submitResp.getMessage(), submitResp.getData());
        if (submitResp.isSuccess()) {
            Assert.assertNotNull(submitResp.getData());

            GoblinStoreMgtArtistApplyRevokeParams revokeParams = new GoblinStoreMgtArtistApplyRevokeParams();
            revokeParams.setApplyId(submitResp.getData());
            ResponseDto<Void> revokeResp = artistService.revokeApply(revokeParams);
            Assert.assertTrue("revoke: " + revokeResp.getMessage(), revokeResp.isSuccess());
        }
    }

    // ======================================== 4.1 申请列表（读 MongoDB，不缓存） ========================================

    /**
     * 本店申请列表 — 全部状态。
     *
     * <pre>
     * 接口: GET /apply/list?page=1&size=10
     * 数据源: MongoDB → GoblinArtistAuthApplyVo collection（列表不缓存 Redis）
     * 验证: 分页正常，列表不为 null
     * </pre>
     */
    @Test
    public void applyList_shouldReturnPage() {
        ResponseDto<PagedResult<GoblinStoreMgtArtistApplyListItemVo>> resp = artistService.applyList(null, 1, 10);
        Assert.assertTrue("applyList: " + resp.getMessage(), resp.isSuccess());
        Assert.assertNotNull(resp.getData());
        log.info("applyList total={}", resp.getData().getTotal());
    }

    /**
     * 本店申请列表 — 按状态（待审核）筛选。
     *
     * <pre>
     * 接口: GET /apply/list?status=0&page=1&size=10
     * 验证: 列表中的申请 status 均 = 0
     * </pre>
     */
    @Test
    public void applyList_filterByStatus() {
        ResponseDto<PagedResult<GoblinStoreMgtArtistApplyListItemVo>> resp = artistService.applyList(GoblinArtistConst.APPLY_PENDING, 1, 10);
        Assert.assertTrue("applyList status=0: " + resp.getMessage(), resp.isSuccess());
        log.info("applyList status=0 total={}", resp.getData().getTotal());
    }

    // ======================================== 4.2 已授权艺人（读 MongoDB，不缓存） ========================================

    /**
     * 本店有效授权列表 — 过滤过期和失效授权。
     *
     * <pre>
     * 接口: GET /auth/list
     * 数据源: MongoDB → GoblinArtistAuthVo collection（不缓存列表）
     * 验证: 仅返回 status=1 且 expireAt > now 的授权
     * </pre>
     */
    @Test
    public void authList_shouldReturnValidAuths() {
        ResponseDto<GoblinStoreMgtArtistAuthListVo> resp = artistService.authList();
        Assert.assertTrue("authList: " + resp.getMessage(), resp.isSuccess());
        Assert.assertNotNull(resp.getData());
        log.info("authList size={}",
                resp.getData().getList() == null ? 0 : resp.getData().getList().size());
    }

    // ======================================== 4.3 专题 全流程（MongoDB + Redis 缓存 + MySQL MQ 异步） ========================================

    /**
     * 专题 CURD 完整生命周期测试。
     *
     * <pre>
     * 步骤:
     *   1. POST /topic/create        → MongoDB insert + Redis set (TTL=86400) + MQ SQL insert topic + goods
     *   2. GET  /topic/detail        → Redis 命中 → 返回内嵌 goods 列表
     *   3. GET  /topic/list          → MongoDB 按 storeId 全量查询（不缓存列表）
     *   4. POST /topic/status        → MongoDB update status + Redis del 单条缓存 + MQ SQL update
     *   5. POST /topic/update        → MongoDB update + Redis del 单条缓存 + MQ SQL update topic + goods replace
     *   6. POST /topic/status        → 同上（下架）
     *   7. POST /topic/delete        → MongoDB update delFlg=1 + Redis del + MQ SQL update
     *   8. GET  /topic/detail        → 验证逻辑删除后不可查
     *
     * 数据验证:
     *   步骤 2: topicName 对齐、status=0(待上架)
     *   步骤 3: countPending >= 1
     *   步骤 5: templateType 从 GRID(1) 改为 LIST(2)
     *   步骤 8: isSuccess() == false
     * </pre>
     */
    @Test
    public void topicCreate_update_status_delete_flow() {
        // 1. 创建 —— MongoDB insert → Redis set → MQ SQL insert
        String topicName = "测试专题_" + System.currentTimeMillis();
        GoblinStoreMgtArtistTopicCreateParams createParams = new GoblinStoreMgtArtistTopicCreateParams();
        createParams.setTopicName(topicName);
        createParams.setTemplateType(GoblinArtistConst.TEMPLATE_GRID);
        createParams.setBannerHeight(240);

        ResponseDto<String> createResp = artistService.topicCreate(createParams);
        Assert.assertTrue("create: " + createResp.getMessage(), createResp.isSuccess());
        Assert.assertNotNull(createResp.getData());
        String topicId = createResp.getData();
        log.info("created topicId={}", topicId);

        // 2. 详情 —— Redis 命中（刚创建时已缓存），goods 已内嵌
        ResponseDto<GoblinStoreMgtArtistTopicDetailVo> detail = artistService.topicDetail(topicId);
        Assert.assertTrue("detail: " + detail.getMessage(), detail.isSuccess());
        Assert.assertEquals(topicName, detail.getData().getTopicName());
        Assert.assertEquals(Integer.valueOf(GoblinArtistConst.TOPIC_PENDING), detail.getData().getStatus());

        // 3. 列表 —— MongoDB 全量查，不缓存，按 sort ASC + created_at DESC 排序
        ResponseDto<GoblinStoreMgtArtistTopicPageVo> listResp = artistService.topicList(null, 1, 20);
        Assert.assertTrue("list: " + listResp.getMessage(), listResp.isSuccess());
        Assert.assertTrue("countPending >= 1", listResp.getData().getCountPending() >= 1);

        // 4. 上架 —— MongoDB update + Redis del + MQ SQL update
        GoblinStoreMgtArtistTopicStatusParams statusParams = new GoblinStoreMgtArtistTopicStatusParams();
        statusParams.setTopicId(topicId);
        statusParams.setStatus(GoblinArtistConst.TOPIC_ONLINE);
        ResponseDto<Void> onlineResp = artistService.topicStatus(statusParams);
        Assert.assertTrue("online: " + onlineResp.getMessage(), onlineResp.isSuccess());

        // 5. 编辑 —— MongoDB update + Redis del + MQ SQL update topic + delete/insert goods
        String editedName = "测试专题_编辑_" + System.currentTimeMillis();
        GoblinStoreMgtArtistTopicUpdateParams updateParams = new GoblinStoreMgtArtistTopicUpdateParams();
        updateParams.setTopicId(topicId);
        updateParams.setTopicName(editedName);
        updateParams.setTemplateType(GoblinArtistConst.TEMPLATE_LIST);
        updateParams.setBannerHeight(300);
        ResponseDto<Void> updateResp = artistService.topicUpdate(updateParams);
        Assert.assertTrue("update: " + updateResp.getMessage(), updateResp.isSuccess());

        // 编辑后重新查详情（从 MongoDB 回源，再缓存 Redis）验证字段变更
        ResponseDto<GoblinStoreMgtArtistTopicDetailVo> afterEdit = artistService.topicDetail(topicId);
        Assert.assertTrue("afterEdit detail", afterEdit.isSuccess());
        Assert.assertEquals(Integer.valueOf(GoblinArtistConst.TEMPLATE_LIST), afterEdit.getData().getTemplateType());

        // 6. 下架 —— MongoDB update + Redis del + MQ SQL update
        statusParams.setStatus(GoblinArtistConst.TOPIC_OFFLINE);
        ResponseDto<Void> offlineResp = artistService.topicStatus(statusParams);
        Assert.assertTrue("offline: " + offlineResp.getMessage(), offlineResp.isSuccess());

        // 7. 逻辑删除 —— MongoDB update delFlg=1 + Redis del + MQ SQL update
        GoblinStoreMgtArtistTopicDeleteParams deleteParams = new GoblinStoreMgtArtistTopicDeleteParams();
        deleteParams.setTopicId(topicId);
        ResponseDto<Void> deleteResp = artistService.topicDelete(deleteParams);
        Assert.assertTrue("delete: " + deleteResp.getMessage(), deleteResp.isSuccess());

        // 8. 删后 detail 不可查（delFlg=1 被过滤）
        ResponseDto<GoblinStoreMgtArtistTopicDetailVo> afterDelete = artistService.topicDetail(topicId);
        Assert.assertFalse("afterDelete should fail", afterDelete.isSuccess());
    }

    /**
     * 创建含商品专题 — 验证 goods 内嵌到 MongoDB 文档中。
     *
     * <pre>
     * 流程:
     *   searchGoods → 取第一个可用商品的 spuId
     *   topicCreate (传 goods 参数) → MongoDB 内嵌 goods(name/coverPic/sellPrice 从 GoblinGoods 冗余)
     *   topicDetail → 验证返回的 goods 非空且 spuId 对齐
     *   清理: topicDelete
     *
     * 关键验证:
     *   goods 为空时 storeTopic 商品数 = 0（非报错）
     *   goods 非空时 detail.goods.size() > 0 且 spuId 一致
     * </pre>
     */
    @Test
    public void topicCreate_withGoods() {
        // 搜索可添加商品 —— MongoDB GoblinGoodsInfoVo collection，PageRequest 真分页
        ResponseDto<PagedResult<GoblinStoreMgtArtistTopicGoodsSearchItemVo>> goodsResp = artistService.searchGoods(null, 1, 5);
        Assert.assertTrue("searchGoods: " + goodsResp.getMessage(), goodsResp.isSuccess());

        List<GoblinStoreMgtArtistTopicGoodsItemParams> goodsList = new ArrayList<>();
        if (goodsResp.getData().getList() != null && !goodsResp.getData().getList().isEmpty()) {
            GoblinStoreMgtArtistTopicGoodsItemParams item = new GoblinStoreMgtArtistTopicGoodsItemParams();
            item.setSpuId(goodsResp.getData().getList().get(0).getSpuId());
            item.setSort(0);
            goodsList.add(item);
        }

        // 创建含商品专题 —— goods 内嵌写入 MongoDB GoblinArtistTopicVo
        GoblinStoreMgtArtistTopicCreateParams createParams = new GoblinStoreMgtArtistTopicCreateParams();
        createParams.setTopicName("带商品专题_" + System.currentTimeMillis());
        createParams.setTemplateType(GoblinArtistConst.TEMPLATE_GRID);
        createParams.setBannerHeight(240);
        createParams.setGoods(goodsList);

        ResponseDto<String> createResp = artistService.topicCreate(createParams);
        Assert.assertTrue("create with goods: " + createResp.getMessage(), createResp.isSuccess());
        String topicId = createResp.getData();

        // 验证 goods 已内嵌在专题文档中
        ResponseDto<GoblinStoreMgtArtistTopicDetailVo> detail = artistService.topicDetail(topicId);
        Assert.assertTrue("detail with goods", detail.isSuccess());
        if (!goodsList.isEmpty()) {
            Assert.assertFalse("goods should not be empty", detail.getData().getGoods().isEmpty());
            Assert.assertEquals(goodsList.get(0).getSpuId(), detail.getData().getGoods().get(0).getSpuId());
        }

        // 清理
        GoblinStoreMgtArtistTopicDeleteParams deleteParams = new GoblinStoreMgtArtistTopicDeleteParams();
        deleteParams.setTopicId(topicId);
        artistService.topicDelete(deleteParams);
    }

    // ======================================== 4.3 搜索商品（读 MongoDB GoblinGoodsInfoVo） ========================================

    /**
     * 搜索本店可添加商品 — 无关键词，验证 MongoDB PageRequest 分页。
     *
     * <pre>
     * 接口: GET /topic/searchGoods?page=1&size=10
     * 数据源: MongoDB → GoblinGoodsInfoVo collection
     * 过滤: storeId + delFlg=0 + marketId 不存在 + spuType=0 或不存
     * 分页: PageRequest.of(page-1, size) — MongoDB 级真分页
     * </pre>
     */
    @Test
    public void searchGoods_shouldReturnPage() {
        ResponseDto<PagedResult<GoblinStoreMgtArtistTopicGoodsSearchItemVo>> resp = artistService.searchGoods(null, 1, 10);
        Assert.assertTrue("searchGoods: " + resp.getMessage(), resp.isSuccess());
        Assert.assertNotNull(resp.getData());
        log.info("searchGoods total={} listSize={}",
                resp.getData().getTotal(),
                resp.getData().getList() == null ? 0 : resp.getData().getList().size());
    }

    /**
     * 搜索本店可添加商品 — 关键词正则匹配。
     *
     * <pre>
     * 接口: GET /topic/searchGoods?keyword=测试&page=1&size=10
     * 匹配: Pattern.compile("^.*测试.*$", CASE_INSENSITIVE) 对 name 字段正则
     * </pre>
     */
    @Test
    public void searchGoods_byKeyword() {
        ResponseDto<PagedResult<GoblinStoreMgtArtistTopicGoodsSearchItemVo>> resp = artistService.searchGoods("测试", 1, 10);
        Assert.assertTrue("searchGoods keyword: " + resp.getMessage(), resp.isSuccess());
        log.info("searchGoods keyword=测试 total={}", resp.getData().getTotal());
    }

    /**
     * 搜索本店可添加商品 — 翻页到第 2 页，验证 skip/limit 正确。
     *
     * <pre>
     * 接口: GET /topic/searchGoods?page=2&size=5
     * 验证: 第 2 页数据量 <= 5，且 total 与首页一致
     * </pre>
     */
    @Test
    public void searchGoods_secondPage() {
        ResponseDto<PagedResult<GoblinStoreMgtArtistTopicGoodsSearchItemVo>> resp = artistService.searchGoods(null, 2, 5);
        Assert.assertTrue("searchGoods page2: " + resp.getMessage(), resp.isSuccess());
        log.info("searchGoods page=2 total={} listSize={}",

                resp.getData().getTotal(),
                resp.getData().getList() == null ? 0 : resp.getData().getList().size());
    }
}
