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.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.server.ResponseStatusException;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

/**
 * 이 파일이 하는 일:
 * 퀴즈의 비즈니스 로직 — 문항 조회, 채점(+기록 저장), 내 결과 요약.
 */
@Service
public class QuizService {

    private final QuizQuestionRepository quizQuestionRepository;
    private final QuizResultRepository quizResultRepository;

    public QuizService(QuizQuestionRepository quizQuestionRepository,
                       QuizResultRepository quizResultRepository) {
        this.quizQuestionRepository = quizQuestionRepository;
        this.quizResultRepository = quizResultRepository;
    }

    /** 코스의 문항 목록 (정답 포함 — 정답을 뺄 책임은 컨트롤러의 DTO 변환에 있다) */
    @Transactional(readOnly = true)
    public List<QuizQuestion> questionsFor(String courseSlug) {
        return quizQuestionRepository.findByCourseSlugOrderBySortOrderAsc(courseSlug);
    }

    /** 채점 결과 한 문항분 — 프론트가 정답·해설을 채점 후에만 받도록 여기 담아 준다 */
    public record GradedQuestion(Long questionId, Integer chosenIndex, Integer correctIndex,
                                 boolean correct, String explanation) {
    }

    /** 전체 채점 결과 */
    public record GradeResult(int score, int total, List<GradedQuestion> results) {
    }

    /**
     * 제출된 답안을 채점하고 응시 기록을 저장한다.
     *
     * 학습 포인트: answers는 {문항id: 고른 보기 번호} 형태의 Map이다.
     * 안 푼 문항(맵에 없음)은 오답 처리한다. 채점 기준(정답)은 서버에만 있으므로
     * 브라우저에서 아무리 요청을 조작해도 점수를 속일 수 없다 — 단, 여러 번
     * 다시 풀 수는 있다(학습용이니까 그건 기능이다!).
     */
    @Transactional
    public GradeResult grade(User user, String courseSlug, Map<Long, Integer> answers) {
        List<QuizQuestion> questions = questionsFor(courseSlug);
        if (questions.isEmpty()) {
            throw new ResponseStatusException(HttpStatus.NOT_FOUND, "이 코스에는 퀴즈가 없습니다.");
        }

        int score = 0;
        List<GradedQuestion> graded = new ArrayList<>();
        for (QuizQuestion question : questions) {
            Integer chosen = answers == null ? null : answers.get(question.getId());
            boolean correct = chosen != null && chosen.equals(question.getAnswerIndex());
            if (correct) {
                score++;
            }
            graded.add(new GradedQuestion(question.getId(), chosen,
                    question.getAnswerIndex(), correct, question.getExplanation()));
        }

        quizResultRepository.save(new QuizResult(user, courseSlug, score, questions.size()));
        return new GradeResult(score, questions.size(), graded);
    }

    /** 코스별 요약 한 줄 — 최고 점수, 응시 횟수 */
    public record ResultSummary(String courseSlug, int bestScore, int total, int attempts) {
    }

    /**
     * 이 사용자의 코스별 퀴즈 결과 요약.
     * 학습 포인트: 응시 기록(원본)에서 코스별 최고점·횟수를 "조회 시점에" 계산한다.
     * 요약을 따로 저장하면 원본과 어긋날 위험이 생긴다 — 계산할 수 있는 값은 계산한다.
     */
    @Transactional(readOnly = true)
    public List<ResultSummary> summaryFor(User user) {
        // 리포지토리가 최신순(submittedAt 내림차순)으로 준다 — 각 코스 목록의 첫 건이 가장 최근 응시다.
        Map<String, List<QuizResult>> bySlug = new TreeMap<>();
        for (QuizResult result : quizResultRepository.findByUserOrderBySubmittedAtDesc(user)) {
            bySlug.computeIfAbsent(result.getCourseSlug(), key -> new ArrayList<>()).add(result);
        }
        List<ResultSummary> summaries = new ArrayList<>();
        for (Map.Entry<String, List<QuizResult>> entry : bySlug.entrySet()) {
            List<QuizResult> attempts = entry.getValue();

            int best = 0;
            for (QuizResult result : attempts) {
                if (result.getScore() > best) {
                    best = result.getScore();
                }
            }
            // 분모(총 문항 수)는 가장 최근 응시 기준 — 문항이 나중에 늘거나 줄어도
            // "최근에 푼 시험"의 만점 기준을 쓰는 것이 자연스럽다.
            // 학습 포인트: 최신순 정렬이 보장되므로 get(0)이 곧 가장 최근이다.
            // 정렬을 리포지토리 메서드 이름으로 선언했기 때문에 이 가정이 안전하다.
            int total = attempts.get(0).getTotal();

            summaries.add(new ResultSummary(entry.getKey(), best, total, attempts.size()));
        }
        return summaries;
    }
}
