package org.fuseri.modulelanguageschool.lecture;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.fuseri.model.dto.lecture.LectureCreateDto;
import org.fuseri.model.dto.lecture.LectureDto;
import org.fuseri.model.dto.user.UserDto;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentMatchers;
import org.mockito.Mockito;
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.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;

import static org.junit.jupiter.api.Assertions.assertTrue;
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 LectureControllerTest {

    private final LocalDateTime now = LocalDateTime.now();
    private final LectureCreateDto lectureCreateDto = new LectureCreateDto(
            now.plusDays(2),
            now.plusDays(2).plusHours(2),
            "Learning how to spell deprecated",
            10, 0L);
    private final LectureDto lectureDto = new LectureDto(
            now.plusDays(2),
            now.plusDays(2).plusHours(2),
            "Learning how to spell deprecated",
            10, 0L, 0L, Collections.emptyList());

    @Autowired
    ObjectMapper objectMapper;

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private LectureFacade lectureFacade;

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

    @WithMockUser(authorities = {"SCOPE_test_1"})
    @Test
    void createLecture() throws Exception {
        Mockito.when(lectureFacade.create(ArgumentMatchers.isA(LectureCreateDto.class))).thenReturn(lectureDto);
        mockMvc.perform(post("/lectures")
                        .content(asJsonString(lectureCreateDto))
                        .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().is2xxSuccessful())
                .andExpect(jsonPath("$.lectureFrom").isNotEmpty())
                .andExpect(jsonPath("$.lectureTo").isNotEmpty())
                .andExpect(jsonPath("$.topic").value("Learning how to spell deprecated"))
                .andExpect(jsonPath("$.capacity").value("10"))
                .andExpect(jsonPath("$.lecturerId").value("0"))
                .andExpect(jsonPath("$.courseId").value("0"));
    }

    @WithMockUser(authorities = {"SCOPE_test_1"})
    @Test
    void createInvalidLecture() throws Exception {
        LectureCreateDto invalidLectureCreateDto =
                new LectureCreateDto(null, null, null, null, null);
        mockMvc.perform(post("/lectures")
                        .content(asJsonString(invalidLectureCreateDto))
                        .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().is4xxClientError());
    }

    @WithMockUser(authorities = {"SCOPE_test_1"})
    @Test
    void createLectureWithoutParameter() throws Exception {
        mockMvc.perform(post("/lectures"))
                .andExpect(status().is4xxClientError());
    }

    @WithMockUser(authorities = {"SCOPE_test_1"})
    @Test
    void findLecture() throws Exception {
        Long id = 0L;
        Mockito.when(lectureFacade.findById(id)).thenReturn(lectureDto);
        mockMvc.perform(get("/lectures/find/" + id))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.lectureFrom").isNotEmpty())
                .andExpect(jsonPath("$.lectureTo").isNotEmpty())
                .andExpect(jsonPath("$.topic").value("Learning how to spell deprecated"))
                .andExpect(jsonPath("$.capacity").value("10"))
                .andExpect(jsonPath("$.lecturerId").value("0"))
                .andExpect(jsonPath("$.courseId").value("0"));
    }

    @WithMockUser(authorities = {"SCOPE_test_1"})
    @Test
    void findLectureWithoutId() throws Exception {
        mockMvc.perform(get("/lectures/find/"))
                .andExpect(status().is4xxClientError());
    }

    @WithMockUser(authorities = {"SCOPE_test_1"})
    @Test
    void findLecturesByCourse() throws Exception {
        Long id = 0L;
        Mockito.when(lectureFacade.findAll(id)).thenReturn(new ArrayList<>());
        String response = mockMvc.perform(get("/lectures/findByCourse")
                        .param("courseId", String.valueOf(id)))
                .andExpect(status().isOk())
                .andReturn().getResponse().getContentAsString();
        assertTrue(response.endsWith("[]"));
    }

    //TODO: FIX NO PARAM BY COURSE
    @WithMockUser(authorities = {"SCOPE_test_1"})
    @Test
    void findLecturesByCourseWithoutParameter() throws Exception {
        mockMvc.perform(get("/lectures/findByCourse"))
                .andExpect(status().is4xxClientError());
    }

    @WithMockUser(authorities = {"SCOPE_test_1"})
    @Test
    void updateLecture() throws Exception {
        Long id = 0L;
        Mockito.when(lectureFacade.update(ArgumentMatchers.eq(id),
                        ArgumentMatchers.isA(LectureCreateDto.class)))
                .thenReturn(lectureDto);
        mockMvc.perform(put("/lectures/update/" + id)
                        .content(asJsonString(lectureCreateDto))
                        .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.lectureFrom").isNotEmpty())
                .andExpect(jsonPath("$.lectureTo").isNotEmpty())
                .andExpect(jsonPath("$.topic").value("Learning how to spell deprecated"))
                .andExpect(jsonPath("$.capacity").value("10"))
                .andExpect(jsonPath("$.lecturerId").value("0"))
                .andExpect(jsonPath("$.courseId").value("0"));
    }

    @WithMockUser(authorities = {"SCOPE_test_1"})
    @Test
    void updateLectureWithoutParameter() throws Exception {
        mockMvc.perform(put("/lectures/update"))
                .andExpect(status().is4xxClientError());
    }

    @WithMockUser(authorities = {"SCOPE_test_1"})
    @Test
    void deleteLecture() throws Exception {
        Long id = 0L;
        Mockito.doNothing().when(lectureFacade).delete(id);
        mockMvc.perform(delete("/lectures/delete/" + id))
                .andExpect(status().is2xxSuccessful());
    }

    @WithMockUser(authorities = {"SCOPE_test_1"})
    @Test
    void deleteCourseWithoutParameter() throws Exception {
        mockMvc.perform(delete("/lectures/delete/"))
                .andExpect(status().is4xxClientError());
    }

    @WithMockUser(authorities = {"SCOPE_test_1"})
    @Test
    void setLecturerForLecture() throws Exception {
        Long id = 0L;
        Mockito.when(lectureFacade.setLecturer(ArgumentMatchers.eq(id),
                        ArgumentMatchers.isA(Long.class)))
                .thenReturn(lectureDto);
        UserDto student = new UserDto("novakovat", "novakova@gamil.com", "Tereza",
                "Nováková",new HashMap<>());
        mockMvc.perform(patch("/lectures/setLecturer/" + id)
                        .param("lecturerId","0")
                        .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.lectureFrom").isNotEmpty())
                .andExpect(jsonPath("$.lectureTo").isNotEmpty())
                .andExpect(jsonPath("$.topic").value("Learning how to spell deprecated"))
                .andExpect(jsonPath("$.capacity").value("10"))
                .andExpect(jsonPath("$.lecturerId").value("0"))
                .andExpect(jsonPath("$.courseId").value("0"));
    }

    @WithMockUser(authorities = {"SCOPE_test_1"})
    @Test
    void setLecturerForLectureWithoutParameters() throws Exception {
        mockMvc.perform(patch("/lectures/setLecturer"))
                .andExpect(status().is4xxClientError());
    }

    @WithMockUser(authorities = {"SCOPE_test_1"})
    @Test
    void enrolLecture() throws Exception {
        Long id = 0L;
        Mockito.when(lectureFacade.enrol(ArgumentMatchers.eq(id),
                        ArgumentMatchers.isA(Long.class)))
                .thenReturn(lectureDto);
        UserDto student = new UserDto("novakovat", "novakova@gamil.com", "Tereza",
                "Nováková",new HashMap<>());
        mockMvc.perform(patch("/lectures/enrolStudent/{id}", id)
                        .param("studentId","0")
                        .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.lectureFrom").isNotEmpty())
                .andExpect(jsonPath("$.lectureTo").isNotEmpty())
                .andExpect(jsonPath("$.topic").value("Learning how to spell deprecated"))
                .andExpect(jsonPath("$.capacity").value("10"))
                .andExpect(jsonPath("$.lecturerId").value("0"))
                .andExpect(jsonPath("$.courseId").value("0"));
    }

    @WithMockUser(authorities = {"SCOPE_test_1"})
    @Test
    void enrolCourseWithoutUserParameters() throws Exception {
        mockMvc.perform(patch("/lectures/enrol"))
                .andExpect(status().is4xxClientError());
    }

    @WithMockUser(authorities = {"SCOPE_test_1"})
    @Test
    void expelLecture() throws Exception {
        Long id = 0L;
        Mockito.when(lectureFacade.expel(ArgumentMatchers.eq(id),
                        ArgumentMatchers.isA(Long.class)))
                .thenReturn(lectureDto);
        UserDto student = new UserDto("novakovat", "novakova@gamil.com", "Tereza",
                "Nováková",new HashMap<>());
        mockMvc.perform(patch("/lectures/expelStudent/" + id)
                        .param("studentId","0")
                        .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.lectureFrom").isNotEmpty())
                .andExpect(jsonPath("$.lectureTo").isNotEmpty())
                .andExpect(jsonPath("$.topic").value("Learning how to spell deprecated"))
                .andExpect(jsonPath("$.capacity").value("10"))
                .andExpect(jsonPath("$.lecturerId").value("0"))
                .andExpect(jsonPath("$.courseId").value("0"));
    }

    @WithMockUser(authorities = {"SCOPE_test_1"})
    @Test
    void expelCourseWithoutUserParameters() throws Exception {
        mockMvc.perform(patch("/lectures/expel"))
                .andExpect(status().is4xxClientError());
    }

}
