package com.sino.mci.send.controller;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.transaction.annotation.Transactional;

import java.util.UUID;

import static com.sino.mci.test.AuthTestHelper.createTenantAndLogin;
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;

@SpringBootTest
@AutoConfigureMockMvc
@Transactional
public class SendPreviewControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Test
    void shouldPreviewConversationTarget() throws Exception {
        String tenantCode = "preview-" + UUID.randomUUID().toString().substring(0, 8);
        String authToken = createTenantAndLogin(mockMvc, tenantCode, "预览测试");

        Long accountId = createChannelAccount(authToken, tenantCode, "ZhangSan", "张三");
        Long conversationId = createConversation(authToken, tenantCode, "wecom", "room-preview-001");
        bindConversationAccount(authToken, tenantCode, conversationId, accountId);

        String payload = String.format(
                "{\"targetType\":\"conversation\",\"targetValue\":{\"conversation_id\":%d},\"channelStrategy\":{\"channel_type\":\"wecom\",\"strategy\":\"primary\"}}",
                conversationId);

        mockMvc.perform(post("/msg-platform/api/v1/send/preview")
                        .contextPath("/msg-platform")
                        .header("Authorization", authToken)
                        .header("X-Tenant-Code", tenantCode)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(payload))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.code").value(0))
                .andExpect(jsonPath("$.data.totalPersonCount").value(0))
                .andExpect(jsonPath("$.data.totalConversationCount").value(1))
                .andExpect(jsonPath("$.data.channelBreakdown.wecom").value(1))
                .andExpect(jsonPath("$.data.unreachableCount").value(0))
                .andExpect(jsonPath("$.data.accountUsage.张三").value(1))
                .andExpect(jsonPath("$.data.policyCheck.blacklistHit").value(0))
                .andExpect(jsonPath("$.data.policyCheck.timeWindowOk").value(true))
                .andExpect(jsonPath("$.data.estimatedTimeSeconds").value(3));
    }

    @Test
    void shouldPreviewRuleTargetWithChannelBreakdownAndAccountUsage() throws Exception {
        String tenantCode = "preview-" + UUID.randomUUID().toString().substring(0, 8);
        String authToken = createTenantAndLogin(mockMvc, tenantCode, "预览测试");

        Long account1 = createChannelAccount(authToken, tenantCode, "Bot01", "机器人一号");
        Long account2 = createChannelAccount(authToken, tenantCode, "Bot02", "机器人二号");

        Long tagId = createTag(authToken, tenantCode, "preview-rule");

        Long personId = createPerson(authToken, tenantCode, 3);
        bindPersonTag(authToken, tenantCode, personId, tagId);
        createPersonIdentity(authToken, tenantCode, personId, "wecom", "user-preview-001");

        Long conversationId = createConversation(authToken, tenantCode, "wecom", "room-preview-rule-001");
        bindConversationTag(authToken, tenantCode, conversationId, tagId);
        bindConversationAccount(authToken, tenantCode, conversationId, account2);

        String payload = String.format(
                "{\"targetType\":\"rule\",\"targetValue\":{\"rules\":[{\"field\":\"tag\",\"operator\":\"in\",\"value\":[%d]}],\"logic\":\"and\"},\"channelStrategy\":{\"channel_type\":\"wecom\",\"strategy\":\"primary\"}}",
                tagId);

        mockMvc.perform(post("/msg-platform/api/v1/send/preview")
                        .contextPath("/msg-platform")
                        .header("Authorization", authToken)
                        .header("X-Tenant-Code", tenantCode)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(payload))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.code").value(0))
                .andExpect(jsonPath("$.data.totalPersonCount").value(1))
                .andExpect(jsonPath("$.data.totalConversationCount").value(1))
                .andExpect(jsonPath("$.data.channelBreakdown.wecom").value(2))
                .andExpect(jsonPath("$.data.accountUsage.机器人一号").value(1))
                .andExpect(jsonPath("$.data.accountUsage.机器人二号").value(1));
    }

    @Test
    void shouldDetectBlacklistHitWithoutIncrementingRateLimitCounter() throws Exception {
        String tenantCode = "preview-" + UUID.randomUUID().toString().substring(0, 8);
        String authToken = createTenantAndLogin(mockMvc, tenantCode, "预览测试");

        Long accountId = createChannelAccount(authToken, tenantCode, "ZhangSan", "张三");
        Long personId = createPerson(authToken, tenantCode, 3);
        createPersonIdentity(authToken, tenantCode, personId, "wecom", "user-preview-002");
        Long conversationId = createConversation(authToken, tenantCode, "wecom", "room-preview-blacklist-001");
        bindConversationAccount(authToken, tenantCode, conversationId, accountId);

        String policies = "[{\"type\":\"blacklist\",\"person_ids\":["
                + personId + "]},{\"type\":\"rate_limit\",\"scope\":\"account\",\"limit\":2,\"window\":\"1h\"}]";
        createPolicyGroup(authToken, tenantCode, "default", "wecom", policies);

        String rateKey = String.format("mci:rate:%s:account:%d:%s", tenantCode, accountId,
                java.time.LocalDateTime.now().format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMddHH")));
        redisTemplate.opsForValue().set(rateKey, "1");
        try {
            String payload = String.format(
                    "{\"targetType\":\"rule\",\"targetValue\":{\"rules\":[{\"field\":\"person_type\",\"operator\":\"eq\",\"value\":3},{\"field\":\"channel_type\",\"operator\":\"eq\",\"value\":\"wecom\"}],\"logic\":\"or\"},\"channelStrategy\":{\"channel_type\":\"wecom\",\"strategy\":\"primary\"}}");

            mockMvc.perform(post("/msg-platform/api/v1/send/preview")
                            .contextPath("/msg-platform")
                            .header("Authorization", authToken)
                            .header("X-Tenant-Code", tenantCode)
                            .contentType(MediaType.APPLICATION_JSON)
                            .content(payload))
                    .andExpect(status().isOk())
                    .andExpect(jsonPath("$.code").value(0))
                    .andExpect(jsonPath("$.data.policyCheck.blacklistHit").value(1))
                    .andExpect(jsonPath("$.data.totalPersonCount").value(1))
                    .andExpect(jsonPath("$.data.totalConversationCount").value(1));

            String value = redisTemplate.opsForValue().get(rateKey);
            org.junit.jupiter.api.Assertions.assertEquals("1", value, "预览不应修改 Redis 限流计数器");
        } finally {
            redisTemplate.delete(rateKey);
        }
    }

    private Long createChannelAccount(String authToken, String tenantCode, String accountId, String accountName) throws Exception {
        String accountPayload = String.format(
                "{\"accountType\":1,\"channelType\":\"wecom\",\"accountId\":\"%s\",\"accountName\":\"%s\"}",
                accountId, accountName);
        String accountResponse = mockMvc.perform(post("/msg-platform/api/v1/channel-account")
                        .contextPath("/msg-platform")
                        .header("Authorization", authToken)
                        .header("X-Tenant-Code", tenantCode)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(accountPayload))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.code").value(0))
                .andReturn().getResponse().getContentAsString();
        return ((Number) com.jayway.jsonpath.JsonPath.read(accountResponse, "$.data.id")).longValue();
    }

    private Long createTag(String authToken, String tenantCode, String tagName) throws Exception {
        String tagPayload = String.format("{\"parentId\":0,\"tagName\":\"%s\"}", tagName);
        String tagResponse = mockMvc.perform(post("/msg-platform/api/v1/tag")
                        .contextPath("/msg-platform")
                        .header("Authorization", authToken)
                        .header("X-Tenant-Code", tenantCode)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(tagPayload))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.code").value(0))
                .andReturn().getResponse().getContentAsString();
        return ((Number) com.jayway.jsonpath.JsonPath.read(tagResponse, "$.data.id")).longValue();
    }

    private Long createPerson(String authToken, String tenantCode, int personType) throws Exception {
        String personPayload = String.format(
                "{\"personCode\":\"P-%d\",\"personName\":\"张三\",\"phone\":\"13800138000\",\"personType\":%d}",
                personType, personType);
        String personResponse = mockMvc.perform(post("/msg-platform/api/v1/person")
                        .contextPath("/msg-platform")
                        .header("Authorization", authToken)
                        .header("X-Tenant-Code", tenantCode)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(personPayload))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.code").value(0))
                .andReturn().getResponse().getContentAsString();
        return ((Number) com.jayway.jsonpath.JsonPath.read(personResponse, "$.data.id")).longValue();
    }

    private void createPersonIdentity(String authToken, String tenantCode, Long personId,
                                      String channelType, String channelUserId) throws Exception {
        String identityPayload = String.format("{\"channelType\":\"%s\",\"channelUserId\":\"%s\"}", channelType, channelUserId);
        mockMvc.perform(post("/msg-platform/api/v1/person/" + personId + "/identities")
                        .contextPath("/msg-platform")
                        .header("Authorization", authToken)
                        .header("X-Tenant-Code", tenantCode)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(identityPayload))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.code").value(0));
    }

    private void bindPersonTag(String authToken, String tenantCode, Long personId, Long tagId) throws Exception {
        String bindPayload = String.format("{\"tagIds\":[%d]}", tagId);
        mockMvc.perform(post("/msg-platform/api/v1/person/" + personId + "/bind-tags")
                        .contextPath("/msg-platform")
                        .header("Authorization", authToken)
                        .header("X-Tenant-Code", tenantCode)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(bindPayload))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.code").value(0));
    }

    private Long createConversation(String authToken, String tenantCode, String channelType,
                                    String channelConversationId) throws Exception {
        String conversationPayload = String.format(
                "{\"conversationType\":2,\"channelType\":\"%s\",\"channelConversationId\":\"%s\",\"conversationName\":\"测试群\"}",
                channelType, channelConversationId);
        String conversationResponse = mockMvc.perform(post("/msg-platform/api/v1/conversation")
                        .contextPath("/msg-platform")
                        .header("Authorization", authToken)
                        .header("X-Tenant-Code", tenantCode)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(conversationPayload))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.code").value(0))
                .andReturn().getResponse().getContentAsString();
        return ((Number) com.jayway.jsonpath.JsonPath.read(conversationResponse, "$.data.id")).longValue();
    }

    private void bindConversationTag(String authToken, String tenantCode, Long conversationId, Long tagId) throws Exception {
        String bindPayload = String.format("{\"tagIds\":[%d]}", tagId);
        mockMvc.perform(post("/msg-platform/api/v1/conversation/" + conversationId + "/bind-tags")
                        .contextPath("/msg-platform")
                        .header("Authorization", authToken)
                        .header("X-Tenant-Code", tenantCode)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(bindPayload))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.code").value(0));
    }

    private void bindConversationAccount(String authToken, String tenantCode, Long conversationId,
                                         Long accountId) throws Exception {
        String bindPayload = String.format("{\"accountId\":%d,\"bindingType\":1,\"sortOrder\":1}", accountId);
        mockMvc.perform(post("/msg-platform/api/v1/conversation/" + conversationId + "/bind-account")
                        .contextPath("/msg-platform")
                        .header("Authorization", authToken)
                        .header("X-Tenant-Code", tenantCode)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(bindPayload))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.code").value(0));
    }

    private void createPolicyGroup(String authToken, String tenantCode, String groupCode,
                                   String channelType, String policiesJson) throws Exception {
        String payload = String.format(
                "{\"groupCode\":\"%s\",\"groupName\":\"%s\",\"channelType\":\"%s\",\"policies\":%s}",
                groupCode, groupCode, channelType, policiesJson);
        mockMvc.perform(post("/msg-platform/api/v1/policy-group")
                        .contextPath("/msg-platform")
                        .header("Authorization", authToken)
                        .header("X-Tenant-Code", tenantCode)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(payload))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.code").value(0));
    }
}
