package com.sino.mci.send.service;

import com.sino.mci.policy.engine.PolicyCheckResult;
import com.sino.mci.policy.engine.PolicyEngine;
import com.sino.mci.policy.engine.PolicyGroupConfig;
import com.sino.mci.send.entity.SendRecord;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;

import java.time.Duration;
import java.time.LocalDateTime;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
public class PolicyEngineTest {

    @Mock
    private StringRedisTemplate redisTemplate;

    @Mock
    private ValueOperations<String, String> valueOperations;

    @Test
    void rateLimitBlocksAfterNMessagesInWindow() {
        when(redisTemplate.opsForValue()).thenReturn(valueOperations);
        when(valueOperations.increment(anyString())).thenReturn(1L, 2L, 3L);

        PolicyEngine engine = new PolicyEngine(redisTemplate);
        PolicyGroupConfig config = PolicyGroupConfig.parseJson(
                "[{\"type\":\"rate_limit\",\"scope\":\"account\",\"limit\":2,\"window\":\"1h\"}]");

        SendRecord record = createRecord("rescue", 1001L, 2001L, 3001L);
        LocalDateTime now = LocalDateTime.of(2026, 7, 7, 14, 30);

        PolicyCheckResult first = engine.check(record, config, now);
        assertTrue(first.isAllowed());

        PolicyCheckResult second = engine.check(record, config, now);
        assertTrue(second.isAllowed());

        PolicyCheckResult third = engine.check(record, config, now);
        assertFalse(third.isAllowed());
        assertEquals("rate_limit_exceeded", third.getReasonCode());

        verify(valueOperations, times(3)).increment(anyString());
        verify(redisTemplate, atLeastOnce()).expire(anyString(), eq(Duration.ofHours(1)));
    }

    @Test
    void timeWindowRejectsOutsideAllowedRange() {
        PolicyEngine engine = new PolicyEngine(redisTemplate);
        PolicyGroupConfig config = PolicyGroupConfig.parseJson(
                "[{\"type\":\"time_window\",\"allow\":[\"09:00-21:00\"]}]");

        SendRecord record = createRecord("rescue", 1001L, 2001L, 3001L);
        LocalDateTime inside = LocalDateTime.of(2026, 7, 7, 10, 0);
        LocalDateTime outside = LocalDateTime.of(2026, 7, 7, 22, 0);

        assertTrue(engine.check(record, config, inside).isAllowed());

        PolicyCheckResult rejected = engine.check(record, config, outside);
        assertFalse(rejected.isAllowed());
        assertEquals("time_window_not_allowed", rejected.getReasonCode());
    }

    @Test
    void blacklistRejectsBlockedPersonOrConversation() {
        PolicyEngine engine = new PolicyEngine(redisTemplate);
        PolicyGroupConfig config = PolicyGroupConfig.parseJson(
                "[{\"type\":\"blacklist\",\"person_ids\":[1001],\"conversation_ids\":[3001]}]");

        SendRecord normal = createRecord("rescue", 1L, 2L, 3L);
        assertTrue(engine.check(normal, config, LocalDateTime.now()).isAllowed());

        SendRecord blockedPerson = createRecord("rescue", 1L, 1001L, 3L);
        PolicyCheckResult personResult = engine.check(blockedPerson, config, LocalDateTime.now());
        assertFalse(personResult.isAllowed());
        assertEquals("blacklist_person", personResult.getReasonCode());

        SendRecord blockedConversation = createRecord("rescue", 1L, 2L, 3001L);
        PolicyCheckResult conversationResult = engine.check(blockedConversation, config, LocalDateTime.now());
        assertFalse(conversationResult.isAllowed());
        assertEquals("blacklist_conversation", conversationResult.getReasonCode());
    }

    @Test
    void combinedPoliciesAllowAndRejectCorrectly() {
        when(redisTemplate.opsForValue()).thenReturn(valueOperations);
        when(valueOperations.increment(anyString())).thenReturn(1L, 2L, 3L);

        PolicyEngine engine = new PolicyEngine(redisTemplate);
        PolicyGroupConfig config = PolicyGroupConfig.parseJson("["
                + "{\"type\":\"blacklist\",\"person_ids\":[999]},"
                + "{\"type\":\"time_window\",\"allow\":[\"09:00-21:00\"]},"
                + "{\"type\":\"rate_limit\",\"scope\":\"account\",\"limit\":2,\"window\":\"1d\"}"
                + "]");

        SendRecord record = createRecord("rescue", 1001L, 2001L, 3001L);
        LocalDateTime now = LocalDateTime.of(2026, 7, 7, 10, 0);

        assertTrue(engine.check(record, config, now).isAllowed());
        assertTrue(engine.check(record, config, now).isAllowed());

        PolicyCheckResult rejected = engine.check(record, config, now);
        assertFalse(rejected.isAllowed());
        assertEquals("rate_limit_exceeded", rejected.getReasonCode());
    }

    private SendRecord createRecord(String tenantCode, Long accountId, Long personId, Long conversationId) {
        SendRecord record = new SendRecord();
        record.setTenantCode(tenantCode);
        record.setAccountId(accountId);
        record.setPersonId(personId);
        record.setConversationId(conversationId);
        return record;
    }
}
