package spec.validator;


import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.client.WireMock;
import com.github.tomakehurst.wiremock.core.WireMockConfiguration;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import io.swagger.handler.ValidatorController;
import io.swagger.models.ValidationResponse;
import io.swagger.oas.inflector.models.RequestContext;
import io.swagger.oas.inflector.models.ResponseContext;

import org.apache.commons.io.FileUtils;

import org.testng.Assert;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.Random;

import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
import static com.github.tomakehurst.wiremock.client.WireMock.get;
import static com.github.tomakehurst.wiremock.client.WireMock.urlPathMatching;



public class ValidatorTest {

    private static final String IMAGE = "image";
    private static final String PNG = "png";
    private static final String VALID_30_YAML = "/valid_oas3.yaml";
    private static final String INVALID_30_YAML ="/invalid_oas3.yaml";
    private static final String VALID_20_YAML = "/valid_swagger2.yaml";
    private static final String INVALID_20_YAML ="/invalid_swagger2.yaml";
    private static final String VALID_IMAGE = "valid.png";
    private static final String INVALID_IMAGE = "invalid.png";
    private static final String APPLICATION = "application";
    private static final String JSON = "json";
    private static final String INFO_MISSING = "attribute info is missing";
    private static final String INFO_MISSING_SCHEMA = "object has missing required properties ([\"info\"])";

    protected int serverPort = getDynamicPort();
    protected WireMockServer wireMockServer;


    @BeforeClass
    private void setUpWireMockServer() throws IOException {
        this.wireMockServer = new WireMockServer(WireMockConfiguration.wireMockConfig().dynamicPort());
        this.wireMockServer.start();
        this.serverPort = wireMockServer.port();
        WireMock.configureFor(this.serverPort);

        String pathFile = FileUtils.readFileToString(new File("src/test/resources/valid_oas3.yaml"));

        WireMock.stubFor(get(urlPathMatching("/valid/yaml"))
                .willReturn(aResponse()
                        .withStatus(HttpURLConnection.HTTP_OK)
                        .withHeader("Content-type", "application/yaml")
                        .withBody(pathFile
                                .getBytes(StandardCharsets.UTF_8))));

        pathFile = FileUtils.readFileToString(new File("src/test/resources/invalid_oas3.yaml"));

        WireMock.stubFor(get(urlPathMatching("/invalid/yaml"))
                .willReturn(aResponse()
                        .withStatus(HttpURLConnection.HTTP_OK)
                        .withHeader("Content-type", "application/yaml")
                        .withBody(pathFile
                                .getBytes(StandardCharsets.UTF_8))));

        pathFile = FileUtils.readFileToString(new File("src/test/resources/valid_swagger2.yaml"));

        WireMock.stubFor(get(urlPathMatching("/validswagger/yaml"))
                .willReturn(aResponse()
                        .withStatus(HttpURLConnection.HTTP_OK)
                        .withHeader("Content-type", "application/yaml")
                        .withBody(pathFile
                                .getBytes(StandardCharsets.UTF_8))));

        pathFile = FileUtils.readFileToString(new File("src/test/resources/invalid_swagger2.yaml"));

        WireMock.stubFor(get(urlPathMatching("/invalidswagger/yaml"))
                .willReturn(aResponse()
                        .withStatus(HttpURLConnection.HTTP_OK)
                        .withHeader("Content-type", "application/yaml")
                        .withBody(pathFile
                                .getBytes(StandardCharsets.UTF_8))));

    }

    @AfterClass
    private void tearDownWireMockServer() {
        this.wireMockServer.stop();
    }




    @Test
    public void testValidateValid20SpecByUrl() throws Exception {
        String url = "http://localhost:${dynamicPort}/validswagger/yaml";
        url = url.replace("${dynamicPort}", String.valueOf(this.serverPort));

        ValidatorController validator = new ValidatorController();
        ResponseContext response = validator.validateByUrl(new RequestContext(), url);

        Assert.assertEquals(IMAGE, response.getContentType().getType());
        Assert.assertEquals(PNG, response.getContentType().getSubtype());
        InputStream entity = (InputStream)response.getEntity();
        InputStream valid = this.getClass().getClassLoader().getResourceAsStream(VALID_IMAGE);

        Assert.assertTrue( validateEquals(entity,valid) == true);

    }

