/*
 * SonarQube
 * Copyright (C) 2009-2023 SonarSource SA
 * mailto:info AT sonarsource DOT com
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package org.sonar.process;

import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import org.hamcrest.CoreMatchers;
import org.junit.Test;

import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assume.assumeThat;

public class NetworkUtilsImplTest {


  private NetworkUtilsImpl underTest = new NetworkUtilsImpl();

  @Test
  public void getNextAvailablePort_returns_a_port() throws Exception {
    String localhost = InetAddress.getLocalHost().getHostName();
    int port = underTest.getNextAvailablePort(localhost).getAsInt();
    assertThat(port)
      .isGreaterThan(1_023)
      .isLessThanOrEqualTo(65_535);
  }

  @Test
  public void getNextAvailablePort_does_not_return_twice_the_same_port() throws Exception {
    String localhost = InetAddress.getLocalHost().getHostName();
    Set<Integer> ports = new HashSet<>(Arrays.asList(
      underTest.getNextAvailablePort(localhost).getAsInt(),
      underTest.getNextAvailablePort(localhost).getAsInt(),
      underTest.getNextAvailablePort(localhost).getAsInt()));
    assertThat(ports).hasSize(3);
  }

  @Test
  public void getLocalNonLoopbackIpv4Address_returns_a_valid_local_and_non_loopback_ipv4() {
    Optional<InetAddress> address = underTest.getLocalNonLoopbackIpv4Address();

    // address is empty on offline builds
    assumeThat(address.isPresent(), CoreMatchers.is(true));

    assertThat(address.get()).isInstanceOf(Inet4Address.class);
    assertThat(address.get().isLoopbackAddress()).isFalse();
  }

  @Test
  public void getHostname_returns_hostname_of_localhost_otherwise_a_constant() {
    try {
      InetAddress localHost = InetAddress.getLocalHost();
      assertThat(underTest.getHostname()).isEqualTo(localHost.getHostName());
    } catch (UnknownHostException e) {
      // no localhost on host running the UT
      assertThat(underTest.getHostname()).isEqualTo("unresolved hostname");
    }
  }

  @Test
  public void getLocalInetAddress_filters_local_addresses() {
    InetAddress address = underTest.getLocalInetAddress(InetAddress::isLoopbackAddress).get();
    assertThat(address.isLoopbackAddress()).isTrue();
  }

  @Test
  public void getLocalInetAddress_returns_empty_if_no_local_addresses_match() {
    Optional<InetAddress> address = underTest.getLocalInetAddress(a -> false);
    assertThat(address).isEmpty();
  }

  @Test
  public void toInetAddress_supports_host_names() {
    assertThat(underTest.toInetAddress("localhost")).isNotEmpty();
    // do not test values that require DNS calls. Build must support offline mode.
  }

  @Test
  public void toInetAddress_supports_ipv4() {
    assertThat(underTest.toInetAddress("1.2.3.4")).isNotEmpty();
  }

  @Test
  public void toInetAddress_supports_ipv6() {
    assertThat(underTest.toInetAddress("2a01:e34:ef1f:dbb0:c2f6:a978:c5c0:9ccb")).isNotEmpty();
    assertThat(underTest.toInetAddress("[2a01:e34:ef1f:dbb0:c2f6:a978:c5c0:9ccb]")).isNotEmpty();
  }

  @Test
  public void toInetAddress_returns_empty_on_unvalid_IP_and_hostname() {
    assertThat(underTest.toInetAddress(randomAlphabetic(32))).isEmpty();
  }

  @Test
  public void isLoopback_returns_true_on_loopback_address_or_host() {
    InetAddress loopback = InetAddress.getLoopbackAddress();

    assertThat(underTest.isLoopback(loopback.getHostAddress())).isTrue();
    assertThat(underTest.isLoopback(loopback.getHostName())).isTrue();
  }

  @Test
  public void isLoopback_returns_true_on_localhost_address_or_host_if_loopback() {
    try {
      InetAddress localHost = InetAddress.getLocalHost();
      boolean isLoopback = localHost.isLoopbackAddress();
      assertThat(underTest.isLoopback(localHost.getHostAddress())).isEqualTo(isLoopback);
      assertThat(underTest.isLoopback(localHost.getHostName())).isEqualTo(isLoopback);
    } catch (UnknownHostException e) {
      // ignore, host running the test has no localhost
    }
  }

  @Test
  public void isLocal_returns_true_on_loopback_address_or_host() {
    InetAddress loopback = InetAddress.getLoopbackAddress();

    assertThat(underTest.isLocal(loopback.getHostAddress())).isTrue();
    assertThat(underTest.isLocal(loopback.getHostName())).isTrue();
  }

  @Test
  public void isLocal_returns_true_on_localhost_address_or_host() {
    try {
      InetAddress localHost = InetAddress.getLocalHost();

      assertThat(underTest.isLocal(localHost.getHostAddress())).isTrue();
      assertThat(underTest.isLocal(localHost.getHostName())).isTrue();
    } catch (UnknownHostException e) {
      // ignore, host running the test has no localhost
    }
  }
}
