package org.fuseri.moduleexercise.exercise;

import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.persistence.EntityNotFoundException;
import org.fuseri.model.dto.exercise.ExerciseCreateDto;
import org.fuseri.model.dto.exercise.ExerciseDto;
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.data.domain.PageImpl;
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 static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertTrue;
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 ExerciseControllerTest {

    @Autowired
    ObjectMapper objectMapper;

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    ExerciseFacade facade;

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

    private ExerciseDto exerciseDto;
    private ExerciseCreateDto exerciseCreateDto;

    @BeforeEach
    void init() {
        exerciseDto = new ExerciseDto();
        exerciseDto.setName("idioms");
        exerciseDto.setDescription("exercise on basic idioms");
        exerciseDto.setDifficulty(2);
        exerciseDto.setCourseId(0);

        exerciseCreateDto = new ExerciseCreateDto("idioms", "exercise on basic idioms", 2, 0);
    }

    @WithMockUser()
    @Test
    void getExercise() throws Exception {
        long id = 1L;
        when(facade.find(id)).thenReturn(exerciseDto);
        mockMvc.perform(get("/exercises/{id}", id))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.name", is(exerciseCreateDto.getName())))
                .andExpect(jsonPath("$.description", is(exerciseCreateDto.getDescription())))
                .andExpect(jsonPath("$.courseId", is((int) exerciseCreateDto.getCourseId())))
                .andExpect(jsonPath("$.difficulty", is(exerciseCreateDto.getDifficulty())));
    }

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

    @WithMockUser()
    @Test
    void getExercise_notFound() throws Exception {
        long id = 1L;
        when(facade.find(id)).thenThrow(new EntityNotFoundException(""));
        mockMvc.perform(get("/exercises/{id}", id))
                .andExpect(status().isNotFound());
    }

    @WithMockUser()
    @Test
    void FindAll() {
        when(facade.findAll(0)).thenReturn(new PageImpl<>(new ArrayList<>()));
        try {
            var response = mockMvc.perform(get("/exercises").param("page", "0"));

            var dis = response.andExpect(status().isOk())
                    .andExpect(status().is2xxSuccessful()).andReturn().getResponse().getContentAsString();
            assertTrue(dis.contains("\"content\":[]"));
        } catch (Exception e) {
            throw new RuntimeException(e);
        }


    }

    @WithMockUser()
    @Test
    void getFiltered() {
        when(facade.findByCourseIdAndDifficulty(0, 2, 0)).thenReturn(new PageImpl<>(new ArrayList<>()));
        try {
            var filtered = mockMvc.perform(get("/exercises/filter").param("page", "0").param("courseId", "0").param("difficulty", "2"));
            var content = filtered.andReturn().getResponse().getContentAsString();

            assertTrue(content.contains("\"content\":[]"));
        } catch (Exception e) {
            assert (false);
        }
    }

    @WithMockUser(authorities = {"SCOPE_test_2"})
    @Test
    void testCreateExercise() throws Exception {
        when(facade.create(ArgumentMatchers.isA(ExerciseCreateDto.class))).thenReturn(exerciseDto);
        mockMvc.perform(post("/exercises").content(asJsonString(exerciseCreateDto)).contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().isCreated())
                .andExpect(jsonPath("$.name").value("idioms"))
                .andExpect(jsonPath("$.description").value("exercise on basic idioms"))
                .andExpect(jsonPath("$.difficulty").value(2))
                .andExpect(jsonPath("$.courseId").value("0")).andReturn().getResponse().getContentAsString();
    }

    @WithMockUser(authorities = {"SCOPE_test_2"})
    @Test
    void testCreateExerciseEmptyBody() throws Exception {
        var postExercise = "";
        mockMvc.perform(post("/exercises").content((postExercise)).contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().is4xxClientError()).andReturn().getResponse().getContentAsString();
    }


    @WithMockUser(authorities = {"SCOPE_test_1"})
    @Test
    void testCreateExerciseMissingDesc() throws Exception {
        var postExercise = """
                {
                  "name": "idioms",
                  "difficulty": 2,
                  "courseId": 0,
                }
                """;

        mockMvc.perform(post("/exercises").content((postExercise)).contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().is4xxClientError()).andReturn().getResponse().getContentAsString();
    }

    @WithMockUser(authorities = {"SCOPE_test_1"})
    @Test
    void testCreateExerciseMissingName() throws Exception {
        var postExercise = """
                {
                  "description: "exercise on basic idioms",
                  "difficulty": 2,
                  "courseId": 0,
                }
                """;

        mockMvc.perform(post("/exercises").content((postExercise)).contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().is4xxClientError()).andReturn().getResponse().getContentAsString();
    }

    @WithMockUser(authorities = {"SCOPE_test_1"})
    @Test
    void testCreateExerciseMissingDifficulty() throws Exception {
        var postExercise = """
                {
                  "name": "idioms"
                  "description: "exercise on basic idioms",
                  "courseId": 0
                }
                """;

        mockMvc.perform(post("/exercises").content((postExercise)).contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().is4xxClientError()).andReturn().getResponse().getContentAsString();
    }

    @WithMockUser(authorities = {"SCOPE_test_1"})
    @Test
    void testCreateExerciseMissingCourseId() throws Exception {
        var postExercise = """
                {
                  "name": "idioms"
                  "description: "exercise on basic idioms",
                  "difficulty": 0
                }
                """;

        mockMvc.perform(post("/exercises").content((postExercise)).contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().is4xxClientError()).andReturn().getResponse().getContentAsString();
    }


    @WithMockUser(authorities = {"SCOPE_test_1"})
    @Test
    void testUpdate() throws Exception {
        long id = 1L;
        when(facade.update(ArgumentMatchers.eq(id), ArgumentMatchers.isA(ExerciseCreateDto.class))).thenReturn(exerciseDto);

        var theId = String.format("/exercises/%d", id);
        var dis = mockMvc.perform(put(theId).content(asJsonString(exerciseCreateDto)).contentType(MediaType.APPLICATION_JSON));

        dis.andExpect(status().isOk()).andExpect(jsonPath("$.name", is(exerciseDto.getName())))
                .andExpect(jsonPath("$.difficulty", is(exerciseDto.getDifficulty())))
                .andExpect(jsonPath("$.description", is(exerciseDto.getDescription())));
    }

    @WithMockUser(authorities = {"SCOPE_test_1"})
    @Test
    void testUpdateNotFound() {
        long id = 9999L;
        when(facade.update(ArgumentMatchers.eq(id), ArgumentMatchers.isA(ExerciseCreateDto.class))).thenThrow(new EntityNotFoundException());

        try {
            var theId = String.format("/exercises/%d", id);
            mockMvc.perform(put(theId).content(asJsonString(exerciseCreateDto)).contentType(MediaType.APPLICATION_JSON))
                    .andExpect(status().isNotFound());

        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @WithMockUser(authorities = {"SCOPE_test_1"})
    @Test
    void testUpdateIncorrectBody() {
        long id = 1L;
        var content = """
                {
                  "description": "exercise on basic idioms",
                  "difficulty": 2,
                  "courseId": 0
                }
                """;
        try {
            var theId = String.format("/exercises/%d", id);
            mockMvc.perform(put(theId).content(content).contentType(MediaType.APPLICATION_JSON))
                    .andExpect(status().is4xxClientError());

        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

}
