package org.fuseri.moduleexercise.answer;

import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.persistence.EntityNotFoundException;
import org.fuseri.model.dto.exercise.AnswerCreateDto;
import org.fuseri.model.dto.exercise.AnswerDto;
import org.fuseri.model.dto.exercise.AnswerInQuestionCreateDto;
import org.fuseri.model.dto.exercise.AnswersCreateDto;
import org.fuseri.model.dto.exercise.ExerciseCreateDto;
import org.fuseri.model.dto.exercise.QuestionCreateDto;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentMatchers;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.web.servlet.MockMvc;

import java.util.List;

import static org.hamcrest.Matchers.is;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@SpringBootTest
@AutoConfigureMockMvc
public class AnswerControllerTest {

    @Autowired
    ObjectMapper objectMapper;

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private AnswerFacade answerFacade;
    long exerciseId = 1L;
    ExerciseCreateDto exercise;
    QuestionCreateDto question1;
    QuestionCreateDto question2;

    public static String asJsonString(final Object obj) {
        try {
            return new ObjectMapper().writeValueAsString(obj);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @BeforeEach
    void init() {
        exercise = new ExerciseCreateDto("idioms", "exercise on basic idioms", 2, 0);
        question1 = new QuestionCreateDto("this statement is false", exerciseId,
                List.of(new AnswerInQuestionCreateDto("dis a logical paradox", true)));
        question2 = new QuestionCreateDto("What month of the year has 28 days?", exerciseId,
                List.of(new AnswerInQuestionCreateDto("February", false),
                        new AnswerInQuestionCreateDto("All of them", true)));
    }

    @WithMockUser(authorities = {"SCOPE_test_1"})
    @Test
    void testCreateAnswer() throws Exception {
        var answerCreateDto = new AnswerCreateDto("BA", true, 1);
        var answerDto = new AnswerDto("BA", true);
        when(answerFacade.create(ArgumentMatchers.isA(AnswerCreateDto.class))).thenReturn(answerDto);

        mockMvc.perform(post("/answers")
                        .content(asJsonString(answerCreateDto))
                        .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().isCreated())
                .andExpect(jsonPath("$.text", is("BA")))
                .andExpect(jsonPath("$.correct", is(true)));
    }

    @WithMockUser(authorities = {"SCOPE_test_1"})
    @Test
    void testCreateAnswerEmptyText() throws Exception {
        var incorrect1 = new AnswerInQuestionCreateDto("", false);
        var createAnswer = new AnswersCreateDto(1, List.of(incorrect1));

        mockMvc.perform(post("/answers")
                        .content(asJsonString(createAnswer))
                        .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().is4xxClientError());
    }

    @WithMockUser(authorities = {"SCOPE_test_1"})
    @Test
    void testCreateAnswerMissingText() throws Exception {
        var prompt = """
                {
                "questionId": 1,
                "answers": [
                    {
                    "text": "something",
                    "correct": false
                    }
                ]
                }
                """;

        mockMvc.perform(post("/answers")
                        .content(prompt)
                        .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().is4xxClientError());
    }

    @WithMockUser(authorities = {"SCOPE_test_1"})
    @Test
    void testCreateAnswerMissingCorrect() throws Exception {

        var prompt = """
                {
                "questionId": 1,
                "answers": [
                    {
                    "text": "something"
                    }
                ]
                }
                """;

        mockMvc.perform(post("/answers")
                        .content(prompt)
                        .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().is4xxClientError());
    }

    @WithMockUser(authorities = {"SCOPE_test_1"})
    @Test
    void testUpdate() throws Exception {
        long id = 1L;
        var updated = new AnswerDto("dis true", false);
        when(answerFacade.update(ArgumentMatchers.eq(id), ArgumentMatchers.isA(AnswerCreateDto.class))).thenReturn(updated);

        mockMvc.perform(put("/answers/{id}", id)
                        .content(asJsonString(updated))
                        .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.text", is("dis true")))
                .andExpect(jsonPath("$.correct", is(false)));
    }

    @WithMockUser(authorities = {"SCOPE_test_1"})
    @Test
    void testUpdateNotFoundAnswer() throws Exception {
        long id = 1L;
        var toUpdate = new AnswerCreateDto("dis true", false, 1);
        when(answerFacade.update(ArgumentMatchers.eq(id), ArgumentMatchers.isA(AnswerCreateDto.class))).thenThrow(new EntityNotFoundException());
        mockMvc.perform(put("/answers/{id}", id)
                        .content(asJsonString(toUpdate)).contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().isNotFound());
    }

    @WithMockUser(authorities = {"SCOPE_test_1"})
    @Test
    void testUpdateEmptyText() throws Exception {
        var updated = new AnswerCreateDto("", false, 1);
        mockMvc.perform(put("/answers/1")
                        .content(asJsonString(updated)).contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().is4xxClientError());
    }

    @WithMockUser(authorities = {"SCOPE_test_1"})
    @Test
    void testUpdateMissingField() throws Exception {
        var updated = """
                {
                "correct": false,
                "questionId": 1
                }
                """;

        mockMvc.perform(put("/answers/1")
                        .content(updated).contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().is4xxClientError());
    }

    @WithMockUser(authorities = {"SCOPE_test_1"})
    @Test
    void testDelete() throws Exception {
        mockMvc.perform(delete("/answers/1"))
                .andExpect(status().is2xxSuccessful());
    }
}
