package dev.awesomedev.mirim.service;

import dev.awesomedev.mirim.domain.CodingProblem;
import dev.awesomedev.mirim.domain.CodingSubmission;
import dev.awesomedev.mirim.domain.User;
import dev.awesomedev.mirim.repository.CodingProblemRepository;
import dev.awesomedev.mirim.repository.CodingSubmissionRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.web.server.ResponseStatusException;

import java.util.Optional;

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

/**
 * 이 파일이 하는 일:
 * CodingService의 제출 규칙을 검증한다 — 특히 "언어별 채점 값 처리"가 핵심 규칙이다.
 *
 * 검증하는 규칙:
 *  (1) 자동채점 언어(JS·SQL)는 브라우저가 보낸 채점 값을 저장하고, 다 맞으면 PASSED.
 *  (2) 수동채점 언어(JAVA)는 브라우저가 뭘 보내오든 채점 값을 버린다 —
 *      조작된 "다 통과" 값이 성적처럼 저장되면 안 되니까.
 *  (3) 빈 코드는 400.
 */
class CodingServiceTest {

    private CodingProblemRepository codingProblemRepository;
    private CodingSubmissionRepository codingSubmissionRepository;
    private CodingService codingService;

    private User student;

    @BeforeEach
    void setUp() {
        codingProblemRepository = mock(CodingProblemRepository.class);
        codingSubmissionRepository = mock(CodingSubmissionRepository.class);
        codingService = new CodingService(codingProblemRepository, codingSubmissionRepository,
                mock(TelegramNotifier.class)); // 테스트에선 진짜 알림을 보내지 않는다
        student = new User("student1", "해시", "수습생1", "STUDENT", "DEV");
    }

    private CodingProblem problemOf(String language) {
        return new CodingProblem(language, "문제", "EASY", "topic", "설명", "뼈대", null, null, null, 0);
    }

    @Test
    @DisplayName("SQL 문제: 채점 값이 저장되고, 다 맞으면 상태가 PASSED가 된다")
    void submit_sql_keeps_grading_and_passes() {
        CodingProblem problem = problemOf("SQL");
        when(codingProblemRepository.findById(1L)).thenReturn(Optional.of(problem));
        when(codingSubmissionRepository.findByProblemAndUser(problem, student)).thenReturn(Optional.empty());
        when(codingSubmissionRepository.save(any())).thenAnswer(inv -> inv.getArgument(0));

        codingService.submit(student, 1L, "SELECT 1", 1, 1, "[{\"passed\":true}]");

        ArgumentCaptor<CodingSubmission> captor = ArgumentCaptor.forClass(CodingSubmission.class);
        verify(codingSubmissionRepository).save(captor.capture());
        CodingSubmission saved = captor.getValue();
        assertEquals(1, saved.getPassedCount());
        assertEquals("PASSED", saved.getStatus());
    }

    @Test
    @DisplayName("JAVA 문제: 브라우저가 '다 통과'라고 보내와도 채점 값을 버린다 (조작 방지)")
    void submit_java_discards_grading() {
        CodingProblem problem = problemOf("JAVA");
        when(codingProblemRepository.findById(1L)).thenReturn(Optional.of(problem));
        when(codingSubmissionRepository.findByProblemAndUser(problem, student)).thenReturn(Optional.empty());
        when(codingSubmissionRepository.save(any())).thenAnswer(inv -> inv.getArgument(0));

        // 개발자도구로 요청을 조작해 "5/5 통과"라고 보낸 상황
        codingService.submit(student, 1L, "class Solution {}", 5, 5, "[]");

        ArgumentCaptor<CodingSubmission> captor = ArgumentCaptor.forClass(CodingSubmission.class);
        verify(codingSubmissionRepository).save(captor.capture());
        CodingSubmission saved = captor.getValue();
        assertNull(saved.getPassedCount());   // 채점 값은 버려졌다
        assertEquals("SUBMITTED", saved.getStatus()); // PASSED가 아니라 멘토 확인 대기
    }

    @Test
    @DisplayName("빈 코드를 제출하면 400을 던진다")
    void submit_rejects_blank_code() {
        ResponseStatusException ex = assertThrows(ResponseStatusException.class,
                () -> codingService.submit(student, 1L, "  ", null, null, null));

        assertEquals(400, ex.getStatusCode().value());
        verify(codingSubmissionRepository, never()).save(any());
    }
}
