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 static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;

/**
 * MartialScoreServiceImpl Unit Tests - Apple Standard
 * Tests ACTUALLY call the Service methods with proper mocking
 */
@ExtendWith(MockitoExtension.class)
@DisplayName("Apple Standard: MartialScoreServiceImpl Tests")
class MartialScoreServiceAppleTest {

    @Spy
    @InjectMocks
    private MartialScoreServiceImpl scoreService;

    @Mock
    private MartialScoreMapper scoreMapper;

    @Mock
    private IMartialJudgeProjectService judgeProjectService;

    // ==================== validateScore() - Real Method Calls ====================

    @Nested
    @DisplayName("test_validateScore - Real Service Method Calls")
    class ValidateScoreRealTests {

        @Test
        @DisplayName("test_validateScore_withNullInput_shouldReturnFalse")
        void test_validateScore_withNullInput_shouldReturnFalse() {
            boolean result = scoreService.validateScore(null);
            assertFalse(result, "validateScore(null) should return false");
        }

        @Test
        @DisplayName("test_validateScore_withMinBoundary_shouldReturnTrue")
        void test_validateScore_withMinBoundary_shouldReturnTrue() {
            boolean result = scoreService.validateScore(new BigDecimal("5.000"));
            assertTrue(result, "validateScore(5.000) should return true");
        }

        @Test
        @DisplayName("test_validateScore_withMaxBoundary_shouldReturnTrue")
        void test_validateScore_withMaxBoundary_shouldReturnTrue() {
            boolean result = scoreService.validateScore(new BigDecimal("10.000"));
            assertTrue(result, "validateScore(10.000) should return true");
        }

        @Test
        @DisplayName("test_validateScore_withBelowMin_shouldReturnFalse")
        void test_validateScore_withBelowMin_shouldReturnFalse() {
            boolean result = scoreService.validateScore(new BigDecimal("4.999"));
            assertFalse(result, "validateScore(4.999) should return false");
        }

        @Test
        @DisplayName("test_validateScore_withAboveMax_shouldReturnFalse")
        void test_validateScore_withAboveMax_shouldReturnFalse() {
            boolean result = scoreService.validateScore(new BigDecimal("10.001"));
            assertFalse(result, "validateScore(10.001) should return false");
        }

        @Test
        @DisplayName("test_validateScore_withNegativeValue_shouldReturnFalse")
        void test_validateScore_withNegativeValue_shouldReturnFalse() {
            boolean result = scoreService.validateScore(new BigDecimal("-1.000"));
            assertFalse(result, "validateScore(-1.000) should return false");
        }

        @Test
        @DisplayName("test_validateScore_withMidRangeValue_shouldReturnTrue")
        void test_validateScore_withMidRangeValue_shouldReturnTrue() {
            boolean result = scoreService.validateScore(new BigDecimal("7.500"));
            assertTrue(result, "validateScore(7.500) should return true");
        }
    }

    // ==================== save() - Real Method Calls with Mocking ====================

    @Nested
    @DisplayName("test_save - Real Service Method Calls")
    class SaveRealTests {

        @Test
        @DisplayName("test_save_withInvalidScoreAboveMax_shouldThrowServiceException")
        void test_save_withInvalidScoreAboveMax_shouldThrowServiceException() {
            MartialScore score = createTestScore(new BigDecimal("11.000"));
            when(judgeProjectService.hasPermission(anyLong(), anyLong())).thenReturn(true);
            
            ServiceException ex = assertThrows(ServiceException.class, 
                () -> scoreService.save(score));
            
            assertTrue(ex.getMessage().contains("5.000-10.000"), 
                "Exception message should contain valid range");
        }

        @Test
        @DisplayName("test_save_withInvalidScoreBelowMin_shouldThrowServiceException")
        void test_save_withInvalidScoreBelowMin_shouldThrowServiceException() {
            MartialScore score = createTestScore(new BigDecimal("4.000"));
            when(judgeProjectService.hasPermission(anyLong(), anyLong())).thenReturn(true);
            
            ServiceException ex = assertThrows(ServiceException.class, 
                () -> scoreService.save(score));
            
            assertTrue(ex.getMessage().contains("5.000-10.000"), 
                "Exception message should contain valid range");
        }
    }

    // ==================== checkAnomalyScore() - Real Method Calls ====================

    @Nested
    @DisplayName("test_checkAnomalyScore - Real Service Method Calls")
    class CheckAnomalyScoreRealTests {

        @Test
        @DisplayName("test_checkAnomalyScore_withLargeDeviation_shouldLogWarning")
        void test_checkAnomalyScore_withLargeDeviation_shouldLogWarning() {
            MartialScore existing1 = createTestScore(new BigDecimal("9.0"));
            existing1.setJudgeId(1L);
            MartialScore existing2 = createTestScore(new BigDecimal("9.0"));
            existing2.setJudgeId(2L);
            MartialScore existing3 = createTestScore(new BigDecimal("9.0"));
            existing3.setJudgeId(3L);
            
            doReturn(Arrays.asList(existing1, existing2, existing3))
                .when(scoreService).list(any(QueryWrapper.class));
            
            MartialScore newScore = createTestScore(new BigDecimal("7.0"));
            newScore.setJudgeId(99L);
            
            assertDoesNotThrow(() -> scoreService.checkAnomalyScore(newScore));
        }

        @Test
        @DisplayName("test_checkAnomalyScore_withInsufficientScores_shouldSkipCheck")
        void test_checkAnomalyScore_withInsufficientScores_shouldSkipCheck() {
            MartialScore existing = createTestScore(new BigDecimal("9.0"));
            existing.setJudgeId(1L);
            
            doReturn(Collections.singletonList(existing))
                .when(scoreService).list(any(QueryWrapper.class));
            
            MartialScore newScore = createTestScore(new BigDecimal("5.0"));
            newScore.setJudgeId(99L);
            
            assertDoesNotThrow(() -> scoreService.checkAnomalyScore(newScore));
        }
    }

    // ==================== 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;
    }
}
