package com.kt.event.participation.test.integration;

import com.kt.event.participation.domain.participant.Participant;
import com.kt.event.participation.domain.participant.ParticipantRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;

import java.util.List;
import java.util.Optional;

import static org.assertj.core.api.Assertions.assertThat;

/**
 * ParticipantRepository 통합 테스트
 *
 * @author Digital Garage Team
 * @since 2025-01-24
 */
@DataJpaTest
@DisplayName("ParticipantRepository 통합 테스트")
class ParticipantRepositoryIntegrationTest {

    @Autowired
    private ParticipantRepository participantRepository;

    // 테스트 데이터 상수
    private static final String VALID_EVENT_ID = "evt_20250124_001";
    private static final String VALID_NAME = "홍길동";
    private static final String VALID_PHONE = "010-1234-5678";
    private static final String VALID_EMAIL = "hong@test.com";

    @BeforeEach
    void setUp() {
        participantRepository.deleteAll();
    }

    @Test
    @DisplayName("참여자를 저장하면 정상적으로 조회할 수 있다")
    void givenParticipant_whenSave_thenCanRetrieve() {
        // Given
        Participant participant = createValidParticipant();

        // When
        Participant saved = participantRepository.save(participant);

        // Then
        assertThat(saved.getId()).isNotNull();
        assertThat(saved.getParticipantId()).isEqualTo(participant.getParticipantId());
        assertThat(saved.getName()).isEqualTo(VALID_NAME);
    }

    @Test
    @DisplayName("참여자 ID로 조회하면 해당 참여자가 반환된다")
    void givenSavedParticipant_whenFindByParticipantId_thenReturnParticipant() {
        // Given
        Participant participant = createValidParticipant();
        participantRepository.save(participant);

        // When
        Optional<Participant> found = participantRepository.findByParticipantId(participant.getParticipantId());

        // Then
        assertThat(found).isPresent();
        assertThat(found.get().getName()).isEqualTo(VALID_NAME);
    }

    @Test
    @DisplayName("이벤트 ID와 전화번호로 중복 참여를 확인할 수 있다")
    void givenSavedParticipant_whenExistsByEventIdAndPhoneNumber_thenReturnTrue() {
        // Given
        Participant participant = createValidParticipant();
        participantRepository.save(participant);

        // When
        boolean exists = participantRepository.existsByEventIdAndPhoneNumber(VALID_EVENT_ID, VALID_PHONE);

        // Then
        assertThat(exists).isTrue();
    }

    @Test
    @DisplayName("이벤트 ID로 참여자 목록을 페이징 조회할 수 있다")
    void givenMultipleParticipants_whenFindByEventId_thenReturnPagedList() {
        // Given
        for (int i = 1; i <= 5; i++) {
            Participant participant = Participant.builder()
                    .participantId("prt_20250124_" + String.format("%03d", i))
                    .eventId(VALID_EVENT_ID)
                    .name("참여자" + i)
                    .phoneNumber("010-1234-" + String.format("%04d", i))
                    .email("test" + i + "@test.com")
                    .storeVisited(i % 2 == 0)
                    .bonusEntries(i % 2 == 0 ? 2 : 1)
                    .agreeMarketing(true)
                    .agreePrivacy(true)
                    .isWinner(false)
                    .build();
            participantRepository.save(participant);
        }
        Pageable pageable = PageRequest.of(0, 3);

        // When
        Page<Participant> page = participantRepository.findByEventIdOrderByCreatedAtDesc(VALID_EVENT_ID, pageable);

        // Then
        assertThat(page.getContent()).hasSize(3);
        assertThat(page.getTotalElements()).isEqualTo(5);
        assertThat(page.getTotalPages()).isEqualTo(2);
    }

    @Test
    @DisplayName("매장 방문 여부로 필터링하여 참여자 목록을 조회할 수 있다")
    void givenParticipantsWithStoreVisit_whenFindByStoreVisited_thenReturnFiltered() {
        // Given
        for (int i = 1; i <= 5; i++) {
            Participant participant = Participant.builder()
                    .participantId("prt_20250124_" + String.format("%03d", i))
                    .eventId(VALID_EVENT_ID)
                    .name("참여자" + i)
                    .phoneNumber("010-1234-" + String.format("%04d", i))
                    .email("test" + i + "@test.com")
                    .storeVisited(i % 2 == 0)
                    .bonusEntries(i % 2 == 0 ? 2 : 1)
                    .agreeMarketing(true)
                    .agreePrivacy(true)
                    .isWinner(false)
                    .build();
            participantRepository.save(participant);
        }
        Pageable pageable = PageRequest.of(0, 10);

        // When
        Page<Participant> page = participantRepository
                .findByEventIdAndStoreVisitedOrderByCreatedAtDesc(VALID_EVENT_ID, true, pageable);

        // Then
        assertThat(page.getContent()).hasSize(2);
        assertThat(page.getContent()).allMatch(Participant::getStoreVisited);
    }

    @Test
    @DisplayName("이벤트 ID로 전체 참여자 수를 조회할 수 있다")
    void givenParticipants_whenCountByEventId_thenReturnCount() {
        // Given
        for (int i = 1; i <= 3; i++) {
            Participant participant = Participant.builder()
                    .participantId("prt_20250124_" + String.format("%03d", i))
                    .eventId(VALID_EVENT_ID)
                    .name("참여자" + i)
                    .phoneNumber("010-1234-" + String.format("%04d", i))
                    .email("test" + i + "@test.com")
                    .storeVisited(true)
                    .bonusEntries(2)
                    .agreeMarketing(true)
                    .agreePrivacy(true)
                    .isWinner(false)
                    .build();
            participantRepository.save(participant);
        }

        // When
        long count = participantRepository.countByEventId(VALID_EVENT_ID);

        // Then
        assertThat(count).isEqualTo(3);
    }

