package com.mesosphere.velocity.marathon.impl;

import com.google.gson.JsonSyntaxException;
import com.mesosphere.velocity.marathon.TestUtils;
import com.mesosphere.velocity.marathon.exceptions.AuthenticationException;
import com.mesosphere.velocity.marathon.fields.MarathonLabel;
import com.mesosphere.velocity.marathon.fields.MarathonUri;
import com.mesosphere.velocity.marathon.fields.MarathonVars;
import com.mesosphere.velocity.marathon.interfaces.AppConfig;
import com.mesosphere.velocity.marathon.interfaces.MarathonBuilder;
import net.sf.json.JSONObject;
import okhttp3.mockwebserver.MockWebServer;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import static org.junit.Assert.*;

public class MarathonBuilderImplTest {
    private MockWebServer httpServer;

    @Before
    public void setUp() throws IOException {
        httpServer = new MockWebServer();
        httpServer.start();
    }

    @After
    public void tearDown() throws IOException {
        httpServer.shutdown();
        httpServer = null;
    }

    /**
     * Test that the content of the allfields fixture is properly sent to the remote server.
     *
     * @throws IOException             when IO issues occur
     * @throws AuthenticationException when authentication fails
     * @throws InterruptedException    when retries are interrupted
     */
    @Test
    public void testAllMarathonFields() throws IOException, AuthenticationException, InterruptedException {
        MockConfig config  = new MockConfig();
        String     payload = TestUtils.loadFixture("allfields.json");
        JSONObject json    = JSONObject.fromObject(payload);
        config.url = TestUtils.getHttpAddresss(httpServer);

        TestUtils.enqueueJsonResponse(httpServer, "{}");
        new MarathonBuilderImpl(config)
                .setJson(json)
                .build()
                .update();
        final JSONObject jsonRequest = TestUtils.jsonFromRequest(httpServer);
        assertEquals("JSON objects are different", json, jsonRequest);

        // secrets
        final JSONObject secrets = jsonRequest.getJSONObject("secrets");
        final JSONObject secret3 = secrets.getJSONObject("secret3");
        assertEquals("Wrong source for secret3", "/foo2", secret3.getString("source"));

        // secrets in env
        final JSONObject env            = jsonRequest.getJSONObject("env");
        final String     actualPassword = env.getJSONObject("PASSWORD").getString("secret");
        final String     actualXPS2     = env.getString("XPS2");
        assertEquals("Invalid value for PASSWORD", "/db/password", actualPassword);
        assertEquals("Invalid value for XPS2", "Rest", actualXPS2);
    }

    /**
     * Test that a JSON configuration without any URIs does not throw an error.
     */
    @Test
    public void testNoUris() throws IOException {
        final String     jsonString = TestUtils.loadFixture("idonly.json");
        final JSONObject json       = JSONObject.fromObject(jsonString);
        final MockConfig config     = new MockConfig();

        MarathonBuilder builder = new MarathonBuilderImpl(config).setJson(json).build();
        assertNull("URIs should be null if none were in the JSON config", builder.getApp().getUris());
    }

    /**
     * Test that existing URIs are not deleted or overwritten on subsequence builds.
     */
    @Test
    public void testExistingUris() throws IOException {
        final String     jsonString = TestUtils.loadFixture("uris.json");
        final JSONObject json       = JSONObject.fromObject(jsonString);

        MarathonBuilder builder = new MarathonBuilderImpl(new MockConfig()).setJson(json).build();
        assertEquals(2, builder.getJson().getJSONArray("uris").size());
        assertEquals(2, builder.getApp().getUris().size());
        assertEquals("https://foo.com/setup.py", builder.getApp().getUris().iterator().next());
    }

    /**
     * Test that an invalid "uris" format causes a JSON exception. The "uris" field should
     * be an array.
     */
    @Test
    public void testInvalidTypeUris() {
        final String     jsonString = "{\"id\": \"testid\", \"uris\": \"http://example.com/artifact\"}";
        final JSONObject json       = JSONObject.fromObject(jsonString);
        final MockConfig config     = new MockConfig();

        try {
            new MarathonBuilderImpl(config).setJson(json).build();
            assertTrue("Should throw json parse exception", false);
        } catch (JsonSyntaxException jse) {
            assertTrue(true);
        }
    }

