package org.springblade.modules.martial;

import org.junit.jupiter.api.*;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.mockito.InjectMocks;
import org.mockito.Mock;
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.List;

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

/**
 * MartialScoreServiceImpl Unit Tests - Google Standard
 * Tests actual business logic methods
 */
@ExtendWith(MockitoExtension.class)
@DisplayName("MartialScoreServiceImpl Business Logic Tests")
class MartialScoreServiceImplTest {

    @InjectMocks
    private MartialScoreServiceImpl scoreService;

    @Mock
    private MartialScoreMapper scoreMapper;

    @Mock
    private IMartialJudgeProjectService judgeProjectService;

    // ==================== validateScore() Tests ====================

    @Nested
    @DisplayName("validateScore() - Score Range Validation [5.000-10.000]")
    class ValidateScoreTests {

        @Test
        @DisplayName("null score returns false")
        void validateScore_null_returnsFalse() {
            assertFalse(scoreService.validateScore(null));
        }

        @Test
        @DisplayName("exactly MIN_SCORE (5.000) returns true")
        void validateScore_exactlyMinScore_returnsTrue() {
            assertTrue(scoreService.validateScore(new BigDecimal("5.000")));
        }

        @Test
        @DisplayName("exactly MAX_SCORE (10.000) returns true")
        void validateScore_exactlyMaxScore_returnsTrue() {
            assertTrue(scoreService.validateScore(new BigDecimal("10.000")));
        }

        @Test
        @DisplayName("below MIN_SCORE (4.999) returns false")
        void validateScore_belowMinScore_returnsFalse() {
            assertFalse(scoreService.validateScore(new BigDecimal("4.999")));
        }

        @Test
        @DisplayName("above MAX_SCORE (10.001) returns false")
        void validateScore_aboveMaxScore_returnsFalse() {
            assertFalse(scoreService.validateScore(new BigDecimal("10.001")));
        }

        @ParameterizedTest
        @ValueSource(strings = {"5.000", "5.001", "7.500", "9.999", "10.000"})
        @DisplayName("valid scores within range return true")
        void validateScore_validScores_returnsTrue(String score) {
            assertTrue(scoreService.validateScore(new BigDecimal(score)));
        }

        @ParameterizedTest
        @ValueSource(strings = {"0.000", "4.999", "10.001", "15.000", "-1.000"})
        @DisplayName("invalid scores outside range return false")
        void validateScore_invalidScores_returnsFalse(String score) {
            assertFalse(scoreService.validateScore(new BigDecimal(score)));
        }

        @Test
        @DisplayName("boundary: 5.000 is valid (inclusive)")
        void validateScore_lowerBoundaryInclusive() {
            assertTrue(scoreService.validateScore(new BigDecimal("5.000")));
        }

        @Test
        @DisplayName("boundary: 10.000 is valid (inclusive)")
        void validateScore_upperBoundaryInclusive() {
            assertTrue(scoreService.validateScore(new BigDecimal("10.000")));
        }
    }

    // ==================== Anomaly Detection Logic Tests ====================

    @Nested
    @DisplayName("Anomaly Detection Logic Tests")
    class AnomalyDetectionLogicTests {

        @Test
        @DisplayName("deviation > 1.0 is anomaly")
        void anomalyThreshold_deviationAbove1_isAnomaly() {
            BigDecimal avgScore = new BigDecimal("9.000");
            BigDecimal newScore = new BigDecimal("7.500"); // deviation = 1.5
            BigDecimal threshold = new BigDecimal("1.000");
            
            BigDecimal deviation = newScore.subtract(avgScore).abs();
            assertTrue(deviation.compareTo(threshold) > 0, 
                "Deviation of 1.5 should exceed threshold of 1.0");
        }

        @Test
        @DisplayName("deviation = 1.0 is not anomaly (boundary)")
        void anomalyThreshold_deviationEquals1_notAnomaly() {
            BigDecimal avgScore = new BigDecimal("9.000");
            BigDecimal newScore = new BigDecimal("8.000"); // deviation = 1.0
            BigDecimal threshold = new BigDecimal("1.000");
            
            BigDecimal deviation = newScore.subtract(avgScore).abs();
            assertFalse(deviation.compareTo(threshold) > 0, 
                "Deviation of exactly 1.0 should not exceed threshold");
        }

        @Test
        @DisplayName("deviation < 1.0 is not anomaly")
        void anomalyThreshold_deviationBelow1_notAnomaly() {
            BigDecimal avgScore = new BigDecimal("9.000");
            BigDecimal newScore = new BigDecimal("8.500"); // deviation = 0.5
            BigDecimal threshold = new BigDecimal("1.000");
            
            BigDecimal deviation = newScore.subtract(avgScore).abs();
            assertFalse(deviation.compareTo(threshold) > 0, 
                "Deviation of 0.5 should not exceed threshold");
        }

