package com.jakubu9333.grammarManager;

import com.fasterxml.jackson.databind.ObjectMapper;


import com.jakubu9333.grammarManager.model.Grammar;
import com.jakubu9333.grammarManager.model.RightSidePart;
import com.jakubu9333.grammarManager.model.RuleModel;
import com.jakubu9333.grammarManager.service.GrammarService;
import com.jakubu9333.grammarManager.service.RuleService;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
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.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;

import java.util.HashSet;
import java.util.List;

import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
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.post;
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.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

/**
 * @author Jakub Uhlarik
 */

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
public class GrammarControllerTest {

    @Autowired
    private MockMvc mvc;
    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private GrammarService grammarService;

    @Autowired
    private RuleService ruleService;

    static Grammar testGrammar1;
    static Grammar testGrammar2;

    @BeforeAll
    public static void init() {
        RuleModel r1 = new RuleModel("rule1", new HashSet<>(List.of(new RightSidePart("Test rule 1"))), false);
        RuleModel r2 = new RuleModel("rule2", new HashSet<>(List.of(new RightSidePart("Test rule 2"))), true);
        testGrammar1 = new Grammar("TestGrammar1", new HashSet<>(List.of(r1)), null);
        testGrammar2 = new Grammar("TestGrammar2", new HashSet<>(List.of(r2)), null);
    }

    @AfterEach
    public void tearDown() {
        grammarService.deleteAll();
    }


    @Test
    public void testGetAllGrammars() throws Exception {
        testGrammar1 = grammarService.processAddGrammar(testGrammar1);
        testGrammar2 = grammarService.processAddGrammar(testGrammar2);

        mvc.perform(get("/api/grammars"))
                .andExpect(status().isOk())
                .andExpect(content().contentType(MediaType.APPLICATION_JSON))
                .andExpect(jsonPath("$", hasSize(2)))
                .andExpect(jsonPath("$[0].uri", is(testGrammar1.getUri())))
                .andExpect(jsonPath("$[0].rules[0].leftSymbol", is("<rule1>")))
                .andExpect(jsonPath("$[1].uri", is(testGrammar2.getUri())))
                .andExpect(jsonPath("$[1].rootRule.leftSymbol", is(testGrammar2.getRootRule().getLeftSymbol())));

    }

    @Test
    public void testDeleteAllGrammar() throws Exception {
        testGrammar1 = grammarService.processAddGrammar(testGrammar1);
        testGrammar2 = grammarService.processAddGrammar(testGrammar2);

        mvc.perform(delete("/api/grammars/all"))
                .andExpect(status().isNoContent());

        mvc.perform(get("/api/grammars"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$", hasSize(0)));
    }

    @Test
    public void testGetGrammarById() throws Exception {
        testGrammar1 = grammarService.processAddGrammar(testGrammar1);
        testGrammar2 = grammarService.processAddGrammar(testGrammar2);

        mvc.perform(get("/api/grammars/" + testGrammar1.getId()))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.rules[0].leftSymbol", is("<rule1>")))
                .andExpect(jsonPath("$.uri", is(testGrammar1.getUri())));
    }

    @Test
    public void testDeleteGrammarById() throws Exception {
        testGrammar1 = grammarService.processAddGrammar(testGrammar1);
        testGrammar2 = grammarService.processAddGrammar(testGrammar2);

        mvc.perform(delete("/api/grammars/" + testGrammar1.getId()))
                .andExpect(status().isNoContent());

        mvc.perform(get("/api/grammars"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$", hasSize(1)))
                .andExpect(jsonPath("$[0].rootRule.leftSymbol", is(testGrammar2.getRootRule().getLeftSymbol())))
                .andExpect(jsonPath("$[0].uri", is(testGrammar2.getUri())));
    }

    @Test
    public void testDuplicateGrammar() throws Exception {
        testGrammar1 = grammarService.processAddGrammar(testGrammar1);

        mvc.perform(post("/api/grammars/" + testGrammar1.getId()))
                .andExpect(status().isCreated())
                .andExpect((jsonPath("$.rules[0].leftSymbol", is("<rule1>"))));


    }

    @Test
    public void testAddGrammar() throws Exception {
        Grammar newGrammar = new Grammar("New Grammar", null, null);
        mvc.perform(post("/api/grammars")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(newGrammar)))
                .andExpect(status().isCreated())
                .andExpect(jsonPath("$.uri", is(newGrammar.getUri())));
    }

    @Test
    public void testChangeUri() throws Exception {
        testGrammar1 = grammarService.processAddGrammar(testGrammar1);
        Grammar newGrammar = new Grammar("New Grammar", null, null);
        mvc.perform(put("/api/grammars/{id}/uri", testGrammar1.getId())
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(newGrammar)))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.id", is(testGrammar1.getId().intValue())))
                .andExpect(jsonPath("$.uri", is(newGrammar.getUri())));

    }

    @Test
    public void testGetUriAllGrammars() throws Exception {
        testGrammar1 = grammarService.processAddGrammar(testGrammar1);
        testGrammar2 = grammarService.processAddGrammar(testGrammar2);

        mvc.perform(get("/api/grammars/uri").param("uri", "TestGrammar1"))
                .andExpect(status().isOk())
                .andExpect(content().contentType(MediaType.APPLICATION_JSON))
                .andExpect(jsonPath("$", hasSize(1)))
                .andExpect(jsonPath("$[0].uri", is(testGrammar1.getUri())))
                .andExpect(jsonPath("$[0].rules[0].leftSymbol", is("<rule1>")));

    }

    @Test
    public void testGetRootAllGrammars() throws Exception {
        testGrammar1 = grammarService.processAddGrammar(testGrammar1);
        testGrammar2 = grammarService.processAddGrammar(testGrammar2);

        mvc.perform(get("/api/grammars/root").param("root", "<rule2>"))
                .andExpect(status().isOk())
                .andExpect(content().contentType(MediaType.APPLICATION_JSON))
                .andExpect(jsonPath("$", hasSize(1)))
                .andExpect(jsonPath("$[0].uri", is(testGrammar2.getUri())))
                .andExpect(jsonPath("$[0].rules[0].leftSymbol", is("<rule2>")));

    }


}