    @Test
    public void testValidateValid30SpecByUrl() throws Exception {
        String url = "http://localhost:${dynamicPort}/valid/yaml";
        url = url.replace("${dynamicPort}", String.valueOf(this.serverPort));

        ValidatorController validator = new ValidatorController();
        ResponseContext response = validator.validateByUrl(new RequestContext(), url);

        Assert.assertEquals(IMAGE, response.getContentType().getType());
        Assert.assertEquals(PNG, response.getContentType().getSubtype());
        InputStream entity = (InputStream)response.getEntity();
        InputStream valid = this.getClass().getClassLoader().getResourceAsStream(VALID_IMAGE);

        Assert.assertTrue( validateEquals(entity,valid) == true);

    }

    @Test
    public void testValidateInvalid30SpecByUrl() throws Exception {
        String url = "http://localhost:${dynamicPort}/invalid/yaml";
        url = url.replace("${dynamicPort}", String.valueOf(this.serverPort));

        ValidatorController validator = new ValidatorController();
        ResponseContext response = validator.validateByUrl(new RequestContext(), url);

        Assert.assertEquals(IMAGE, response.getContentType().getType());
        Assert.assertEquals(PNG, response.getContentType().getSubtype());
        InputStream entity = (InputStream)response.getEntity();
        InputStream valid = this.getClass().getClassLoader().getResourceAsStream(INVALID_IMAGE);

        Assert.assertTrue( validateEquals(entity,valid) == true);

    }

    @Test
    public void testValidateInvalid20SpecByUrl() throws Exception {
        String url = "http://localhost:${dynamicPort}/invalidswagger/yaml";
        url = url.replace("${dynamicPort}", String.valueOf(this.serverPort));

        ValidatorController validator = new ValidatorController();
        ResponseContext response = validator.validateByUrl(new RequestContext(), url);

        Assert.assertEquals(IMAGE, response.getContentType().getType());
        Assert.assertEquals(PNG, response.getContentType().getSubtype());
        InputStream entity = (InputStream)response.getEntity();
        InputStream valid = this.getClass().getClassLoader().getResourceAsStream(INVALID_IMAGE);

        Assert.assertTrue( validateEquals(entity,valid) == true);

    }

    @Test
    public void testValidateInvalid30SpecByContent() throws Exception {
        final ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
        final JsonNode rootNode = mapper.readTree(Files.readAllBytes(java.nio.file.Paths.get(getClass().getResource(INVALID_30_YAML).toURI())));
        ValidatorController validator = new ValidatorController();
        ResponseContext response = validator.validateByContent(new RequestContext(), rootNode);

        Assert.assertEquals(IMAGE, response.getContentType().getType());
        Assert.assertEquals(PNG, response.getContentType().getSubtype());
        InputStream entity = (InputStream)response.getEntity();
        InputStream valid = this.getClass().getClassLoader().getResourceAsStream(INVALID_IMAGE);

        Assert.assertTrue( validateEquals(entity,valid) == true);


    }

    @Test
    public void testValidateInvalid20SpecByContent() throws Exception {
        final ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
        final JsonNode rootNode = mapper.readTree(Files.readAllBytes(java.nio.file.Paths.get(getClass().getResource(INVALID_20_YAML).toURI())));
        ValidatorController validator = new ValidatorController();
        ResponseContext response = validator.validateByContent(new RequestContext(), rootNode);

        Assert.assertEquals(IMAGE, response.getContentType().getType());
        Assert.assertEquals(PNG, response.getContentType().getSubtype());
        InputStream entity = (InputStream)response.getEntity();
        InputStream valid = this.getClass().getClassLoader().getResourceAsStream(INVALID_IMAGE);

        Assert.assertTrue( validateEquals(entity,valid) == true);


    }


    @Test
    public void testValidateValid30SpecByContent() throws Exception {
        final ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
        final JsonNode rootNode = mapper.readTree(Files.readAllBytes(java.nio.file.Paths.get(getClass().getResource(VALID_30_YAML).toURI())));
        ValidatorController validator = new ValidatorController();
        ResponseContext response = validator.validateByContent(new RequestContext(), rootNode);

        Assert.assertEquals(IMAGE, response.getContentType().getType());
        Assert.assertEquals(PNG, response.getContentType().getSubtype());
        InputStream entity = (InputStream)response.getEntity();
        InputStream valid = this.getClass().getClassLoader().getResourceAsStream(VALID_IMAGE);


        Assert.assertTrue( validateEquals(entity,valid) == true);
    }