        @Test
        @DisplayName("calculate average score correctly")
        void calculateAverageScore_multipleScores_correct() {
            List<BigDecimal> scores = Arrays.asList(
                new BigDecimal("9.0"),
                new BigDecimal("9.2"),
                new BigDecimal("8.8")
            );
            
            BigDecimal sum = scores.stream().reduce(BigDecimal.ZERO, BigDecimal::add);
            BigDecimal avg = sum.divide(new BigDecimal(scores.size()), 3, java.math.RoundingMode.HALF_UP);
            
            assertEquals(new BigDecimal("9.000"), avg);
        }
    }

    // ==================== Save Business Logic Tests ====================

    @Nested
    @DisplayName("save() - Business Rule Validation")
    class SaveBusinessRuleTests {

        @Test
        @DisplayName("save with score above MAX throws ServiceException")
        void save_scoreAboveMax_throwsException() {
            MartialScore score = createTestScore(new BigDecimal("11.0"));
            when(judgeProjectService.hasPermission(anyLong(), anyLong())).thenReturn(true);
            
            ServiceException ex = assertThrows(ServiceException.class, 
                () -> scoreService.save(score));
            assertTrue(ex.getMessage().contains("5.000-10.000"));
        }

        @Test
        @DisplayName("save with score below MIN throws ServiceException")
        void save_scoreBelowMin_throwsException() {
            MartialScore score = createTestScore(new BigDecimal("4.0"));
            when(judgeProjectService.hasPermission(anyLong(), anyLong())).thenReturn(true);
            
            ServiceException ex = assertThrows(ServiceException.class, 
                () -> scoreService.save(score));
            assertTrue(ex.getMessage().contains("5.000-10.000"));
        }

        @Test
        @DisplayName("save with exactly MIN score (5.000) passes validation")
        void save_exactlyMinScore_passesValidation() {
            MartialScore score = createTestScore(new BigDecimal("5.000"));
            // Should not throw for valid score
            assertTrue(scoreService.validateScore(score.getScore()));
        }

        @Test
        @DisplayName("save with exactly MAX score (10.000) passes validation")
        void save_exactlyMaxScore_passesValidation() {
            MartialScore score = createTestScore(new BigDecimal("10.000"));
            // Should not throw for valid score
            assertTrue(scoreService.validateScore(score.getScore()));
        }
    }

    // ==================== Update Status Check Tests ====================

    @Nested
    @DisplayName("updateById() - Status Check Logic")
    class UpdateStatusCheckTests {

        @Test
        @DisplayName("status=0 (draft) allows update")
        void statusCheck_draft_allowsUpdate() {
            int status = 0;
            assertTrue(status != 1, "Draft status should allow update");
        }

        @Test
        @DisplayName("status=1 (submitted) blocks update")
        void statusCheck_submitted_blocksUpdate() {
            int status = 1;
            assertTrue(status == 1, "Submitted status should block update");
        }

        @Test
        @DisplayName("status=2 (modified) allows update")
        void statusCheck_modified_allowsUpdate() {
            int status = 2;
            assertTrue(status != 1, "Modified status should allow update");
        }
    }

    // ==================== Batch Validation Tests ====================

    @Nested
    @DisplayName("validateScores() - Batch Validation")
    class ValidateScoresBatchTests {

        @Test
        @DisplayName("all valid scores return true")
        void validateScores_allValid_returnsTrue() {
            List<BigDecimal> scores = Arrays.asList(
                new BigDecimal("9.0"),
                new BigDecimal("8.5"),
                new BigDecimal("9.5")
            );
            
            boolean allValid = scores.stream()
                .allMatch(s -> scoreService.validateScore(s));
            assertTrue(allValid);
        }

        @Test
        @DisplayName("one invalid score returns false")
        void validateScores_oneInvalid_returnsFalse() {
            List<BigDecimal> scores = Arrays.asList(
                new BigDecimal("9.0"),
                new BigDecimal("4.0"), // invalid
                new BigDecimal("9.5")
            );
            
            boolean allValid = scores.stream()
                .allMatch(s -> scoreService.validateScore(s));
            assertFalse(allValid);
        }

        @Test
        @DisplayName("empty list returns true (vacuous truth)")
        void validateScores_empty_returnsTrue() {
            List<BigDecimal> scores = Collections.emptyList();
            
            boolean allValid = scores.stream()
                .allMatch(s -> scoreService.validateScore(s));
            assertTrue(allValid);
        }
    }

    // ==================== Helper Methods ====================

    private MartialScore createTestScore(BigDecimal score) {
        MartialScore s = new MartialScore();
        s.setId(1L);
        s.setCompetitionId(1L);
        s.setAthleteId(100L);
        s.setProjectId(10L);
        s.setJudgeId(5L);
        s.setJudgeName("Test Judge");
        s.setScore(score);
        s.setStatus(0);
        return s;
    }
}