    /**
     * Test that existing "env" section is not deleted or overwritten on subsequence builds.
     */
    @Test
    public void testExistingEnv() throws IOException {
        final String     jsonString = TestUtils.loadFixture("env.json");
        final JSONObject json       = JSONObject.fromObject(jsonString);
        final MockConfig config     = new MockConfig();

        // add to the env
        config.env.add(new MarathonVars("example", "test"));
        // build
        MarathonBuilder builder = new MarathonBuilderImpl(config).setJson(json).build();

        assertEquals("bar", builder.getApp().getEnv().get("foo"));
        assertEquals("buzz", builder.getApp().getEnv().get("fizz"));
        assertEquals("test", builder.getApp().getEnv().get("example"));
    }


    /**
     * Test container type detection within template JSON
     */
    @Test
    public void testSimpleContainerTypeDetection() throws IOException {
        final String     jsonString = TestUtils.loadFixture("mesos-containertype.json");
        final JSONObject json       = JSONObject.fromObject(jsonString);
        final MockConfig config     = new MockConfig();

        MarathonBuilder builder = new MarathonBuilderImpl(config).setJson(json).build();

        assertEquals("MESOS", builder.getApp().getContainer().getType());
    }


    /**
     * Test container type setup through configuration
     */
    @Test
    public void testContainerTypeConfiguration() throws IOException {
        final String     jsonString = TestUtils.loadFixture("idonly.json");
        final JSONObject json       = JSONObject.fromObject(jsonString);
        final MockConfig config     = new MockConfig();

        config.docker = "test";
        config.containerType = "MESOS";

        MarathonBuilder builder = new MarathonBuilderImpl(config).setJson(json).build();

        assertEquals("MESOS", builder.getApp().getContainer().getType());
    }

    /**
     * Test default container type
     */
    @Test
    public void testDefaultContainerType() throws IOException {
        final String     jsonString = TestUtils.loadFixture("idonly.json");
        final JSONObject json       = JSONObject.fromObject(jsonString);
        final MockConfig config     = new MockConfig();

        config.docker = "test";

        MarathonBuilder builder = new MarathonBuilderImpl(config).setJson(json).build();

        assertEquals("DOCKER", builder.getApp().getContainer().getType());
    }

    /**
     * Test that an empty env section can be added to without issues.
     */
    @Test
    public void testNoEnv() throws IOException {
        final String     jsonString = TestUtils.loadFixture("idonly.json");
        final JSONObject json       = JSONObject.fromObject(jsonString);
        final MockConfig config     = new MockConfig();
        MarathonBuilder  builder    = new MarathonBuilderImpl(config).setJson(json).build();
        assertNull("Env should be null", builder.getApp().getEnv());

        config.env.add(new MarathonVars("foo", "bar"));
        builder = builder.build();
        assertEquals("foo not set correctly", "bar", builder.getApp().getEnv().get("foo"));
    }


    static class MockConfig implements AppConfig {
        String              url;
        String              appId;
        boolean             forceUpdate;
        String              docker;
        String              containerType;
        boolean             dockerForcePull;
        String              credentialsId;
        List<MarathonUri>   uris;
        List<MarathonLabel> labels;
        List<MarathonVars>  env;

        MockConfig() {
            uris = new ArrayList<>();
            labels = new ArrayList<>();
            env = new ArrayList<>();
        }

        @Override
        public String getAppId() {
            return appId;
        }

        @Override
        public String getUrl() {
            return url;
        }

        @Override
        public String getContainerType() {
            return containerType;
        }

        @Override
        public boolean getForceUpdate() {
            return forceUpdate;
        }

        @Override
        public String getDocker() {
            return docker;
        }

        @Override
        public boolean getDockerForcePull() {
            return dockerForcePull;
        }

        @Override
        public String getCredentialsId() {
            return credentialsId;
        }

        @Override
        public List<MarathonUri> getUris() {
            return uris;
        }

        @Override
        public List<MarathonLabel> getLabels() {
            return labels;
        }

        @Override
        public List<MarathonVars> getEnv() {
            return env;
        }
    }
}