package openapi;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.flipkart.zjsonpatch.JsonDiff;
import com.jayway.jsonpath.JsonPath;
import de.tudresden.inf.st.openapi.ast.*;
import io.swagger.parser.OpenAPIParser;
import io.swagger.v3.core.util.Json;
import io.swagger.v3.core.util.Yaml;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.parser.OpenAPIV3Parser;
import io.swagger.v3.parser.core.models.SwaggerParseResult;
import org.apache.commons.validator.routines.UrlValidator;
import org.junit.jupiter.api.*;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;

import javax.validation.constraints.AssertTrue;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;

public class OpenAPIMain_test {

    static List<File> resources = new ArrayList<>();

    @BeforeAll
    static void init() {
        File r = new File("./src/main/resources");
        initResources(r);
    }

    @MethodSource("resources")
    @ParameterizedTest
    void parserTest(File file) throws Exception {
        OpenAPIObject jastAddObject;
        OpenAPI openAPI;
        ObjectMapper mapper = new ObjectMapper();
        List<String> validation;

        // parse OpenAPI in POJO, parse Json by POJO and validate OpenAPI-Json
        SwaggerParseResult result = new OpenAPIParser().readLocation(file.getPath(), null, null);
        openAPI = result.getOpenAPI();
        System.out.println("Loading expression DSL file '" + file + "'.");
        JsonNode expectedNode = mapper.readTree(Json.mapper().writeValueAsString(openAPI));
        validation = new OpenAPIV3Parser().readContents(expectedNode.toString()).getMessages();

        Assertions.assertFalse(validation.size() != 0, "validation of the input yaml not succeeded");

        // parse OpenAPI in JastAdd, transform it to OpenAPI-POJO back and validate this
        jastAddObject = OpenAPIObject.parseOpenAPI(openAPI);
        OpenAPI transformedAPI = OpenAPIObject.reverseOpenAPI(jastAddObject);
        JsonNode actualNode = mapper.readTree(Json.mapper().writeValueAsString(transformedAPI));
        validation = new OpenAPIV3Parser().readContents(actualNode.toString()).getMessages();

        Assertions.assertFalse(validation.size() != 0, "validation of the transformed yaml not succeeded");

        // compare if parsed OpenAPI (source object, Json) is equivalent to back-transformed OpenAPI (generated object, Json)
        compareJson(expectedNode, actualNode, Paths.get(file.getPath()));
    }

    @MethodSource("resources")
    @ParameterizedTest
    void RandomUrlTest(File file) {
        OpenAPIObject jastAddObject;
        OpenAPI openAPI;
        List<String> urls;
        UrlValidator urlValidator = new UrlValidator();

        SwaggerParseResult result = new OpenAPIParser().readLocation(file.getPath(), null, null);
        openAPI = result.getOpenAPI();
        System.out.println("Loading expression DSL file '" + file + "'.");

        jastAddObject = OpenAPIObject.parseOpenAPI(openAPI);
        urls = jastAddObject.generateRequests(jastAddObject.generateParameters());

        for (String url : urls)
            Assertions.assertTrue(urlValidator.isValid(url), "validation of " + url + " not succeeded");
    }

    /*
    @MethodSource("resources")
    @ParameterizedTest
    void RandomParameterTest(File file) {
        OpenAPIObject jastAddObject;
        OpenAPI openAPI;
        List<String> urls;
        Map<String, List<Object>> parameters;

        SwaggerParseResult result = new OpenAPIParser().readLocation(file.getPath(), null, null);
        openAPI = result.getOpenAPI();
        System.out.println("Loading expression DSL file '" + file + "'.");

        jastAddObject = OpenAPIObject.parseOpenAPI(openAPI);
        parameters = jastAddObject.generateParameters();
        for ( String key : parameters.keySet() ){
            for ( Object value : parameters.get(key) ) {
                if ( value instanceof String )
                    Assertions.assertTrue(((String) value).matches("[a-zA-Z0-9#.()/%&\\s-]{0,19}"), "validation of " + key + " : " + value + " not succeeded");
            }
        }
    }
     */

    static Stream<File> resources() {
        return resources.stream();
    }

    static void compareJson(JsonNode expectedNode, JsonNode actualNode, Path path) throws IOException {
        JsonNode diff = JsonDiff.asJson(expectedNode, actualNode);
        String pathNode;
        String result = "";

        for (int i = diff.size() - 1; i >= 0; i--) {
            // get the path of a node involving difference.
            pathNode = "$" + diff.get(i).get("path").toString();
            for (String s : pathNode.split("/")) {
                if (s.contains("."))
                    pathNode = pathNode.replace(s, "['" + s + "']");
                else if (s.contains(" "))
                    pathNode = pathNode.replace(s, "['" + s + "']");
            }
            pathNode = pathNode
                    .replace("/", ".")
                    .replace("~1", "/")
                    .replace("\"", "");
            for (String s : pathNode.split("\\.")) {
                if (!s.contains("['") && isNumeric(s) && Integer.parseInt(s) < 200)
                    result = result.concat("[" + s + "].");
                else
                    result = result.concat(s + ".");
            }
            pathNode = result.substring(0, result.length() - 1);

            // check, if this node is null or has an empty value.
            if (JsonPath.parse(expectedNode.toString()).read(pathNode, String.class) == null || JsonPath.parse(expectedNode.toString()).read(pathNode, String.class).isEmpty())
                ((ArrayNode) diff).remove(i);
            else if (JsonPath.parse(actualNode.toString()).read(pathNode, String.class) == null || JsonPath.parse(actualNode.toString()).read(pathNode, String.class).isEmpty())
                ((ArrayNode) diff).remove(i);

            result = "";
        }

        // if the Jsons are equivalent, there is no reason to to the text comparison.
        // if there is a difference, a text comparison might look better than just the diff.
        if (diff.size() != 0) {
            Assertions.assertEquals(actualNode.toPrettyString(), expectedNode.toPrettyString(), "JSONs for " + path + " are different:\n" + diff.toPrettyString());
        }
    }

    static boolean isNumeric(String str) {
        try {
            int d = Integer.parseInt(str);
        } catch (NumberFormatException nfe) {
            return false;
        }
        return true;
    }

    static boolean isAlphaNumeric(String s) {
        return s != null && s.matches("^[a-zA-Z0-9]*$");
    }

    static void initResources(File file) {
        if (file.isDirectory()) {
            for (File f : file.listFiles())
                initResources(f);
        } else if (file.isFile() && file.getPath().contains("yaml"))
            resources.add(file);
    }
}