package com.jakubu9333.grammarManager;

import com.jakubu9333.grammarManager.model.FileMetadata;
import com.jakubu9333.grammarManager.model.Grammar;
import com.jakubu9333.grammarManager.service.FileService;
import org.javatuples.Pair;
import org.junit.jupiter.api.Test;
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.test.web.servlet.MockMvc;

import java.util.ArrayList;
import java.util.List;

import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

/**
 * @author Jakub Uhlarik
 */

@SpringBootTest
@AutoConfigureMockMvc
public class FilesControllerTest {

    private static final String API_FILES_URL = "/api/files";

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private FileService fileService;

    @Test
    public void deleteFileByIdTest() throws Exception {
        Long id = 1L;
        mockMvc.perform(delete(API_FILES_URL + "/" + id))
                .andExpect(status().isNoContent());
        verify(fileService, times(1)).deleteFileById(id);
    }

    @Test
    public void deleteFilesFromGrammarTest() throws Exception {
        Long id = 1L;
        mockMvc.perform(delete(API_FILES_URL + "/grammar/" + id))
                .andExpect(status().isNoContent());
        verify(fileService, times(1)).deleteFilesFromGrammar(id);
    }

    @Test
    public void getFilesFromGrammarTest() throws Exception {
        Long id = 1L;
        List<FileMetadata> fileMetadataList = new ArrayList<>();
        fileMetadataList.add(new FileMetadata());
        when(fileService.getFilesFromGrammar(id)).thenReturn(fileMetadataList);
        mockMvc.perform(get(API_FILES_URL + "/grammar/" + id))
                .andExpect(status().isOk())
                .andExpect(content().contentType(MediaType.APPLICATION_JSON))
                .andExpect(jsonPath("$", hasSize(1)))
                .andExpect(jsonPath("$[0].id", is(fileMetadataList.get(0).getId())))
                .andExpect(jsonPath("$[0].size", is((int) fileMetadataList.get(0).getSize())));
        verify(fileService, times(1)).getFilesFromGrammar(id);
    }

    @Test
    public void getFileTest() throws Exception {
        Long id = 1L;
        Pair<FileMetadata, String> filePair = new Pair<>(new FileMetadata(), "file content");
        MediaType type = MediaType.TEXT_PLAIN;
        when(fileService.getFileHolderAndStringById(id)).thenReturn(filePair);
        mockMvc.perform(get(API_FILES_URL + "/" + id))
                .andExpect(status().isOk())
                .andExpect(content().contentType(type))
                .andReturn();
        verify(fileService, times(1)).getFileHolderAndStringById(id);
    }

    @Test
    public void getFileDownloadTest() throws Exception {
        Long id = 1L;
        Pair<FileMetadata, String> filePair = new Pair<>(new FileMetadata(), "file content");
        MediaType type = MediaType.TEXT_PLAIN;
        when(fileService.getFileHolderAndStringById(id)).thenReturn(filePair);
        mockMvc.perform(get(API_FILES_URL + "/" + id).param("download", "true"))
                .andExpect(status().isOk())
                .andExpect(content().contentType(type))
                .andExpect(header().string("Content-Disposition", "attachment; filename=grammar1.jsgf"))
                .andReturn();
        verify(fileService, times(1)).getFileHolderAndStringById(id);
    }


    @Test
    public void getXmlDownloadTest() throws Exception {
        Long id = 1L;
        Pair<FileMetadata, String> filePair = new Pair<>(new FileMetadata(true, true, null), "file content");
        MediaType type = MediaType.APPLICATION_XML;
        when(fileService.getFileHolderAndStringById(id)).thenReturn(filePair);
        mockMvc.perform(get(API_FILES_URL + "/" + id).param("download", "true"))
                .andExpect(status().isOk())
                .andExpect(content().contentType(type))
                .andExpect(header().string("Content-Disposition", "attachment; filename=grammar1.grxml"))
                .andReturn();
        verify(fileService, times(1)).getFileHolderAndStringById(id);
    }

    @Test
    public void testFileFromGrammarReturnsBadRequestForInvalidFileFormat() throws Exception {
        mockMvc.perform(get(API_FILES_URL + "/grammar/1/generated/?format=invalid"))
                .andExpect(status().isBadRequest());
    }