    @Test
    public void testValidateValid20SpecByContent() throws Exception {
        final ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
        final JsonNode rootNode = mapper.readTree(Files.readAllBytes(java.nio.file.Paths.get(getClass().getResource(VALID_20_YAML).toURI())));
        ValidatorController validator = new ValidatorController();
        ResponseContext response = validator.validateByContent(new RequestContext(), rootNode);

        Assert.assertEquals(IMAGE, response.getContentType().getType());
        Assert.assertEquals(PNG, response.getContentType().getSubtype());
        InputStream entity = (InputStream)response.getEntity();
        InputStream valid = this.getClass().getClassLoader().getResourceAsStream(VALID_IMAGE);

        Assert.assertTrue( validateEquals(entity,valid) == true);
    }



    @Test
    public void testDebugValid30SpecByUrl() throws Exception {
        String url = "http://localhost:${dynamicPort}/valid/yaml";
        url = url.replace("${dynamicPort}", String.valueOf(this.serverPort));

        ValidatorController validator = new ValidatorController();
        ResponseContext response = validator.reviewByUrl(new RequestContext(), url);

        // since inflector 2.0.3 content type is managed by inflector according to request headers and spec
/*
        Assert.assertEquals(APPLICATION, response.getContentType().getType());
        Assert.assertEquals(JSON, response.getContentType().getSubtype());
*/
        ValidationResponse validationResponse = (ValidationResponse) response.getEntity();
        Assert.assertTrue(validationResponse.getMessages() == null || validationResponse.getMessages().size() == 0);
        Assert.assertTrue(validationResponse.getSchemaValidationMessages() == null || validationResponse.getSchemaValidationMessages().size() == 0);
    }

    @Test
    public void testDebugValid20SpecByUrl() throws Exception {
        String url = "http://localhost:${dynamicPort}/validswagger/yaml";
        url = url.replace("${dynamicPort}", String.valueOf(this.serverPort));


        ValidatorController validator = new ValidatorController();
        ResponseContext response = validator.reviewByUrl(new RequestContext(), url);

        // since inflector 2.0.3 content type is managed by inflector according to request headers and spec
/*
        Assert.assertEquals(APPLICATION, response.getContentType().getType());
        Assert.assertEquals(JSON, response.getContentType().getSubtype());
*/
        ValidationResponse validationResponse = (ValidationResponse) response.getEntity();
        Assert.assertTrue(validationResponse.getMessages() == null || validationResponse.getMessages().size() == 0);
        Assert.assertTrue(validationResponse.getSchemaValidationMessages() == null || validationResponse.getSchemaValidationMessages().size() == 0);
    }

    @Test
    public void testDebugInvalid30SpecByUrl() throws Exception {
        String url = "http://localhost:${dynamicPort}/invalid/yaml";
        url = url.replace("${dynamicPort}", String.valueOf(this.serverPort));

        ValidatorController validator = new ValidatorController();
        ResponseContext response = validator.reviewByUrl(new RequestContext(), url);

        // since inflector 2.0.3 content type is managed by inflector according to request headers and spec
/*
        Assert.assertEquals(APPLICATION, response.getContentType().getType());
        Assert.assertEquals(JSON, response.getContentType().getSubtype());
*/
        ValidationResponse validationResponse = (ValidationResponse) response.getEntity();
        Assert.assertTrue(validationResponse.getMessages().contains(INFO_MISSING));
        Assert.assertTrue(validationResponse.getSchemaValidationMessages().get(0).getMessage().equals(INFO_MISSING_SCHEMA));

    }

    @Test
    public void testDebugInvalid20SpecByUrl() throws Exception {
        String url = "http://localhost:${dynamicPort}/invalidswagger/yaml";
        url = url.replace("${dynamicPort}", String.valueOf(this.serverPort));

        ValidatorController validator = new ValidatorController();
        ResponseContext response = validator.reviewByUrl(new RequestContext(), url);

        // since inflector 2.0.3 content type is managed by inflector according to request headers and spec
/*
        Assert.assertEquals(APPLICATION, response.getContentType().getType());
        Assert.assertEquals(JSON, response.getContentType().getSubtype());
*/
        ValidationResponse validationResponse = (ValidationResponse) response.getEntity();
        Assert.assertTrue(validationResponse.getMessages().contains(INFO_MISSING));
        Assert.assertTrue(validationResponse.getSchemaValidationMessages().get(0).getMessage().equals(INFO_MISSING_SCHEMA));

    }

