package dev.awesomedev.mirim.service;

import dev.awesomedev.mirim.domain.QuizQuestion;
import dev.awesomedev.mirim.domain.QuizResult;
import dev.awesomedev.mirim.domain.User;
import dev.awesomedev.mirim.repository.QuizQuestionRepository;
import dev.awesomedev.mirim.repository.QuizResultRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.web.server.ResponseStatusException;

import java.util.List;
import java.util.Map;

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

/**
 * 이 파일이 하는 일:
 * QuizService의 채점 규칙을 검증한다. 특히 놓치기 쉬운 경계 규칙 —
 * "안 푼 문제는 오답", "정답은 서버 기준으로 채점", "응시 기록을 남긴다".
 *
 * 학습 포인트: 채점처럼 "규칙이 곧 제품"인 로직은 반드시 테스트로 못 박는다.
 * 나중에 코드를 고치다 실수로 규칙이 바뀌면, 이 테스트가 빨간불로 알려 준다.
 */
class QuizServiceTest {

    private QuizQuestionRepository quizQuestionRepository;
    private QuizResultRepository quizResultRepository;
    private QuizService quizService;

    private User student;

    @BeforeEach
    void setUp() {
        quizQuestionRepository = mock(QuizQuestionRepository.class);
        quizResultRepository = mock(QuizResultRepository.class);
        quizService = new QuizService(quizQuestionRepository, quizResultRepository);
        student = new User("student1", "해시", "수습생1", "STUDENT", "DEV");
    }

    /** 정답 인덱스가 answerIdx인 문항 하나. id를 리플렉션으로 넣는다(테스트용). */
    private QuizQuestion question(long id, int answerIdx) {
        QuizQuestion q = new QuizQuestion("hardware", "질문?", "보기1", "보기2", "보기3", "보기4",
                answerIdx, "해설", 0);
        ReflectionTestUtils.setField(q, "id", id);
        return q;
    }

    @Test
    @DisplayName("맞은 개수만큼 점수가 나오고, 안 푼 문제는 오답으로 처리한다")
    void grade_counts_correct_and_treats_unanswered_as_wrong() {
        // 3문항: 정답이 각각 0, 1, 2
        List<QuizQuestion> questions = List.of(question(1, 0), question(2, 1), question(3, 2));
        when(quizQuestionRepository.findByCourseSlugOrderBySortOrderAsc("hardware"))
                .thenReturn(questions);

        // 학생 답: 1번은 정답(0), 2번은 오답(3), 3번은 아예 안 풂(맵에 없음)
        Map<Long, Integer> answers = Map.of(1L, 0, 2L, 3);

        QuizService.GradeResult result = quizService.grade(student, "hardware", answers);

        assertEquals(1, result.score());   // 맞은 건 1번 하나
        assertEquals(3, result.total());
        // 안 푼 3번은 chosenIndex가 null이고 오답 처리
        QuizService.GradedQuestion q3 = result.results().get(2);
        assertNull(q3.chosenIndex());
        assertFalse(q3.correct());
        // 응시 기록을 남겼는지 (다시 풀기·성적 집계의 근거)
        verify(quizResultRepository).save(any(QuizResult.class));
    }

    @Test
    @DisplayName("답을 하나도 안 보내도(null) 터지지 않고 전부 오답으로 채점한다")
    void grade_handles_null_answers() {
        when(quizQuestionRepository.findByCourseSlugOrderBySortOrderAsc("hardware"))
                .thenReturn(List.of(question(1, 0), question(2, 1)));

        QuizService.GradeResult result = quizService.grade(student, "hardware", null);

        assertEquals(0, result.score());
        assertEquals(2, result.total());
    }

    @Test
    @DisplayName("퀴즈가 없는 코스를 채점하려 하면 404를 던진다")
    void grade_throws_404_when_no_quiz() {
        when(quizQuestionRepository.findByCourseSlugOrderBySortOrderAsc("nope"))
                .thenReturn(List.of());

        ResponseStatusException ex = assertThrows(ResponseStatusException.class,
                () -> quizService.grade(student, "nope", Map.of()));

        assertEquals(404, ex.getStatusCode().value());
        verify(quizResultRepository, never()).save(any());
    }
}
