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

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

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

/**
 * MartialCompetitionService Unit Tests
 * 
 * Test coverage for competition entity validation and business logic
 * 
 * @author QA Team
 */
@ExtendWith(MockitoExtension.class)
@DisplayName("Competition Service Tests")
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class MartialCompetitionServiceTest {

    private MartialCompetition validCompetition;
    private LocalDateTime now;

    @BeforeEach
    void setUp() {
        now = LocalDateTime.now();
        validCompetition = createValidCompetition();
    }

    private MartialCompetition createValidCompetition() {
        MartialCompetition competition = new MartialCompetition();
        competition.setId(1L);
        competition.setCompetitionName("2026 National Wushu Championship");
        competition.setCompetitionCode("WS2026001");
        competition.setOrganizer("China Wushu Association");
        competition.setLocation("Beijing");
        competition.setVenue("National Stadium");
        competition.setRegistrationStartTime(now.plusDays(1));
        competition.setRegistrationEndTime(now.plusDays(30));
        competition.setCompetitionStartTime(now.plusDays(45));
        competition.setCompetitionEndTime(now.plusDays(47));
        competition.setContactPerson("Zhang Ming");
        competition.setContactPhone("13800138000");
        competition.setContactEmail("contact@wushu.org");
        competition.setStatus(0);
        competition.setTotalParticipants(0);
        competition.setTotalAmount(BigDecimal.ZERO);
        return competition;
    }

    // ==================== Entity Validation Tests ====================

    @Nested
    @DisplayName("Entity Validation Tests")
    class EntityValidationTests {

        @Test
        @Order(1)
        @DisplayName("Valid competition entity should have all required fields")
        void test_validCompetition_hasAllRequiredFields() {
            assertAll("Required fields validation",
                () -> assertNotNull(validCompetition.getCompetitionName(), "Name should not be null"),
                () -> assertNotNull(validCompetition.getCompetitionCode(), "Code should not be null"),
                () -> assertNotNull(validCompetition.getOrganizer(), "Organizer should not be null"),
                () -> assertNotNull(validCompetition.getLocation(), "Location should not be null")
            );
        }

        @Test
        @Order(2)
        @DisplayName("Competition code should follow naming convention")
        void test_competitionCode_followsNamingConvention() {
            String code = validCompetition.getCompetitionCode();
            assertTrue(code.matches("^[A-Z]{2}\\d{7}$"), 
                "Code should match pattern: 2 uppercase letters + 7 digits");
        }

        @ParameterizedTest
        @NullAndEmptySource
        @DisplayName("Competition name should not be null or empty")
        void test_competitionName_notNullOrEmpty(String name) {
            validCompetition.setCompetitionName(name);
            assertTrue(name == null || name.isEmpty(), 
                "This test verifies null/empty detection");
        }

        @Test
        @Order(3)
        @DisplayName("Contact phone should be valid format")
        void test_contactPhone_validFormat() {
            String phone = validCompetition.getContactPhone();
            assertNotNull(phone);
            assertEquals(11, phone.length(), "Phone should be 11 digits");
            assertTrue(phone.startsWith("1"), "Phone should start with 1");
        }

        @ParameterizedTest
        @ValueSource(strings = {"test@example.com", "user@domain.org", "admin@wushu.cn"})
        @DisplayName("Contact email should be valid format")
        void test_contactEmail_validFormat(String email) {
            validCompetition.setContactEmail(email);
            assertTrue(email.contains("@") && email.contains("."),
                "Email should contain @ and .");
        }
    }

    // ==================== Date Validation Tests ====================

    @Nested
    @DisplayName("Date Validation Tests")
    class DateValidationTests {

        @Test
        @Order(10)
        @DisplayName("Registration end should be after registration start")
        void test_registrationDates_endAfterStart() {
            LocalDateTime start = validCompetition.getRegistrationStartTime();
            LocalDateTime end = validCompetition.getRegistrationEndTime();
            assertTrue(end.isAfter(start), "Registration end time should be after start time");
        }

        @Test
        @Order(11)
        @DisplayName("Competition start should be after registration end")
        void test_competitionStart_afterRegistrationEnd() {
            LocalDateTime regEnd = validCompetition.getRegistrationEndTime();
            LocalDateTime compStart = validCompetition.getCompetitionStartTime();
            assertTrue(compStart.isAfter(regEnd), "Competition should start after registration ends");
        }

        @Test
        @Order(12)
        @DisplayName("Competition end should be after competition start")
        void test_competitionDates_endAfterStart() {
            LocalDateTime start = validCompetition.getCompetitionStartTime();
            LocalDateTime end = validCompetition.getCompetitionEndTime();
            assertTrue(end.isAfter(start), "Competition end time should be after start time");
        }

        @Test
        @Order(13)
        @DisplayName("Invalid date range should be detected")
        void test_invalidDateRange_detected() {
            validCompetition.setRegistrationStartTime(now.plusDays(30));
            validCompetition.setRegistrationEndTime(now.plusDays(1));
            LocalDateTime start = validCompetition.getRegistrationStartTime();
            LocalDateTime end = validCompetition.getRegistrationEndTime();
            assertTrue(end.isBefore(start), "Should detect invalid date range");
        }

        @Test
        @Order(14)
        @DisplayName("Past registration start should be detected")
        void test_pastRegistrationStart_detected() {
            validCompetition.setRegistrationStartTime(now.minusDays(10));
            assertTrue(validCompetition.getRegistrationStartTime().isBefore(now),
                "Should detect past registration start date");
        }
    }

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

    @Nested
    @DisplayName("Business Logic Tests")
    class BusinessLogicTests {

        @Test
        @Order(40)
        @DisplayName("Competition status should be valid")
        void test_competitionStatus_validValues() {
            int[] validStatuses = {0, 1, 2, 3, 4};
            for (int status : validStatuses) {
                validCompetition.setStatus(status);
                assertTrue(status >= 0 && status <= 4, "Status " + status + " should be valid");
            }
        }

        @Test
        @Order(41)
        @DisplayName("Is boutique flag should be valid")
        void test_isBoutique_validValues() {
            validCompetition.setIsCompeBoutique(1);
            assertEquals(1, validCompetition.getIsCompeBoutique());
            validCompetition.setIsCompeBoutique(2);
            assertEquals(2, validCompetition.getIsCompeBoutique());
        }

        @Test
        @Order(42)
        @DisplayName("Total participants should be non-negative")
        void test_totalParticipants_nonNegative() {
            validCompetition.setTotalParticipants(100);
            assertTrue(validCompetition.getTotalParticipants() >= 0);
        }

        @Test
        @Order(43)
        @DisplayName("Total amount should be non-negative")
        void test_totalAmount_nonNegative() {
            validCompetition.setTotalAmount(new BigDecimal("10000.00"));
            assertTrue(validCompetition.getTotalAmount().compareTo(BigDecimal.ZERO) >= 0);
        }

        @ParameterizedTest
        @CsvSource({"100, 50.00, 5000.00", "200, 100.00, 20000.00", "50, 0.00, 0.00"})
        @DisplayName("Calculate expected revenue")
        void test_calculateExpectedRevenue(int participants, String fee, String expectedRevenue) {
            BigDecimal feeAmount = new BigDecimal(fee);
            BigDecimal expected = new BigDecimal(expectedRevenue);
            BigDecimal actual = feeAmount.multiply(BigDecimal.valueOf(participants));
            assertEquals(expected, actual);
        }
    }

    // ==================== Edge Case Tests ====================

    @Nested
    @DisplayName("Edge Case Tests")
    class EdgeCaseTests {

        @Test
        @Order(50)
        @DisplayName("Competition with maximum name length")
        void test_competitionName_maxLength() {
            String longName = "A".repeat(200);
            validCompetition.setCompetitionName(longName);
            assertEquals(200, validCompetition.getCompetitionName().length());
        }

        @Test
        @Order(51)
        @DisplayName("Competition with special characters in name")
        void test_competitionName_specialCharacters() {
            String specialName = "2026 Championship (Spring) - Beijing [Final]";
            validCompetition.setCompetitionName(specialName);
            assertEquals(specialName, validCompetition.getCompetitionName());
        }

        @Test
        @Order(52)
        @DisplayName("Competition with Chinese characters")
        void test_competitionName_chineseCharacters() {
            String chineseName = "2026年全国武术锦标赛";
            validCompetition.setCompetitionName(chineseName);
            assertEquals(chineseName, validCompetition.getCompetitionName());
        }

        @Test
        @Order(53)
        @DisplayName("Competition with zero participants")
        void test_competition_zeroParticipants() {
            validCompetition.setTotalParticipants(0);
            assertEquals(0, validCompetition.getTotalParticipants());
        }

        @Test
        @Order(54)
        @DisplayName("Competition with null optional fields")
        void test_competition_nullOptionalFields() {
            validCompetition.setIntroduction(null);
            validCompetition.setPosterImages(null);
            validCompetition.setRules(null);
            validCompetition.setAwards(null);
            assertAll("Optional fields can be null",
                () -> assertNull(validCompetition.getIntroduction()),
                () -> assertNull(validCompetition.getPosterImages()),
                () -> assertNull(validCompetition.getRules()),
                () -> assertNull(validCompetition.getAwards())
            );
        }
    }
}