    @Test
    public void testFileFromGrammarReturnsFileInXmlFormat() throws Exception {
        when(fileService.newFileFromGrammar(anyLong(), eq(true), anyBoolean())).thenReturn(new Pair<>("file-content", 0));

        mockMvc.perform(get(API_FILES_URL + "/grammar/1/generated/?format=xml"))
                .andExpect(status().isOk())
                .andExpect(content().contentType(MediaType.APPLICATION_XML))
                .andExpect(content().string("file-content"));
    }

    @Test
    public void testFileFromGrammarReturnsFileInJsgfFormat() throws Exception {
        when(fileService.newFileFromGrammar(anyLong(), eq(false), anyBoolean())).thenReturn(new Pair<>("file-content", 0));

        mockMvc.perform(get(API_FILES_URL + "/grammar/1/generated/?format=jsgf"))
                .andExpect(status().isOk())
                .andExpect(content().contentType(MediaType.TEXT_PLAIN))
                .andExpect(content().string("file-content"));
    }


    @Test
    public void testFileFromGrammarReturnsFileDownload() throws Exception {
        when(fileService.newFileFromGrammar(anyLong(), eq(false), anyBoolean())).thenReturn(new Pair<>("file-content", 0));

        mockMvc.perform(get(API_FILES_URL + "/grammar/1/generated/?format=jsgf").param("download", "true"))
                .andExpect(status().isOk())
                .andExpect(content().contentType(MediaType.TEXT_PLAIN))
                .andExpect(header().string("Content-Disposition", "attachment; filename=1_generated_g.jsgf"))
                .andExpect(content().string("file-content"));
    }


    @Test
    public void testGenFromGrammarReturnsBadRequestForInvalidFileFormat() throws Exception {
        mockMvc.perform(put(API_FILES_URL + "/grammar/1/generated/?format=invalid"))
                .andExpect(status().isBadRequest());
    }

    @Test
    public void testGenFromGrammarReturnsFileInXmlFormat() throws Exception {
        when(fileService.newFileFromGrammar(anyLong(), eq(true), anyBoolean())).thenReturn(new Pair<>("file-content", 0));

        mockMvc.perform(put(API_FILES_URL + "/grammar/1/generated/?format=xml"))
                .andExpect(status().isOk())
                .andExpect(content().contentType(MediaType.APPLICATION_XML))
                .andExpect(content().string("file-content"));
    }

    @Test
    public void testGenFromGrammarReturnsFileInJsgfFormat() throws Exception {
        when(fileService.newFileFromGrammar(anyLong(), eq(false), anyBoolean())).thenReturn(new Pair<>("file-content", 0));

        mockMvc.perform(put(API_FILES_URL + "/grammar/1/generated/?format=jsgf"))
                .andExpect(status().isOk())
                .andExpect(content().contentType(MediaType.TEXT_PLAIN))
                .andExpect(content().string("file-content"));
    }

    @Test
    public void testGenCreatedFromGrammarReturnsFileInJsgfFormat() throws Exception {
        when(fileService.newFileFromGrammar(anyLong(), eq(false), anyBoolean())).thenReturn(new Pair<>("file-content", 1));

        mockMvc.perform(put(API_FILES_URL + "/grammar/1/generated/?format=jsgf"))
                .andExpect(status().isCreated())
                .andExpect(content().contentType(MediaType.TEXT_PLAIN))
                .andExpect(content().string("file-content"));
    }

    @Test
    public void testGrammarFromFileWithoutFormatParameter() throws Exception {
        Grammar grammar = new Grammar("rthretr");
        when(fileService.addGrammarFromFile(anyString(), eq(null), eq(null), eq(false), eq(true))).thenReturn(new Pair<>(grammar, true));

        mockMvc.perform(post(API_FILES_URL + "/")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content("{\"content\":\"file-content\"}"))
                .andExpect(status().isCreated())
                .andExpect(jsonPath("$.id").value(grammar.getId()));
    }

