package de.ivu.elect.business.security.control;

import static java.util.Arrays.asList;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Serializable;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

import org.apache.commons.lang3.SystemUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class NlAirGap implements Serializable {

  private static final long serialVersionUID = 1L;

  private static final Logger LOG = LoggerFactory.getLogger(NlAirGap.class);

  private static final List<String> PING_ADRESSEN_V4 = asList("83.137.17.164", "informatiebeveiligingsdienst.nl", "145.100.190.243", "surfnet.nl"); // NOSONAR ELECTVAPP-362, Adressen vorgegeben...

  private static final List<String> PING_ADRESSEN_V6 = asList("2001:4038:200:43::164", "2001:610:508:108:192:87:108:15", "2001:610:188:410:145:100:190:243", "informatiebeveiligingsdienst.nl", "surfnet.nl"); // NOSONAR ELECTVAPP-751, Adressen vorgegeben...

  private static final List<String> HTTP_ADRESSEN_ALL = asList("ncsc.nl", "178.22.85.149", "178.22.85.148", "178.22.85.147", "178.22.85.146", "informatiebeveiligingsdienst.nl", "83.137.17.164", "kiesraad.nl", "178.22.85.146", "178.22.85.148", "178.22.85.149", "178.22.85.147", "rijksoverheid.nl", "178.22.85.11", "178.22.85.9", "178.22.85.10", "178.22.85.8", "vng.nl", "185.135.12.253", "logius.nl", "46.22.185.32", "digitoegangelijk.nl", "46.22.185.32", "knmi.nl", "145.23.253.47", "surfnet.nl", "145.100.190.243", "192.87.108.15"); // NOSONAR ELECTVAPP-362, Adressen vorgegeben...

  private static final List<String> HTTP_ADRESSEN_WORKING = asList("ncsc.nl", "informatiebeveiligingsdienst.nl", "83.137.17.164", "kiesraad.nl", "rijksoverheid.nl", "vng.nl", "185.135.12.253", "logius.nl", "46.22.185.32", "knmi.nl", "145.23.253.47", "surfnet.nl"); // NOSONAR ELECTVAPP-362, Adressen vorgegeben...

  private static final List<String> HTTP_ADRESSEN_ROBOTS_WORKING = asList("ncsc.nl", "informatiebeveiligingsdienst.nl", "kiesraad.nl", "rijksoverheid.nl", "vng.nl", "logius.nl", "knmi.nl", "surfnet.nl"); // NOSONAR ELECTVAPP-362, Adressen vorgegeben...

  private static final Set<Integer> ALLOWED_RESPONSE_CODES = new HashSet<>(asList(200, 301, 302));

  public static final int PROCESS_TIMEOUT_IN_SECONDS = 5;

  public static final int HTTP_TIMEOUT_IN_SECONDS = 2;

  public boolean isAirGapPresent() {
    boolean pingLocalHostWorks = false;
    try {
      pingLocalHostWorks = isReachablePingV4("127.0.0.1");
    } catch (Exception e) {
      // ignore
    }

    if (!pingLocalHostWorks) {
      try {
        pingLocalHostWorks = isReachablePingV6("::1");
      } catch (Exception e) {
        // ignore
      }
    }

    AtomicLong anzahlFehlerfrei = new AtomicLong(0L);
    List<Throwable> fehler = new ArrayList<>();

    if (pingLocalHostWorks) {
      for (String adresse : PING_ADRESSEN_V4) {
        if (isReachablePingV4(adresse, anzahlFehlerfrei, fehler)) {
          return false;
        }
      }
      for (String adresse : PING_ADRESSEN_V6) {
        if (isReachablePingV6(adresse, anzahlFehlerfrei, fehler)) {
          return false;
        }
      }
    }

    for (String adresse : HTTP_ADRESSEN_WORKING) {
      if (isReachableHttpPing(adresse, anzahlFehlerfrei, fehler)) {
        return false;
      }
    }

    for (String adresse : HTTP_ADRESSEN_ROBOTS_WORKING) {
      if (isDownloadeabeRobotsTxt(adresse, anzahlFehlerfrei, fehler)) {
        return false;
      }
    }

    if (anzahlFehlerfrei.get() > 0) {
      return true; // mindestens ein Aufruf war fehlerfrei > keine Internetverbindung vorhanden
    }

    if (fehler.size() > 0) {
      throw new RuntimeException(fehler.get(0).getMessage(), fehler.get(0));
    }

    return true; // cannot happen
  }

  private boolean isReachablePingV4(String ipAddress, AtomicLong anzahlFehlerfrei, List<Throwable> fehler) {
    return isReachablePing46(ipAddress, anzahlFehlerfrei, fehler, false);
  }

  private boolean isReachablePingV6(String ipAddress, AtomicLong anzahlFehlerfrei, List<Throwable> fehler) {
    return isReachablePing46(ipAddress, anzahlFehlerfrei, fehler, true);
  }

  private boolean isReachablePing46(String ipAddress, AtomicLong anzahlFehlerfrei, List<Throwable> fehler, boolean ipV6) {
    try {
      boolean result = isReachablePing46(ipAddress, ipV6);
      anzahlFehlerfrei.addAndGet(1L);
      if (result) {
        LOG.info("Ping successful: {}", ipAddress);
      }
      return result;
    } catch (Exception e) {
      fehler.add(e);
    }
    return false;
  }

  private boolean isReachableHttpPing(String ipAddress, AtomicLong anzahlFehlerfrei, List<Throwable> fehler) {
    try {
      boolean result = pingURL(ipAddress);
      anzahlFehlerfrei.addAndGet(1L);
      if (result) {
        LOG.info("Http-Ping successful: {}", ipAddress);
      }
      return result;
    } catch (Exception e) {
      fehler.add(e);
    }
    return false;
  }

  private boolean isDownloadeabeRobotsTxt(String ipAddress, AtomicLong anzahlFehlerfrei, List<Throwable> fehler) {
    try {
      boolean result = downloadRobotsTxt(ipAddress);
      anzahlFehlerfrei.addAndGet(1L);
      if (result) {
        LOG.info("Download-robots.txt successful: {}", ipAddress);
      }
      return result;
    } catch (Exception e) {
      fehler.add(e);
    }
    return false;
  }

  public boolean isReachablePingV4(String ipAddress) {
    return isReachablePing46(ipAddress, false);
  }
  public boolean isReachablePingV6(String ipAddress) {
    return isReachablePing46(ipAddress, true);
  }

  private boolean isReachablePing46(String ipAddress, boolean ipV6) {
    List<String> command = buildCommand(ipAddress, ipV6);
    ProcessBuilder processBuilder = new ProcessBuilder(command);
    try {
      processBuilder.redirectErrorStream(true);
      Process process = processBuilder.start();
      process.waitFor(PROCESS_TIMEOUT_IN_SECONDS, TimeUnit.SECONDS);
      int exitValue = process.exitValue();
      if (exitValue == 0) {
        try(BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))){
          StringBuilder builder = new StringBuilder();
          String line;
          while ( (line = reader.readLine()) != null) {
            builder.append(line);
            builder.append(System.getProperty("line.separator"));
          }

          String output = builder.toString();
          if (output.contains("ttl=") || output.contains("TTL=")/*ELECT-10561 Windows hat "erfolgreiche" Pings ohne Netzwerkkabel*/) {
            LOG.info("Ping successful, command: {}", command);
            LOG.info("Output: {}", builder);
            return true;
          }
        } catch (Exception ignored) {
        }
      }
      return false;
    } catch (InterruptedException | IOException ex) { // NOSONAR
      throw new RuntimeException(ex.getMessage(), ex);
    }
  }

  private List<String> buildCommand(String ipAddress, boolean ipV6) {
    List<String> command = new ArrayList<>();
    command.add("ping");

    if (ipV6) {
      command.add("-6");
    }
    if (SystemUtils.IS_OS_WINDOWS) {
      command.add("-n");
    } else if (SystemUtils.IS_OS_UNIX) {
      command.add("-c");
    } else {
      throw new UnsupportedOperationException("Unsupported operating system");
    }

    command.add("1");
    command.add(ipAddress);

    return command;
  }

  public static boolean pingURL(String address) {
    String url = "http://" + address;
    int timeout = HTTP_TIMEOUT_IN_SECONDS * 1000;
    try {
      HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
      connection.setConnectTimeout(timeout);
      connection.setReadTimeout(timeout);
      connection.setRequestMethod("HEAD");
      return ALLOWED_RESPONSE_CODES.contains(connection.getResponseCode());
    } catch (IOException exception) {
      return false;
    }
  }

  public static boolean downloadRobotsTxt(String address) {
    String url = "https://" + address + "/robots.txt";
    int timeout = HTTP_TIMEOUT_IN_SECONDS * 1000;
    try {
      HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
      connection.setConnectTimeout(timeout);
      connection.setReadTimeout(timeout);
      connection.setRequestMethod("HEAD");
      return connection.getResponseCode() == 200;
    } catch (IOException exception) {
      return false;
    }
  }

//  public static void main(String[] args) {
//    NlAirGap nlAirGap = new NlAirGap();
//    while (true) {
//      System.out.println(nlAirGap.isReachablePingV6("::1"));
//    }
//  }

}
