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.CsvSource;
import org.junit.jupiter.params.provider.ValueSource;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springblade.modules.martial.pojo.entity.MartialProject;

import java.math.BigDecimal;
import java.time.LocalDateTime;

import static org.junit.jupiter.api.Assertions.*;

/**
 * MartialProjectService Unit Tests
 * @author QA Team
 */
@ExtendWith(MockitoExtension.class)
@DisplayName("Project Service Tests")
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class MartialProjectServiceTest {

    private MartialProject validProject;
    private LocalDateTime now;

    @BeforeEach
    void setUp() {
        now = LocalDateTime.now();
        validProject = createValidProject();
    }

    private MartialProject createValidProject() {
        MartialProject project = new MartialProject();
        project.setId(1L);
        project.setCompetitionId(100L);
        project.setVenueId(10L);
        project.setProjectName("Men Changquan");
        project.setProjectCode("CQ001");
        project.setCategory("Male");
        project.setEventType(1);
        project.setType(1);
        project.setMinParticipants(1);
        project.setMaxParticipants(1);
        project.setMinAge(18);
        project.setMaxAge(35);
        project.setGenderLimit(1);
        project.setEstimatedDuration(5);
        project.setPrice(new BigDecimal("100.00"));
        project.setDifficultyCoefficient(new BigDecimal("1.00"));
        project.setRegistrationStartTime(now.plusDays(1));
        project.setRegistrationEndTime(now.plusDays(30));
        project.setSortOrder(1);
        project.setStatus(0);
        return project;
    }

    @Nested
    @DisplayName("Entity Validation Tests")
    class EntityValidationTests {
        @Test
        @DisplayName("Valid project should have all required fields")
        void test_validProject_hasAllRequiredFields() {
            assertAll("Required fields",
                () -> assertNotNull(validProject.getCompetitionId()),
                () -> assertNotNull(validProject.getProjectName()),
                () -> assertNotNull(validProject.getProjectCode()),
                () -> assertNotNull(validProject.getEventType()),
                () -> assertNotNull(validProject.getType())
            );
        }

        @ParameterizedTest
        @ValueSource(ints = {1, 2, 3, 4})
        @DisplayName("Event type should be valid (1-4)")
        void test_eventType_validValues(int eventType) {
            validProject.setEventType(eventType);
            assertTrue(eventType >= 1 && eventType <= 4);
        }

        @ParameterizedTest
        @ValueSource(ints = {1, 2, 3})
        @DisplayName("Project type should be valid (1-3)")
        void test_projectType_validValues(int type) {
            validProject.setType(type);
            assertTrue(type >= 1 && type <= 3);
        }

        @ParameterizedTest
        @ValueSource(ints = {0, 1, 2})
        @DisplayName("Gender limit should be valid (0-2)")
        void test_genderLimit_validValues(int genderLimit) {
            validProject.setGenderLimit(genderLimit);
            assertTrue(genderLimit >= 0 && genderLimit <= 2);
        }
    }

    @Nested
    @DisplayName("Age Validation Tests")
    class AgeValidationTests {
        @Test
        @DisplayName("Max age should be >= min age")
        void test_ageRange_maxGreaterThanMin() {
            assertTrue(validProject.getMaxAge() >= validProject.getMinAge());
        }

        @ParameterizedTest
        @CsvSource({"6, 12", "13, 17", "18, 35", "36, 60"})
        @DisplayName("Age range should be valid")
        void test_ageRange_validRanges(int minAge, int maxAge) {
            validProject.setMinAge(minAge);
            validProject.setMaxAge(maxAge);
            assertTrue(maxAge >= minAge);
        }

        @Test
        @DisplayName("Invalid age range should be detected")
        void test_ageRange_invalidDetected() {
            validProject.setMinAge(35);
            validProject.setMaxAge(18);
            assertTrue(validProject.getMinAge() > validProject.getMaxAge());
        }
    }

    @Nested
    @DisplayName("Participant Validation Tests")
    class ParticipantValidationTests {
        @Test
        @DisplayName("Max participants should be >= min participants")
        void test_participantRange_valid() {
            assertTrue(validProject.getMaxParticipants() >= validProject.getMinParticipants());
        }

        @ParameterizedTest
        @CsvSource({"1, 1, 1", "2, 2, 2", "3, 8, 3"})
        @DisplayName("Participant range by project type")
        void test_participantRange_byType(int type, int max, int min) {
            validProject.setType(type);
            validProject.setMinParticipants(min);
            validProject.setMaxParticipants(max);
            assertTrue(validProject.getMaxParticipants() >= validProject.getMinParticipants());
        }
    }

    @Nested
    @DisplayName("Price and Coefficient Tests")
    class PriceAndCoefficientTests {
        @Test
        @DisplayName("Price should be non-negative")
        void test_price_nonNegative() {
            assertTrue(validProject.getPrice().compareTo(BigDecimal.ZERO) >= 0);
        }

        @ParameterizedTest
        @CsvSource({"0.00", "50.00", "100.00", "500.00"})
        @DisplayName("Valid price values")
        void test_price_validValues(String price) {
            validProject.setPrice(new BigDecimal(price));
            assertTrue(validProject.getPrice().compareTo(BigDecimal.ZERO) >= 0);
        }

        @Test
        @DisplayName("Difficulty coefficient should be positive")
        void test_difficultyCoefficient_positive() {
            assertTrue(validProject.getDifficultyCoefficient().compareTo(BigDecimal.ZERO) > 0);
        }

        @ParameterizedTest
        @CsvSource({"0.80", "1.00", "1.20", "1.50"})
        @DisplayName("Valid difficulty coefficient values")
        void test_difficultyCoefficient_validValues(String coefficient) {
            validProject.setDifficultyCoefficient(new BigDecimal(coefficient));
            assertTrue(validProject.getDifficultyCoefficient().compareTo(BigDecimal.ZERO) > 0);
        }
    }

    @Nested
    @DisplayName("Business Logic Tests")
    class BusinessLogicTests {
        @Test
        @DisplayName("Individual project should have 1 participant")
        void test_individualProject_oneParticipant() {
            validProject.setType(1);
            validProject.setMinParticipants(1);
            validProject.setMaxParticipants(1);
            assertEquals(1, validProject.getMinParticipants());
            assertEquals(1, validProject.getMaxParticipants());
        }

        @Test
        @DisplayName("Dual project should have 2 participants")
        void test_dualProject_twoParticipants() {
            validProject.setType(2);
            validProject.setMinParticipants(2);
            validProject.setMaxParticipants(2);
            assertEquals(2, validProject.getMinParticipants());
        }

        @Test
        @DisplayName("Group project should have 3+ participants")
        void test_groupProject_multipleParticipants() {
            validProject.setType(3);
            validProject.setMinParticipants(3);
            validProject.setMaxParticipants(8);
            assertTrue(validProject.getMinParticipants() >= 3);
        }

        @Test
        @DisplayName("Estimated duration should be positive")
        void test_estimatedDuration_positive() {
            assertTrue(validProject.getEstimatedDuration() > 0);
        }

        @Test
        @DisplayName("Sort order should be non-negative")
        void test_sortOrder_nonNegative() {
            assertTrue(validProject.getSortOrder() >= 0);
        }
    }

    @Nested
    @DisplayName("Edge Case Tests")
    class EdgeCaseTests {
        @Test
        @DisplayName("Project with Chinese name")
        void test_projectName_chinese() {
            validProject.setProjectName("男子长拳");
            assertEquals("男子长拳", validProject.getProjectName());
        }

        @Test
        @DisplayName("Project with null optional fields")
        void test_nullOptionalFields() {
            validProject.setDescription(null);
            validProject.setVenueId(null);
            assertNull(validProject.getDescription());
            assertNull(validProject.getVenueId());
        }

        @Test
        @DisplayName("Project with zero price (free)")
        void test_freeProject() {
            validProject.setPrice(BigDecimal.ZERO);
            assertEquals(BigDecimal.ZERO, validProject.getPrice());
        }

        @Test
        @DisplayName("Project with default difficulty coefficient")
        void test_defaultDifficultyCoefficient() {
            validProject.setDifficultyCoefficient(new BigDecimal("1.00"));
            assertEquals(new BigDecimal("1.00"), validProject.getDifficultyCoefficient());
        }
    }
}
