package org.fuseri.modulecertificate;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.fuseri.model.dto.certificate.CertificateCreateDto;
import org.fuseri.model.dto.certificate.CertificateGenerateDto;
import org.fuseri.model.dto.certificate.CertificateSimpleDto;
import org.fuseri.model.dto.course.CourseCertificateDto;
import org.fuseri.model.dto.course.LanguageTypeDto;
import org.fuseri.model.dto.course.ProficiencyLevelDto;
import org.fuseri.model.dto.user.UserDto;
import org.fuseri.modulecertificate.certificate.CertificateFacade;
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.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.http.MediaType;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.web.servlet.MockMvc;

import java.time.Instant;
import java.util.HashMap;
import java.util.List;

import static org.mockito.ArgumentMatchers.any;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;


@SpringBootTest
@AutoConfigureMockMvc
class CertificateControllerTests {

    private final UserDto USER = new UserDto("novakovat",
            "novakova@gamil.com", "Tereza", "Nováková",new HashMap<>());
    private final CourseCertificateDto COURSE = new CourseCertificateDto("AJ1", 10,
            LanguageTypeDto.ENGLISH, ProficiencyLevelDto.A1);
    private final CertificateCreateDto certificateCreateDto = new CertificateCreateDto(USER, COURSE);
    private final CertificateSimpleDto certificateDto = new CertificateSimpleDto(0L, USER.getId(),
            Instant.now(), COURSE.getId(), "");
    private final CertificateGenerateDto certificateGenerateDto = new CertificateGenerateDto(0L,USER.getId(),
            Instant.now(), COURSE.getId(), "");

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private CertificateFacade certificateFacade;

    private 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 generateCertificate() throws Exception {
        Mockito.when(certificateFacade.generate(any(CertificateCreateDto.class), any()))
                .thenReturn(certificateGenerateDto);

        mockMvc.perform(post("/certificates")
                        .content(asJsonString(certificateCreateDto))
                        .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().is2xxSuccessful());
    }

    @WithMockUser(authorities = {"SCOPE_test_1"})
    @Test
    void generateCertificateWithNullUser() throws Exception {
        mockMvc.perform(post("/certificates")
                        .content(asJsonString(new CertificateCreateDto(null, COURSE)))
                        .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().is4xxClientError());
    }

    @WithMockUser(authorities = {"SCOPE_test_1"})
    @Test
    void generateCertificateWithNullCourse() throws Exception {
        mockMvc.perform(post("/certificates")
                        .content(asJsonString(new CertificateCreateDto(USER, null)))
                        .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().is4xxClientError());
    }

    @WithMockUser(authorities = {"SCOPE_test_1"})
    @Test
    void generateCertificateWithoutParams() throws Exception {
        mockMvc.perform(post("/certificates")
                        .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().is4xxClientError());
    }

    @WithMockUser(authorities = {"SCOPE_test_1"})
    @Test
    void findCertificate() throws Exception {
        Mockito.when(certificateFacade.findById(ArgumentMatchers.anyLong())).thenReturn(certificateDto);

        mockMvc.perform(get("/certificates/" + certificateDto.getId()))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.id").value(certificateDto.getId()));
    }

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

    @WithMockUser(authorities = {"SCOPE_test_1"})
    @Test
    void findCertificatesForUser() throws Exception {
        Mockito.when(certificateFacade.findByUserId(ArgumentMatchers.anyLong())).thenReturn(List.of(certificateDto));

        mockMvc.perform(get("/certificates/find-for-user").param("userId", "0"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$").isArray())
                .andExpect(jsonPath("$").isNotEmpty());
    }

    @WithMockUser(authorities = {"SCOPE_test_1"})
    @Test
    void findCertificatesWithoutUserId() throws Exception {
        mockMvc.perform(get("/certificates/find-for-user"))
                .andExpect(status().is5xxServerError());
    }

    @WithMockUser(authorities = {"SCOPE_test_1"})
    @Test
    void findCertificateIdForUserAndCourse() throws Exception {
        Mockito.when(certificateFacade.findByUserIdAndCourseId(ArgumentMatchers.anyLong(),
                        ArgumentMatchers.anyLong()))
                .thenReturn(List.of(certificateDto));

        mockMvc.perform(get("/certificates/find-for-user-and-course")
                        .param("userId", "0")
                        .param("courseId", "0"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$").isArray())
                .andExpect(jsonPath("$").isNotEmpty());
    }

    @WithMockUser(authorities = {"SCOPE_test_1"})
    @Test
    void findCertificateIdWithoutUserId() throws Exception {
        mockMvc.perform(get("/certificates/find-for-user-and-course")
                        .param("courseId", "0"))
                .andExpect(status().is5xxServerError());
    }

    @WithMockUser(authorities = {"SCOPE_test_1"})
    @Test
    void findCertificateIdWithoutCourseId() throws Exception {
        mockMvc.perform(get("/certificates/find-for-user-and-course")
                        .param("userId", "0"))
                .andExpect(status().is5xxServerError());
    }

    @WithMockUser(authorities = {"SCOPE_test_1"})
    @Test
    void findCertificateIdWithoutParams() throws Exception {
        mockMvc.perform(get("/certificates/find-for-user-and-course"))
                .andExpect(status().is5xxServerError());
    }

    @WithMockUser(authorities = {"SCOPE_test_1"})
    @Test
    void deleteCertificate() throws Exception {
        Mockito.doNothing().when(certificateFacade).deleteCertificate(ArgumentMatchers.anyLong());

        mockMvc.perform(delete("/certificates/" + 0L))
                .andExpect(status().is2xxSuccessful());
    }

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

    @WithMockUser(authorities = {"SCOPE_test_1"})
    @Test
    void findAllCertificates() throws Exception {
        Mockito.when(certificateFacade.findAll(any(Pageable.class)))
                .thenReturn(Page.empty(PageRequest.of(0, 1)));

        mockMvc.perform(get("/certificates")
                        .param("page", "0")
                        .param("size", "10"))
                .andExpect(status().isOk())
                .andExpect(content().contentType(MediaType.APPLICATION_JSON))
                .andExpect(jsonPath("$.content").isEmpty());
    }

    @WithMockUser(authorities = {"SCOPE_test_1"})
    @Test
    void findAllCertificatesWithoutParam() throws Exception {
        Mockito.when(certificateFacade.findAll(any(Pageable.class)))
                .thenReturn(Page.empty(PageRequest.of(0, 1)));

        mockMvc.perform(get("/certificates"))
                .andExpect(status().isOk())
                .andExpect(content().contentType(MediaType.APPLICATION_JSON))
                .andExpect(jsonPath("$.content").isEmpty());
    }
}
