package org.fuseri.moduleexercise.question;

import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.persistence.EntityNotFoundException;
import org.fuseri.model.dto.exercise.AnswerDto;
import org.fuseri.model.dto.exercise.AnswerInQuestionCreateDto;
import org.fuseri.model.dto.exercise.QuestionCreateDto;
import org.fuseri.model.dto.exercise.QuestionDto;
import org.fuseri.model.dto.exercise.QuestionUpdateDto;
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.ArrayList;
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 QuestionControllerTest {

    @Autowired
    ObjectMapper objectMapper;
    @Autowired
    private MockMvc mockMvc;

    @MockBean
    QuestionFacade facade;

    private QuestionDto qston;

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

    @BeforeEach
    void init() {
        qston = new QuestionDto("\"what is the meaning of: costs an arm and leg\"", 1, new ArrayList<>());
    }

    @WithMockUser(authorities = {"SCOPE_test_1"})
    @Test
    void testCreateQuestion() throws Exception {
        var exerciseId = 1L;
        var question = new QuestionCreateDto("what is the meaning of: costs an arm and leg", exerciseId, List.of(new AnswerInQuestionCreateDto("dis very expencive", true), new AnswerInQuestionCreateDto("FMA refference", false)));
        when(facade.create(ArgumentMatchers.isA(QuestionCreateDto.class))).thenReturn(qston);
        var posted = mockMvc.perform(post("/questions").content(asJsonString(question)).contentType(MediaType.APPLICATION_JSON));

        posted.andExpect(status().isCreated())
                .andExpect(jsonPath("$.text", is(qston.getText())))
                .andExpect(jsonPath("$.exerciseId", is((int) qston.getExerciseId())));

    }

    @WithMockUser(authorities = {"SCOPE_test_1"})
    @Test
    void testCreateQuestionEmptyQuestions() throws Exception {
        var prompt = """
                {
                "text": "this is a question without exercise Id",
                "exerciseId": 1,
                "answers": []
                }
                """;

        var posted = mockMvc.perform(post("/questions").content(prompt).contentType(MediaType.APPLICATION_JSON));

        posted.andExpect(status().isCreated());
    }


    @WithMockUser(authorities = {"SCOPE_test_1"})
    @Test
    void testCreateQuestionEmptyField() throws Exception {
        var exerciseId = 1L;
        var question = new QuestionCreateDto("", exerciseId, List.of(new AnswerInQuestionCreateDto("dis very expencive", true), new AnswerInQuestionCreateDto("FMA refference", false)));
        var posted = mockMvc.perform(post("/questions").content(asJsonString(question)).contentType(MediaType.APPLICATION_JSON));

        posted.andExpect(status().is4xxClientError());
    }

    @WithMockUser(authorities = {"SCOPE_test_1"})
    @Test
    void testCreateQuestionMissingField() throws Exception {
        var prompt = """
                {
                "text": "this is a question without exercise Id,
                "answers" : []
                }
                """;

        var posted = mockMvc.perform(post("/questions").content(prompt).contentType(MediaType.APPLICATION_JSON));

        posted.andExpect(status().is4xxClientError());
    }


    @WithMockUser()
    @Test
    void getQuestion() throws Exception {
        var question = new QuestionDto("this statement is false", 1L, new ArrayList<>());
        when(facade.find(1)).thenReturn(question);
        var gets = mockMvc.perform(get("/questions/1"));
        gets.andExpect(status().isOk()).andExpect(jsonPath("$.text", is("this statement is false")));
    }

    @WithMockUser()
    @Test
    void getQuestionNotFound() throws Exception {
        when(facade.find(9999)).thenThrow(new EntityNotFoundException());
        var gets = mockMvc.perform(get("/questions/9999"));
        gets.andExpect(status().isNotFound());
    }


    @WithMockUser()
    @Test
    void getAnswer() throws Exception {
        var sss = List.of(new AnswerDto("February", false), new AnswerDto("All of them", true));
        when(facade.getQuestionAnswers(2)).thenReturn(sss);
        var gets = mockMvc.perform(get("/questions/2/answers"));
        gets.andExpect(status().isOk())
                .andExpect(jsonPath("$[0].text", is("February")))
                .andExpect(jsonPath("$[1].text", is("All of them")));


    }

    @WithMockUser(authorities = {"SCOPE_test_1"})
    @Test
    void testUpdate() throws Exception {
        var updated = """
                {
                  "text": "wat a paradox?",
                  "exerciseId": "1"
                }
                """;

        var question = new QuestionDto("wat a paradox?", 1, new ArrayList<>());

        when(facade.patchUpdate(ArgumentMatchers.eq(1), ArgumentMatchers.isA(QuestionUpdateDto.class))).thenReturn(question);
        var gets = mockMvc.perform(put("/questions/1").content(updated).contentType(MediaType.APPLICATION_JSON));

        gets.andExpect(status().isOk());
    }

    @WithMockUser(authorities = {"SCOPE_test_1"})
    @Test
    void deleteExisting() {
        try {
            mockMvc.perform(delete("/questions/1")).andExpect(status().isNoContent());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

}