    @Test
    @DisplayName("당첨자만 순위 순으로 조회할 수 있다")
    void givenWinners_whenFindWinners_thenReturnSortedByRank() {
        // Given
        for (int i = 1; i <= 3; i++) {
            Participant participant = Participant.builder()
                    .participantId("prt_20250124_" + String.format("%03d", i))
                    .eventId(VALID_EVENT_ID)
                    .name("당첨자" + i)
                    .phoneNumber("010-1234-" + String.format("%04d", i))
                    .email("winner" + i + "@test.com")
                    .storeVisited(true)
                    .bonusEntries(2)
                    .agreeMarketing(true)
                    .agreePrivacy(true)
                    .isWinner(true)
                    .build();
            participant.markAsWinner(4 - i); // 역순으로 순위 부여
            participantRepository.save(participant);
        }
        Pageable pageable = PageRequest.of(0, 10);

        // When
        Page<Participant> page = participantRepository
                .findByEventIdAndIsWinnerTrueOrderByWinnerRankAsc(VALID_EVENT_ID, pageable);

        // Then
        assertThat(page.getContent()).hasSize(3);
        assertThat(page.getContent().get(0).getWinnerRank()).isEqualTo(1);
        assertThat(page.getContent().get(1).getWinnerRank()).isEqualTo(2);
        assertThat(page.getContent().get(2).getWinnerRank()).isEqualTo(3);
    }

    @Test
    @DisplayName("이벤트 ID로 당첨자 수를 조회할 수 있다")
    void givenWinners_whenCountWinners_thenReturnCount() {
        // Given
        for (int i = 1; i <= 5; i++) {
            Participant participant = Participant.builder()
                    .participantId("prt_20250124_" + String.format("%03d", i))
                    .eventId(VALID_EVENT_ID)
                    .name("참여자" + i)
                    .phoneNumber("010-1234-" + String.format("%04d", i))
                    .email("test" + i + "@test.com")
                    .storeVisited(true)
                    .bonusEntries(2)
                    .agreeMarketing(true)
                    .agreePrivacy(true)
                    .isWinner(i <= 2)
                    .build();
            if (i <= 2) {
                participant.markAsWinner(i);
            }
            participantRepository.save(participant);
        }

        // When
        long count = participantRepository.countByEventIdAndIsWinnerTrue(VALID_EVENT_ID);

        // Then
        assertThat(count).isEqualTo(2);
    }

    @Test
    @DisplayName("이벤트 ID로 최대 ID를 조회할 수 있다")
    void givenParticipants_whenFindMaxId_thenReturnMaxId() {
        // Given
        for (int i = 1; i <= 3; i++) {
            Participant participant = Participant.builder()
                    .participantId("prt_20250124_" + String.format("%03d", i))
                    .eventId(VALID_EVENT_ID)
                    .name("참여자" + i)
                    .phoneNumber("010-1234-" + String.format("%04d", i))
                    .email("test" + i + "@test.com")
                    .storeVisited(true)
                    .bonusEntries(2)
                    .agreeMarketing(true)
                    .agreePrivacy(true)
                    .isWinner(false)
                    .build();
            participantRepository.save(participant);
        }

        // When
        Optional<Long> maxId = participantRepository.findMaxIdByEventId(VALID_EVENT_ID);

        // Then
        assertThat(maxId).isPresent();
        assertThat(maxId.get()).isGreaterThan(0);
    }

    @Test
    @DisplayName("비당첨자 목록만 조회할 수 있다")
    void givenMixedParticipants_whenFindNonWinners_thenReturnOnlyNonWinners() {
        // Given
        for (int i = 1; i <= 5; i++) {
            Participant participant = Participant.builder()
                    .participantId("prt_20250124_" + String.format("%03d", i))
                    .eventId(VALID_EVENT_ID)
                    .name("참여자" + i)
                    .phoneNumber("010-1234-" + String.format("%04d", i))
                    .email("test" + i + "@test.com")
                    .storeVisited(true)
                    .bonusEntries(2)
                    .agreeMarketing(true)
                    .agreePrivacy(true)
                    .isWinner(i <= 2)
                    .build();
            participantRepository.save(participant);
        }

        // When
        List<Participant> nonWinners = participantRepository.findByEventIdAndIsWinnerFalse(VALID_EVENT_ID);

        // Then
        assertThat(nonWinners).hasSize(3);
        assertThat(nonWinners).allMatch(p -> !p.getIsWinner());
    }

    @Test
    @DisplayName("이벤트 ID와 참여자 ID로 조회할 수 있다")
    void givenParticipant_whenFindByEventIdAndParticipantId_thenReturnParticipant() {
        // Given
        Participant participant = createValidParticipant();
        participantRepository.save(participant);

        // When
        Optional<Participant> found = participantRepository
                .findByEventIdAndParticipantId(VALID_EVENT_ID, participant.getParticipantId());

        // Then
        assertThat(found).isPresent();
        assertThat(found.get().getName()).isEqualTo(VALID_NAME);
    }

    // 헬퍼 메서드
    private Participant createValidParticipant() {
        return Participant.builder()
                .participantId("prt_20250124_001")
                .eventId(VALID_EVENT_ID)
                .name(VALID_NAME)
                .phoneNumber(VALID_PHONE)
                .email(VALID_EMAIL)
                .storeVisited(true)
                .bonusEntries(2)
                .agreeMarketing(true)
                .agreePrivacy(true)
                .isWinner(false)
                .build();
    }
}
