/*-
 * -\-\-
 * Helios Testing Library
 * --
 * Copyright (C) 2016 Spotify AB
 * --
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * -/-/-
 */

package com.spotify.helios.testing;

import static com.google.common.base.Charsets.UTF_8;
import static com.google.common.collect.Iterables.getOnlyElement;
import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.hamcrest.Matchers.contains;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.experimental.results.PrintableResult.testResult;
import static org.junit.experimental.results.ResultMatchers.isSuccessful;

import com.google.common.base.Optional;
import com.spotify.helios.common.Json;
import com.spotify.helios.common.descriptors.Deployment;
import com.spotify.helios.common.descriptors.Job;
import com.spotify.helios.common.descriptors.JobId;
import com.spotify.helios.common.descriptors.JobStatus;
import com.spotify.helios.testing.descriptors.TemporaryJobEvent;
import java.io.File;
import java.net.Socket;
import java.nio.file.Files;
import java.util.Map;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;

public class SimpleTest extends TemporaryJobsTestBase {

  @ClassRule
  public static final TemporaryFolder REPORT_DIR = new TemporaryFolder();

  @Test
  public void simpleTest() throws Exception {
    assertThat(testResult(SimpleTestImpl.class), isSuccessful());
    assertTrue("jobs are running that should not be",
        client.jobs().get(15, SECONDS).isEmpty());

    // Ensure test reports were written and everything was successful
    final File[] reportFiles = REPORT_DIR.getRoot().listFiles();
    if (reportFiles == null) {
      fail();
    }
    assertNotEquals(reportFiles.length, 0);

    for (final File reportFile : reportFiles) {
      final byte[] testReport = Files.readAllBytes(reportFile.toPath());
      final TemporaryJobEvent[] events = Json.read(testReport, TemporaryJobEvent[].class);

      for (final TemporaryJobEvent event : events) {
        assertTrue(event.isSuccess());
      }
    }
  }

  public static class SimpleTestImpl {

    @Rule
    public final TemporaryJobs temporaryJobs = temporaryJobsBuilder()
        .client(client)
        .prober(new TestProber())
        .jobDeployedMessageFormat(
            "Logs Link: http://${host}:8150/${name}%3A${version}%3A${hash}?cid=${containerId}")
        .jobPrefix(Optional.of(testTag).get())
        .deployTimeoutMillis(MINUTES.toMillis(3))
        .testReportDirectory(REPORT_DIR.getRoot().getAbsolutePath())
        .build();

    private TemporaryJob job1;

    @Before
    public void setup() {
      job1 = temporaryJobs.job()
          .command("nc", "-p", "4711", "-lle", "cat")
          .port("echo", 4711)
          .deploy(testHost1);
    }

    @Test
    public void testDeployment() throws Exception {
      // Verify that it is possible to deploy additional jobs during test
      temporaryJobs.job()
          .command(IDLE_COMMAND)
          .host(testHost1)
          .deploy();

      final Map<JobId, Job> jobs = client.jobs().get(15, SECONDS);
      assertEquals("wrong number of jobs running", 2, jobs.size());
      for (final Job job : jobs.values()) {
        assertEquals("wrong job running", BUSYBOX, job.getImage());
      }

      //verify address and addresses return valid HostAndPort objects
      assertEquals("wrong host", testHost1, job1.address("echo").getHost());
      assertEquals("wrong host", testHost1, getOnlyElement(job1.addresses("echo")).getHost());

      ping(DOCKER_HOST.address(), job1.port(testHost1, "echo"));
    }

    @Test
    public void testRandomHost() throws Exception {
      temporaryJobs.job()
          .command("sh", "-c", "while :; do sleep 5; done")
          .deploy();

      final Map<JobId, Job> jobs = client.jobs().get(15, SECONDS);
      assertEquals("wrong number of jobs running", 2, jobs.size());
      for (final Job job : jobs.values()) {
        assertEquals("wrong job running", BUSYBOX, job.getImage());
      }

      ping(DOCKER_HOST.address(), job1.port(testHost1, "echo"));
    }

    @Test
    public void testSpecificHost() throws Exception {
      final TemporaryJob job = temporaryJobs.job()
          .command(IDLE_COMMAND)
          .hostFilter(testHost2)
          .deploy();

      final JobStatus status = client.jobStatus(job.job().getId()).get(15, SECONDS);
      final Map<String, Deployment> deployments = status.getDeployments();
      assertThat(deployments.keySet(), contains(testHost2));
    }

    @Test
    public void testManualUndeploy() throws Exception {
      final TemporaryJob job = temporaryJobs.job()
          .command(IDLE_COMMAND)
          .deploy();

      job.undeploy();

      final JobStatus status = client.jobStatus(job.job().getId()).get(15, SECONDS);
      assertNull("job still exists", status);
    }

    @Test
    public void testDefaultLocalHostFilter() throws Exception {
      temporaryJobs.job()
          .command("sh", "-c", "while :; do sleep 5; done")
          .deploy();
    }

    @Test(expected = AssertionError.class)
    public void testExceptionWithBadHostFilter() throws Exception {
      // Shouldn't be able to deploy if filter doesn't match any hosts
      temporaryJobs.job()
          .command("sh", "-c", "while :; do sleep 5; done")
          .hostFilter("THIS_FILTER_SHOULDNT_MATCH_ANY_HOST")
          .deploy();
    }

    @Test
    public void testImageFromBuild() {
      temporaryJobs.job()
          .image(BUSYBOX)
          .command("sh", "-c", "while :; do sleep 5; done")
          .deploy();
    }

    @Test
    public void testVolume() {
      temporaryJobs.job()
          .volume(System.getProperty("user.dir") + "/helios-testing/src/test/resources/helios.conf",
              "/helios.conf")
          .command("sh", "-c", "while :; do sleep 5; done")
          .deploy();
    }

    private void ping(final String host, final int port) throws Exception {
      try (final Socket s = new Socket(host, port)) {
        final byte[] ping = "ping".getBytes(UTF_8);
        s.getOutputStream().write(ping);
        final byte[] pong = new byte[4];
        final int n = s.getInputStream().read(pong);
        assertEquals(4, n);
        assertArrayEquals(ping, pong);
      }
    }
  }


}