    @Test
    public void testGrammarFromFileUnk() throws Exception {
        Grammar grammar = new Grammar("ergtrhtrh");
        when(fileService.addGrammarFromFile(anyString(), eq(null), eq(null), eq(false), eq(true))).thenReturn(new Pair<>(grammar, true));

        mockMvc.perform(post(API_FILES_URL + "/?format=u")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content("{\"content\":\"file-content\"}"))
                .andExpect(status().isCreated())
                .andExpect(jsonPath("$.id").value(grammar.getId()))
                .andExpect(jsonPath("$.uri").value(grammar.getUri()));
    }

    @Test
    public void testGrammarFromFileWithJsgfFormatParameter() throws Exception {
        Grammar grammar = new Grammar("ergerge");
        when(fileService.addGrammarFromFile(anyString(), eq(false), eq(null), eq(false), eq(true))).thenReturn(new Pair<>(grammar, true));

        mockMvc.perform(post(API_FILES_URL + "/?format=jsgf")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content("{\"content\":\"file-content\"}"))
                .andExpect(status().isCreated())
                .andExpect(jsonPath("$.id").value(grammar.getId()))
                .andExpect(jsonPath("$.uri").value(grammar.getUri()));
    }

    @Test
    public void testGrammarFromFileWithXmlFormatParameter() throws Exception {
        Grammar grammar = new Grammar();
        when(fileService.addGrammarFromFile(anyString(), eq(true), eq(null), eq(false), eq(true))).thenReturn(new Pair<>(grammar, true));

        mockMvc.perform(post(API_FILES_URL + "/?format=xml")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content("{\"content\":\"file-content\"}"))
                .andExpect(status().isCreated())
                .andExpect(jsonPath("$.id").value(grammar.getId()));
    }

    @Test
    public void testGrammarFromFileWithInvalidFormatParameter() throws Exception {
        mockMvc.perform(post(API_FILES_URL + "/?format=invalid")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content("{\"content\":\"file-content\"}"))
                .andExpect(status().isBadRequest());
    }

    @Test
    public void testGrammarFromFileWithExistingGrammarWithInvalidFormatParameter() throws Exception {
        mockMvc.perform(post(API_FILES_URL + "/1").param("format", "invalid")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content("{\"content\":\"file-content\"}"))
                .andExpect(status().isBadRequest());
    }

    @Test
    public void testGrammarFromFileWithExistingGrammarIdAndCreateFalse() throws Exception {
        Grammar grammar = new Grammar("dfsdf");
        when(fileService.addGrammarFromFile(anyString(), eq(null), eq(1L), eq(false), eq(true))).thenReturn(new Pair<>(grammar, false));

        mockMvc.perform(post(API_FILES_URL + "/1?create=false")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content("{\"content\":\"file-content\"}"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.id").value(grammar.getId()))
                .andExpect(jsonPath("$.uri").value(grammar.getUri()));
    }

    @Test
    public void testGrammarFromFileWithExistingGrammarFormatJsgf() throws Exception {
        Grammar grammar = new Grammar("dfd");
        when(fileService.addGrammarFromFile(anyString(), eq(false), eq(1L), anyBoolean(), anyBoolean())).thenReturn(new Pair<>(grammar, false));

        mockMvc.perform(post(API_FILES_URL + "/1").param("format", "jsgf")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content("{\"content\":\"file-content\"}"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.id").value(grammar.getId()))
                .andExpect(jsonPath("$.uri").value(grammar.getUri()));
    }


    @Test
    public void testGrammarFromFileWithExistingGrammarFormatXml() throws Exception {
        Grammar grammar = new Grammar("xd");
        when(fileService.addGrammarFromFile(anyString(), eq(true), eq(1L), anyBoolean(), anyBoolean())).thenReturn(new Pair<>(grammar, false));

        mockMvc.perform(post(API_FILES_URL + "/1").param("format", "xml")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content("{\"content\":\"file-content\"}"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.id").value(grammar.getId()))
                .andExpect(jsonPath("$.uri").value(grammar.getUri()));
    }

    @Test
    public void testGrammarFromFileWithExistingNullReturn() throws Exception {
        when(fileService.addGrammarFromFile(anyString(), eq(true), eq(1L), anyBoolean(), anyBoolean())).thenReturn(null);

        mockMvc.perform(post(API_FILES_URL + "/1").param("format", "xml")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content("{\"content\":\"file-content\"}"))
                .andExpect(status().isNoContent());

    }
}
