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.BeforeEach;
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 java.util.Set;

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 RuleControllerTest {

    @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);
    }

    @BeforeEach
    public void initEach() {
        testGrammar1 = grammarService.processAddGrammar(testGrammar1);
        testGrammar2 = grammarService.processAddGrammar(testGrammar2);
    }


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

    @Test
    void testGetAllRules() throws Exception {
        List<RuleModel> rules = List.of(new RuleModel("dssf", null), new RuleModel("Sdsfffs", null), new RuleModel("SFs", null));
        ruleService.addRuleMultiple(rules, testGrammar1);

        mvc.perform(get("/api/rules"))
                .andExpect(status().isOk())
                .andExpect(content().contentType(MediaType.APPLICATION_JSON))
                .andExpect(jsonPath("$.[0].id").exists())
                .andExpect(jsonPath("$.[1].id").exists())
                .andExpect(jsonPath("$.[2].id").exists());
    }

    @Test
    void testAddRule() throws Exception {
        RuleModel rule = new RuleModel("dssf", null);

        mvc.perform(post("/api/rules/{id}", testGrammar1.getId().intValue())
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(rule)))
                .andExpect(status().isCreated())
                .andExpect(content().contentType(MediaType.APPLICATION_JSON))
                .andExpect(jsonPath("$.id").exists())
                .andExpect(jsonPath("$.leftSymbol").value("<dssf>"))
                .andExpect(jsonPath("$.grammar.id").value(testGrammar1.getId().intValue()));
    }

    @Test
    void testAddMultipleRules() throws Exception {
        Set<RuleModel> rules = new HashSet<>();
        rules.add(new RuleModel("dssf", null));
        rules.add(new RuleModel("dgfssf", null));


        mvc.perform(post("/api/rules/{1}/batch", testGrammar1.getId().intValue())
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(rules)))
                .andExpect(status().isCreated())
                .andExpect(content().contentType(MediaType.APPLICATION_JSON))
                .andExpect(jsonPath("$", hasSize(2)))
                .andExpect(jsonPath("$[0].grammar.id", is(testGrammar1.getId().intValue())))
                .andExpect(jsonPath("$[1].grammar.id", is(testGrammar1.getId().intValue())));
    }

    @Test
    public void getRuleByIdTest() throws Exception {

        RuleModel rule = new RuleModel("Rule6", null);
        rule = ruleService.processAddRule(rule, testGrammar1).getValue0();


        mvc.perform(get("/api/rules/{id}", rule.getId().intValue()))
                .andExpect(status().isOk())
                .andExpect(content().contentType(MediaType.APPLICATION_JSON))
                .andExpect(jsonPath("$.id", is(rule.getId().intValue())))
                .andExpect(jsonPath("$.leftSymbol", is("<Rule6>")));
    }

    @Test
    public void testDeleteAllRules() throws Exception {
        RuleModel rule = new RuleModel("Rule1", null);
        ruleService.processAddRule(rule, testGrammar1);
        mvc.perform(delete("/api/rules/all"))
                .andExpect(status().isNoContent());

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


    @Test
    public void testDeleteRuleById() throws Exception {
        ruleService.deleteRules();
        RuleModel rule = new RuleModel("Rule3", null);
        RuleModel rule1 = new RuleModel("Rule4", null);
        ruleService.processAddRule(rule, testGrammar1);
        ruleService.processAddRule(rule1, testGrammar1);
        mvc.perform(delete("/api/rules/{id}", rule.getId()))
                .andExpect(status().isNoContent());


        mvc.perform(get("/api/rules"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$", hasSize(1))).andExpect(jsonPath("$[0].leftSymbol", is("<Rule4>")));
    }

    @Test
    public void testRulesFromGrammarById() throws Exception {
        int ruleNum = testGrammar1.getRules().size();
        mvc.perform(get("/api/rules/grammar/{id}", testGrammar1.getId()))
                .andExpect(status().isOk())
                .andExpect(content().contentType(MediaType.APPLICATION_JSON))
                .andExpect(jsonPath("$", hasSize(ruleNum)));


    }

    @Test
    public void testRulesFromGrammarByandById() throws Exception {

        mvc.perform(get("/api/rules/grammar/{id}/{ruleId}", testGrammar2.getId(), testGrammar2.getRootRule().getId()))
                .andExpect(status().isOk())
                .andExpect(content().contentType(MediaType.APPLICATION_JSON))
                .andExpect(jsonPath("$.leftSymbol", is("<rule2>")));


    }

    @Test
    public void testRulesByLeft() throws Exception {

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


    }


    @Test
    public void testRuleUpdateById() throws Exception {
        RuleModel update = new RuleModel("dgdflgjfd", null);
        Long idRem = testGrammar2.getRootRule().getId();
        mvc.perform(put("/api/rules/{id}", idRem)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(update)))
                .andExpect(status().isOk())
                .andExpect(content().contentType(MediaType.APPLICATION_JSON))
                .andExpect(jsonPath("$.id", is(idRem.intValue())))
                .andExpect(jsonPath("$.leftSymbol", is("<dgdflgjfd>")));


    }
}
