package org.springblade.modules.martial;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.junit.jupiter.api.*;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springblade.core.log.exception.ServiceException;
import org.springblade.modules.martial.mapper.MartialScoreMapper;
import org.springblade.modules.martial.pojo.entity.MartialScore;
import org.springblade.modules.martial.service.IMartialJudgeProjectService;
import org.springblade.modules.martial.service.impl.MartialScoreServiceImpl;

import java.math.BigDecimal;
import java.util.Arrays;
import java.util.Collections;
import java.util.concurrent.TimeUnit;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;

/**
 * 评分服务测试类 - 阿里巴巴规范
 * 
 * <p>遵循《阿里巴巴Java开发手册》测试规范：
 * <ul>
 *   <li>Given-When-Then 注释结构</li>
 *   <li>中文 @DisplayName 业务描述</li>
 *   <li>边界值测试</li>
 *   <li>异常消息验证</li>
 * </ul>
 *
 * @author test
 * @date 2026-01-16
 */
@ExtendWith(MockitoExtension.class)
@DisplayName("评分服务单元测试")
class MartialScoreServiceAliTest {

    @Spy
    @InjectMocks
    private MartialScoreServiceImpl scoreService;

    @Mock
    private MartialScoreMapper scoreMapper;

    @Mock
    private IMartialJudgeProjectService judgeProjectService;

    // ==================== 分数验证测试 ====================

    @Nested
    @DisplayName("分数验证测试-validateScore()")
    class ValidateScoreTests {

        @Test
        @DisplayName("验证分数-空值输入-应返回false")
        void test_validateScore_withNull_shouldReturnFalse() {
            // Given: 空分数输入
            BigDecimal score = null;
            
            // When: 调用验证方法
            boolean result = scoreService.validateScore(score);
            
            // Then: 返回 false
            assertFalse(result, "空分数应返回false");
        }

        @Test
        @DisplayName("验证分数-最低边界值5.000-应返回true")
        void test_validateScore_withMinBoundary_shouldReturnTrue() {
            // Given: 最低有效分数 5.000（武术评分规则：最低分5分）
            BigDecimal score = new BigDecimal("5.000");
            
            // When: 调用验证方法
            boolean result = scoreService.validateScore(score);
            
            // Then: 边界值应通过验证
            assertTrue(result, "最低边界值5.000应返回true");
        }

        @Test
        @DisplayName("验证分数-最高边界值10.000-应返回true")
        void test_validateScore_withMaxBoundary_shouldReturnTrue() {
            // Given: 最高有效分数 10.000（武术评分规则：满分10分）
            BigDecimal score = new BigDecimal("10.000");
            
            // When: 调用验证方法
            boolean result = scoreService.validateScore(score);
            
            // Then: 边界值应通过验证
            assertTrue(result, "最高边界值10.000应返回true");
        }

        @Test
        @DisplayName("验证分数-低于最低分4.999-应返回false")
        void test_validateScore_belowMin_shouldReturnFalse() {
            // Given: 低于最低分的无效分数
            BigDecimal score = new BigDecimal("4.999");
            
            // When: 调用验证方法
            boolean result = scoreService.validateScore(score);
            
            // Then: 无效分数应返回false
            assertFalse(result, "低于最低分4.999应返回false");
        }

        @Test
        @DisplayName("验证分数-高于满分10.001-应返回false")
        void test_validateScore_aboveMax_shouldReturnFalse() {
            // Given: 高于满分的无效分数
            BigDecimal score = new BigDecimal("10.001");
            
            // When: 调用验证方法
            boolean result = scoreService.validateScore(score);
            
            // Then: 无效分数应返回false
            assertFalse(result, "高于满分10.001应返回false");
        }

        @Test
        @DisplayName("验证分数-负数-1.000-应返回false")
        void test_validateScore_negative_shouldReturnFalse() {
            // Given: 负数分数（非法输入）
            BigDecimal score = new BigDecimal("-1.000");
            
            // When: 调用验证方法
            boolean result = scoreService.validateScore(score);
            
            // Then: 负数应返回false
            assertFalse(result, "负数分数应返回false");
        }

        @Test
        @DisplayName("验证分数-中间值7.500-应返回true")
        void test_validateScore_midRange_shouldReturnTrue() {
            // Given: 有效范围内的中间值
            BigDecimal score = new BigDecimal("7.500");
            
            // When: 调用验证方法
            boolean result = scoreService.validateScore(score);
            
            // Then: 有效分数应返回true
            assertTrue(result, "中间值7.500应返回true");
        }

        @Test
        @Timeout(value = 500, unit = TimeUnit.MILLISECONDS)
        @DisplayName("验证分数-性能测试-应在100ms内完成")
        void test_validateScore_performance() {
            // Given: 大量分数验证请求
            BigDecimal score = new BigDecimal("9.000");
            
            // When & Then: 1000次验证应在100ms内完成
            for (int i = 0; i < 1000; i++) {
                scoreService.validateScore(score);
            }
        }
    }

    // ==================== 保存评分测试 ====================

    @Nested
    @DisplayName("保存评分测试-save()")
    class SaveScoreTests {

