/*-
 * -\-\-
 * Helios Tools
 * --
 * Copyright (C) 2016 - 2017 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.cli.command;

import static com.spotify.helios.common.descriptors.PortMapping.TCP;
import static com.spotify.helios.common.descriptors.PortMapping.UDP;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;

import com.google.common.collect.ImmutableList;
import com.spotify.helios.cli.command.PortMappingParser.PortMappingWithName;
import com.spotify.helios.common.descriptors.PortMapping;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;

public class PortMappingParserTest {

  @Rule
  public ExpectedException expectedException = ExpectedException.none();

  private static final List<TestData> GOOD_SPECS = ImmutableList.of(
      TestData.of("http=8080:80", "http", PortMapping.of(8080, 80, TCP)),
      TestData.of("foo=4711:80/tcp", "foo", PortMapping.of(4711, 80, TCP)),
      TestData.of("dns=53:53/udp", "dns", PortMapping.of(53, 53, UDP)),
      TestData.of("bar=4711", "bar", PortMapping.of(4711, null, TCP)),
      TestData.of("smtp=0.0.0.0:53:53/udp", "smtp", PortMapping.builder()
          .ip("0.0.0.0")
          .internalPort(53)
          .externalPort(53)
          .protocol(UDP)
          .build()),
      TestData.of("mail=127.0.0.1:23:23/tcp", "mail", PortMapping.builder()
          .ip("127.0.0.1")
          .internalPort(23)
          .externalPort(23)
          .protocol(TCP)
          .build()),
      TestData.of("qux=10.99.0.1:1001:1002", "qux", PortMapping.builder()
          .ip("10.99.0.1")
          .internalPort(1001)
          .externalPort(1002)
          .protocol(TCP)
          .build())
  );

  private static final List<TestData> BAD_SPECS = ImmutableList.of(
      TestData.partial("smtp=localhost:53:53/udp"),
      TestData.partial("http8080:80"),
      TestData.partial("foo=80:4711:80/tcp"),
      TestData.partial("=53:53/udp"),
      TestData.partial("foo")
  );

  @Test
  public void testParsePortMappingGoodSpecs() throws Exception {
    for (final TestData d : GOOD_SPECS) {
      final PortMappingWithName mappingWithName = PortMappingParser.parsePortMapping(d.getSpec());
      final PortMapping portMapping = mappingWithName.portMapping();
      assertThat(mappingWithName.name(), equalTo(d.getName()));
      assertThat(portMapping.getInternalPort(), equalTo(d.getPortMapping().getInternalPort()));
      assertThat(portMapping.getExternalPort(), equalTo(d.getPortMapping().getExternalPort()));
      assertThat(portMapping.getProtocol(), equalTo(d.getPortMapping().getProtocol()));
    }
  }

  @Test
  public void testParsePortMappingBadSpecs() throws Exception {
    for (final TestData d : BAD_SPECS) {
      expectedException.expect(IllegalArgumentException.class);
      expectedException.expectMessage("Bad port mapping: " + d.getSpec());
      PortMappingParser.parsePortMapping(d.getSpec());
    }
  }

  @Test
  public void testParsePortMappingBadIp() throws Exception {
    expectedException.expect(IllegalArgumentException.class);
    expectedException.expectMessage("999.999.999.999 is not a valid IP address.");
    final TestData d = TestData.partial("mail=999.999.999.999:23:23/tcp");
    PortMappingParser.parsePortMapping(d.getSpec());
  }

  @Test
  public void testParsePortMappings() throws Exception {
    final Map<String, PortMapping> mappings =
        PortMappingParser.parsePortMappings(testDataToSpecs(GOOD_SPECS));
    final Map<String, PortMapping> expectedMappings = new HashMap<>();
    for (final TestData d : GOOD_SPECS) {
      expectedMappings.put(d.getName(), d.getPortMapping());
    }
    assertThat(mappings, equalTo(expectedMappings));
  }

  @Test
  public void testParsePortMappingsDuplicateName() throws Exception {
    final ImmutableList<TestData> specs = ImmutableList.<TestData>builder()
        .addAll(GOOD_SPECS)
        .add(GOOD_SPECS.get(0))
        .build();
    expectedException.expect(IllegalArgumentException.class);
    expectedException.expectMessage("Duplicate port mapping name: " + GOOD_SPECS.get(0).getName());
    PortMappingParser.parsePortMappings(testDataToSpecs(specs));
  }

  private List<String> testDataToSpecs(final List<TestData> testData) {
    final ImmutableList.Builder<String> specs = ImmutableList.builder();
    for (final TestData d : testData) {
      specs.add(d.getSpec());
    }
    return specs.build();
  }

  private static class TestData {

    private final String spec;
    private final String name;
    private final PortMapping portMapping;

    private TestData(final String spec, final String name, final PortMapping portMapping) {
      this.spec = spec;
      this.name = name;
      this.portMapping = portMapping;
    }

    static TestData partial(final String spec) {
      return new TestData(spec, null, null);
    }

    public static TestData of(final String spec, final String name, final PortMapping portMapping) {
      return new TestData(spec, name, portMapping);
    }

    String getSpec() {
      return spec;
    }

    String getName() {
      return name;
    }

    PortMapping getPortMapping() {
      return portMapping;
    }
  }
}