    @Test
    public void testDebugInvalid30SpecByContent() throws Exception {
        final ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
        final JsonNode rootNode = mapper.readTree(Files.readAllBytes(java.nio.file.Paths.get(getClass().getResource(INVALID_30_YAML).toURI())));
        ValidatorController validator = new ValidatorController();
        ResponseContext response = validator.reviewByContent(new RequestContext(), rootNode);

        // since inflector 2.0.3 content type is managed by inflector according to request headers and spec
/*
        Assert.assertEquals(APPLICATION, response.getContentType().getType());
        Assert.assertEquals(JSON, response.getContentType().getSubtype());
*/

        ValidationResponse validationResponse = (ValidationResponse) response.getEntity();
        Assert.assertTrue(validationResponse.getMessages().contains(INFO_MISSING));
        Assert.assertTrue(validationResponse.getSchemaValidationMessages().get(0).getMessage().equals(INFO_MISSING_SCHEMA));
    }

    @Test
    public void testDebugInvalid20SpecByContent() throws Exception {
        final ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
        final JsonNode rootNode = mapper.readTree(Files.readAllBytes(java.nio.file.Paths.get(getClass().getResource(INVALID_20_YAML).toURI())));
        ValidatorController validator = new ValidatorController();
        ResponseContext response = validator.reviewByContent(new RequestContext(), rootNode);

        // since inflector 2.0.3 content type is managed by inflector according to request headers and spec
/*
        Assert.assertEquals(APPLICATION, response.getContentType().getType());
        Assert.assertEquals(JSON, response.getContentType().getSubtype());
*/
        ValidationResponse validationResponse = (ValidationResponse) response.getEntity();
        Assert.assertTrue(validationResponse.getMessages().contains(INFO_MISSING));
        Assert.assertTrue(validationResponse.getSchemaValidationMessages().get(0).getMessage().equals(INFO_MISSING_SCHEMA));
    }

    @Test
    public void testDebugValid20SpecByContent() throws Exception {
        final ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
        final JsonNode rootNode = mapper.readTree(Files.readAllBytes(java.nio.file.Paths.get(getClass().getResource(VALID_20_YAML).toURI())));
        ValidatorController validator = new ValidatorController();
        ResponseContext response = validator.reviewByContent(new RequestContext(), rootNode);

        // since inflector 2.0.3 content type is managed by inflector according to request headers and spec
/*
        Assert.assertEquals(APPLICATION, response.getContentType().getType());
        Assert.assertEquals(JSON, response.getContentType().getSubtype());
*/
        ValidationResponse validationResponse = (ValidationResponse) response.getEntity();
        Assert.assertTrue(validationResponse.getMessages() == null || validationResponse.getMessages().size() == 0);
        Assert.assertTrue(validationResponse.getSchemaValidationMessages() == null || validationResponse.getSchemaValidationMessages().size() == 0);
    }

    @Test
    public void testDebugValid30SpecByContent() throws Exception {
        final ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
        final JsonNode rootNode = mapper.readTree(Files.readAllBytes(java.nio.file.Paths.get(getClass().getResource(VALID_30_YAML).toURI())));
        ValidatorController validator = new ValidatorController();
        ResponseContext response = validator.reviewByContent(new RequestContext(), rootNode);

        // since inflector 2.0.3 content type is managed by inflector according to request headers and spec
/*
        Assert.assertEquals(APPLICATION, response.getContentType().getType());
        Assert.assertEquals(JSON, response.getContentType().getSubtype());
*/
        ValidationResponse validationResponse = (ValidationResponse) response.getEntity();
        Assert.assertTrue(validationResponse.getMessages() == null || validationResponse.getMessages().size() == 0);
        Assert.assertTrue(validationResponse.getSchemaValidationMessages() == null || validationResponse.getSchemaValidationMessages().size() == 0);
    }



    private boolean validateEquals(InputStream image1, InputStream image2) throws IOException {

        int i =  image1.read();
        while (-1 != i)
        {
            int y = image2.read();
            if (i != y)
            {
                return false;
            }
            i = image1.read();
        }

        int y = image2.read();
        return(y == -1);
    }

    private static int getDynamicPort() {
        return new Random().ints(10000, 20000).findFirst().getAsInt();
    }

}