        @Test
        @DisplayName("保存评分-分数超过满分-应抛出业务异常")
        void test_save_scoreAboveMax_shouldThrowServiceException() {
            // Given: 超过满分的评分数据
            MartialScore score = buildMartialScore(new BigDecimal("11.000"));
            when(judgeProjectService.hasPermission(anyLong(), anyLong())).thenReturn(true);
            
            // When: 调用保存方法
            ServiceException ex = assertThrows(ServiceException.class, 
                () -> scoreService.save(score));
            
            // Then: 应抛出包含有效范围的异常消息
            assertTrue(ex.getMessage().contains("5.000-10.000"), 
                "异常消息应包含有效分数范围");
        }

        @Test
        @DisplayName("保存评分-分数低于最低分-应抛出业务异常")
        void test_save_scoreBelowMin_shouldThrowServiceException() {
            // Given: 低于最低分的评分数据
            MartialScore score = buildMartialScore(new BigDecimal("4.000"));
            when(judgeProjectService.hasPermission(anyLong(), anyLong())).thenReturn(true);
            
            // When: 调用保存方法
            ServiceException ex = assertThrows(ServiceException.class, 
                () -> scoreService.save(score));
            
            // Then: 应抛出包含有效范围的异常消息
            assertTrue(ex.getMessage().contains("5.000-10.000"), 
                "异常消息应包含有效分数范围");
        }
    }

    // ==================== 异常评分检测测试 ====================

    @Nested
    @DisplayName("异常评分检测测试-checkAnomalyScore()")
    class CheckAnomalyScoreTests {

        @Test
        @DisplayName("异常检测-偏差超过阈值1.0-应记录警告日志")
        void test_checkAnomalyScore_largeDeviation_shouldLogWarning() {
            // Given: 已有3个裁判评分，平均分9.0
            MartialScore existing1 = buildMartialScore(new BigDecimal("9.0"));
            existing1.setJudgeId(1L);
            MartialScore existing2 = buildMartialScore(new BigDecimal("9.0"));
            existing2.setJudgeId(2L);
            MartialScore existing3 = buildMartialScore(new BigDecimal("9.0"));
            existing3.setJudgeId(3L);
            
            doReturn(Arrays.asList(existing1, existing2, existing3))
                .when(scoreService).list(any(QueryWrapper.class));
            
            // Given: 新评分偏差2.0（超过阈值1.0）
            MartialScore newScore = buildMartialScore(new BigDecimal("7.0"));
            newScore.setJudgeId(99L);
            
            // When & Then: 应不抛出异常，仅记录警告日志
            assertDoesNotThrow(() -> scoreService.checkAnomalyScore(newScore),
                "异常评分检测不应抛出异常，仅记录日志");
        }

        @Test
        @DisplayName("异常检测-评分数量不足-应跳过检测")
        void test_checkAnomalyScore_insufficientScores_shouldSkip() {
            // Given: 仅有1个已有评分（不足以计算平均分）
            MartialScore existing = buildMartialScore(new BigDecimal("9.0"));
            existing.setJudgeId(1L);
            
            doReturn(Collections.singletonList(existing))
                .when(scoreService).list(any(QueryWrapper.class));
            
            // Given: 极端偏差的新评分
            MartialScore newScore = buildMartialScore(new BigDecimal("5.0"));
            newScore.setJudgeId(99L);
            
            // When & Then: 评分数量不足时应跳过检测
            assertDoesNotThrow(() -> scoreService.checkAnomalyScore(newScore),
                "评分数量不足时应跳过异常检测");
        }

        @Test
        @DisplayName("异常检测-偏差在阈值内-应正常通过")
        void test_checkAnomalyScore_withinThreshold_shouldPass() {
            // Given: 已有3个裁判评分，平均分9.0
            MartialScore existing1 = buildMartialScore(new BigDecimal("9.0"));
            existing1.setJudgeId(1L);
            MartialScore existing2 = buildMartialScore(new BigDecimal("9.0"));
            existing2.setJudgeId(2L);
            MartialScore existing3 = buildMartialScore(new BigDecimal("9.0"));
            existing3.setJudgeId(3L);
            
            doReturn(Arrays.asList(existing1, existing2, existing3))
                .when(scoreService).list(any(QueryWrapper.class));
            
            // Given: 新评分偏差0.5（在阈值1.0内）
            MartialScore newScore = buildMartialScore(new BigDecimal("8.5"));
            newScore.setJudgeId(99L);
            
            // When & Then: 偏差在阈值内应正常通过
            assertDoesNotThrow(() -> scoreService.checkAnomalyScore(newScore),
                "偏差在阈值内应正常通过");
        }
    }

    // ==================== 测试数据构造方法 ====================

    /**
     * 构造评分测试数据
     *
     * @param score 分数
     * @return 评分实体
     */
    private MartialScore buildMartialScore(BigDecimal score) {
        MartialScore s = new MartialScore();
        s.setId(1L);
        s.setCompetitionId(1L);
        s.setAthleteId(100L);
        s.setProjectId(10L);
        s.setJudgeId(5L);
        s.setJudgeName("测试裁判");
        s.setScore(score);
        s.setStatus(0);
        return s;
    }
}
