package org.fuseri.modulelanguageschool.course;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.fuseri.model.dto.course.CourseCreateDto;
import org.fuseri.model.dto.course.CourseDto;
import org.fuseri.model.dto.course.LanguageTypeDto;
import org.fuseri.model.dto.course.ProficiencyLevelDto;
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.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
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.HashMap;
import java.util.List;

import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
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 CourseControllerTest {

    private final CourseCreateDto courseCreateDto = new CourseCreateDto("english b2 course", 10, LanguageTypeDto.ENGLISH, ProficiencyLevelDto.B2);
    private final CourseDto courseDto = new CourseDto("english b2 course", 10, LanguageTypeDto.ENGLISH, ProficiencyLevelDto.B2);

    @Autowired
    ObjectMapper objectMapper;

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private CourseFacade courseFacade;

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

    @WithMockUser(authorities = {"SCOPE_test_1"})
    @Test
    void createCourse() throws Exception {
        Mockito.when(courseFacade.create(ArgumentMatchers.isA(CourseCreateDto.class))).thenReturn(courseDto);
        mockMvc.perform(post("/courses")
                        .content(asJsonString(courseCreateDto))
                        .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().is2xxSuccessful())
                .andExpect(jsonPath("$.name").value("english b2 course"))
                .andExpect(jsonPath("$.capacity").value(10))
                .andExpect(jsonPath("$.language").value("ENGLISH"))
                .andExpect(jsonPath("$.proficiency").value("B2"));
    }

    @WithMockUser(authorities = {"SCOPE_test_1"})
    @Test
    void createInvalidCourse() throws Exception {
        CourseCreateDto invalidCourseCreateDto =
                new CourseCreateDto(null, null, null, null);
        mockMvc.perform(post("/courses")
                        .content(asJsonString(invalidCourseCreateDto))
                        .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().is4xxClientError());
    }

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


    @WithMockUser(authorities = {"SCOPE_test_1"})
    @Test
    void findCourse() throws Exception {
        Long id = 0L;
        Mockito.when(courseFacade.findById(id)).thenReturn(courseDto);
        mockMvc.perform(get("/courses/find/" + id))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.name").value("english b2 course"))
                .andExpect(jsonPath("$.capacity").value(10))
                .andExpect(jsonPath("$.language").value("ENGLISH"))
                .andExpect(jsonPath("$.proficiency").value("B2"))
                .andReturn().getResponse().getContentAsString();
    }

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

    @WithMockUser(authorities = {"SCOPE_test_1"})
    @Test
    void findAll() throws Exception {
        Mockito.when(courseFacade.findAll((PageRequest) any()))
                .thenReturn(new PageImpl<>(new ArrayList<>()));
        String response = mockMvc.perform(get("/courses/findAll?page=0"))
                .andExpect(status().is2xxSuccessful()).andReturn().getResponse().getContentAsString();
        assertTrue(response.contains("\"content\":[]"));
    }

    @WithMockUser(authorities = {"SCOPE_test_1"})
    @Test
    void findAllWithoutPage() throws Exception {
        mockMvc.perform(get("/courses/findAll"))
                .andExpect(status().is4xxClientError());
    }


    @WithMockUser(authorities = {"SCOPE_test_1"})
    @Test
    void findAllByLang() throws Exception {
        int page = 0;
        LanguageTypeDto lang = LanguageTypeDto.ENGLISH;
        Mockito.when(courseFacade.findAll(lang)).thenReturn(new ArrayList<>());
        String response = mockMvc.perform(get("/courses/findAllByLang")
                        .param("page", Integer.toString(page))
                        .param("lang", lang.toString()))
                .andExpect(status().isOk())
                .andReturn().getResponse().getContentAsString();
        assertTrue(response.endsWith("[]"));
    }

    @WithMockUser(authorities = {"SCOPE_test_1"})
    @Test
    void findAllByLangWithoutLang() throws Exception {
        mockMvc.perform(get("/courses/findAllByLang"))
                .andExpect(status().is4xxClientError());
    }

    @WithMockUser(authorities = {"SCOPE_test_1"})
    @Test
    void findAllByLangProf() throws Exception {
        LanguageTypeDto lang = LanguageTypeDto.ENGLISH;
        ProficiencyLevelDto proficiencyLevel = ProficiencyLevelDto.A1;
        Mockito.when(courseFacade.findAll(lang, proficiencyLevel)).thenReturn(new ArrayList<>());
        String response = mockMvc.perform(get("/courses/findAllByLangProf")
                        .param("lang", lang.toString())
                        .param("prof", proficiencyLevel.toString()))
                .andExpect(status().isOk())
                .andReturn().getResponse().getContentAsString();
        assertTrue(response.endsWith("[]"));
    }

    @WithMockUser(authorities = {"SCOPE_test_1"})
    @Test
    void findAllByLangProfWithoutParameters() throws Exception {
        mockMvc.perform(get("/courses/findAllByLangProf"))
                .andExpect(status().is4xxClientError());
    }

    @WithMockUser(authorities = {"SCOPE_test_1"})
    @Test
    void findAllByLangProfWithoutLangProf() throws Exception {
        String page = "0";
        mockMvc.perform(get("/courses/findAllByLangProf").param("page", page))
                .andExpect(status().is4xxClientError());
    }

    @WithMockUser(authorities = {"SCOPE_test_1"})
    @Test
    void updateCourse() throws Exception {
        Long id = 0L;
        Mockito.when(courseFacade.update(ArgumentMatchers.eq(id),
                        ArgumentMatchers.isA(CourseCreateDto.class)))
                .thenReturn(courseDto);

        mockMvc.perform(put("/courses/update/" + id)
                        .content(asJsonString(courseDto))
                        .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.name").value("english b2 course"))
                .andExpect(jsonPath("$.capacity").value(10))
                .andExpect(jsonPath("$.language").value("ENGLISH"))
                .andExpect(jsonPath("$.proficiency").value("B2"))
                .andExpect(jsonPath("$.id").value(courseDto.getId()));
    }

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

    @WithMockUser(authorities = {"SCOPE_test_1"})
    @Test
    void deleteCourse() throws Exception {
        Long id = 0L;
        Mockito.doNothing().when(courseFacade).delete(id);

        mockMvc.perform(delete("/courses/delete/" + id))
                .andExpect(status().is2xxSuccessful());
    }

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

    @WithMockUser(authorities = {"SCOPE_test_1"})
    @Test
    void enrolCourse() throws Exception {
        long id = 0L;
        UserDto student = new UserDto("novakovat", "novakova@gamil.com", "Tereza",
                "Nováková",new HashMap<>());
        student.setId(1L);

        CourseDto courseDtoWithStudent = new CourseDto("english b2 course", 10, LanguageTypeDto.ENGLISH, ProficiencyLevelDto.B2);
        courseDtoWithStudent.setStudents(new ArrayList<>(List.of(student.getId())));

        Mockito.when(courseFacade.enrol(ArgumentMatchers.anyLong(),
                ArgumentMatchers.anyLong())).thenReturn(courseDtoWithStudent);

        mockMvc.perform(patch("/courses/enrolStudent/" + id).param("studentId", String.valueOf(1L))
                        .content(asJsonString(student))
                        .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.name").value("english b2 course"))
                .andExpect(jsonPath("$.capacity").value(10))
                .andExpect(jsonPath("$.language").value("ENGLISH"))
                .andExpect(jsonPath("$.proficiency").value("B2"));
    }

    @WithMockUser(authorities = {"SCOPE_test_1"})
    @Test
    void enrolCourseWithoutUserParameter() throws Exception {
        mockMvc.perform(patch("/courses/enrolStudent/" + 0L))
                .andExpect(status().is4xxClientError());
    }

    @WithMockUser(authorities = {"SCOPE_test_1"})
    @Test
    void enrolCourseWithoutCourseIdParameter() throws Exception {
        UserDto student = new UserDto("novakovat", "novakova@gamil.com", "Tereza",
                "Nováková",new HashMap<>());

        mockMvc.perform(patch("/courses/enrol/")
                        .content(asJsonString(student))
                        .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().is4xxClientError());
    }

    @WithMockUser(authorities = {"SCOPE_test_1"})
    @Test
    void expelCourse() throws Exception {
        Long id = 0L;
        Mockito.when(courseFacade.expel(ArgumentMatchers.eq(id),
                        ArgumentMatchers.isA(Long.class)))
                .thenReturn(courseDto);

        UserDto student = new UserDto("novakovat", "novakova@gamil.com", "Tereza",
                "Nováková",new HashMap<>());

        mockMvc.perform(patch("/courses/expelStudent/" + id)
                        .param("studentId","0")
                        .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.name").value("english b2 course"))
                .andExpect(jsonPath("$.capacity").value(10))
                .andExpect(jsonPath("$.language").value("ENGLISH"))
                .andExpect(jsonPath("$.proficiency").value("B2"))
                .andExpect(jsonPath("$.students").isEmpty());
    }

    @WithMockUser(authorities = {"SCOPE_test_1"})
    @Test
    void expelCourseWithoutUserParameter() throws Exception {
        mockMvc.perform(patch("/courses/expelStudent/" + 0L))
                .andExpect(status().is4xxClientError());
    }

    @WithMockUser(authorities = {"SCOPE_test_1"})
    @Test
    void deleteCourseWithoutCourseIdParameter() throws Exception {
        UserDto student = new UserDto("novakovat", "novakova@gamil.com", "Tereza",
                "Nováková",new HashMap<>());

        mockMvc.perform(patch("/courses/expel/")
                        .content(asJsonString(student))
                        .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().is4xxClientError());
    }
}
