import * as dns from 'dns'
import * as net from 'net'
import * as Traceroute from 'nodejs-traceroute'

export async function checkHarEntries(entries, threshholds) {
  let allResults = "";
  for (const entry of entries) {
    try {
      let isProblematic = false;
      Object.keys(threshholds).forEach(threshhold => {
        if (entry.timings[threshhold] > threshholds[threshhold]) {
          isProblematic = true;
        }
      });
      if (isProblematic) {
        allResults += '\r\n\r\n' + entry.request.method + ' request TO: ' + entry.request.url;
        allResults += '\r\nAT: ' + entry.startedDateTime + ' with REQUESTID: ' + entry._requestId;
        allResults += '\r\nwith TIMINGS:';
        Object.keys(entry.timings).forEach(timing => {
          allResults += '\r\n  ' + timing + ': ' + entry.timings[timing]
        });
      }

    } catch (error) {
      if (entry._requestId) {
        console.error("Error in checkHarEntries occurred while processing har entry with id: " + entry._requestId)
      }
      console.error("Error occurred while processing har entry in checkHarEntries: " + error);
    }
  }
  if (allResults == "") {
    allResults = "NONE"
  }
  let intro = "Network Timings diagnostic explained:";
  intro += "\r\nAll timings are in milliseconds, if an entry doesn’t apply to a request it will contain undef.";
  intro += "-1 values mean that a connection already existed or dns was already known etc.";
  intro += "\r\n"
  intro += "\r\nblocked: time in milliseconds spent waiting for a network connection,";
  intro += " could indicate browser maximum tcp connections are all used, is waiting for proxy negotiation or browser is overloaded, etc.";
  intro += "\r\ndns: time spent in DNS resolution of the host name, could indicate network or dns server or dns entry issue";
  intro += "\r\nconnect: time spent making the TCP connection, could indicate network or server issue";
  intro += "\r\nsend: time spent sending the request to the server";
  intro += "\r\nwait: time spent waiting for a response from the server.";
  intro += "\r\nreceive: time spent reading the response from the server.";
  intro += "\r\nssl: time spent negotiating the SSL/TLS session.";
  intro += "\r\n_queued: request was postponed by the browser because it's considered lower priority (often happens with loading styles, images, etc.)";
  intro += "\r\n"
  intro += "\r\nentries that exceed the following threshholds (set in test config) will be shown:";
  Object.keys(threshholds).forEach(timing => {
    intro += '\r\n  ' + timing + ': ' + threshholds[timing]
  });
  intro += "\r\n";
  intro += "\r\nFOLLOWING NETWORK ENTRIES MAY TO HAVE SOME ISSUE: ";

  allResults = intro + allResults;

  return allResults;
};
export async function resolveAddress(hostname) {
  return new Promise((resolve, reject) => {
    dns.lookup(hostname, {"all": true}, (err, address, family) => {
      if (err) reject(err);
      resolve(address);
    });
  })
};
export async function isPortReachable(host, port) {
  var sock = new net.Socket();
  sock.setTimeout(2500);
  sock.on('connect', function () {
    console.log(host + ':' + port + ' is up.');
    sock.destroy();
  }).on('error', function (e) {
    console.log(host + ':' + port + ' is down: ' + e.message);
  }).on('timeout', function (e) {
    console.log(host + ':' + port + ' is down: timeout');
  }).connect(host, port);
};
export async function doTraceroute(hosts) {

  let tracePromises = [];
  hostsKeys = Object.keys(hosts);
  let allTraceResult = "\r\n\r\nPerforming TRACEROUTE on ip's defined in config:";
  for (const hostKey of hostsKeys) {
    tracePromises.push(new Promise(async (resolve, reject) => {
      let traceResult = "\r\n\r\nTraceroute to " + hostKey;
      try {
        const tracer = new Traceroute();
        tracer
          .on('pid', (pid) => {
            traceResult += "\r\n" + `pid: ${pid}`;
          })
          .on('destination', (destination) => {
            traceResult += "\r\n" + `destination: ${destination}`;
          })
          .on('hop', (hop) => {
            traceResult += "\r\n" + `hop: ${JSON.stringify(hop)}`;
          })
          .on('close', (code) => {
            traceResult += "\r\n" + `close: code ${code}`;
            resolve(traceResult);
          });

        tracer.trace(hosts[hostKey]);
      } catch (ex) {
        traceResult += "\r\n" + ex;
        reject(traceResult);
      }
    }));
  }
  let traceResults = await Promise.all(tracePromises);
  traceResults.forEach(trRes => {
    allTraceResult += trRes;
  });
  return allTraceResult;
}
export async function getNetworkInfo(networkEvents) {
  try {
    let requests = [];
    let filteredSent = networkEvents.filter(
      item => item.method && item.method == 'Network.requestWillBeSent'
    );
    let filteredReceived = networkEvents.filter(
      item => item.method && item.method == 'Network.responseReceived'
    )
    filteredSent.forEach(sentEvent => {
      let response = filteredReceived.find(item => item.params.requestId == sentEvent.params.requestId);
      let url = new URL(sentEvent.params.request.url);
      if (url.host === "") {
        url = new URL(sentEvent.params.documentURL);
      }
      let urlPort = +url.port;
      if (urlPort < 1) {
        switch (url.protocol) {
          case "http:":
            urlPort = 80;
            break;
          case "https:":
            urlPort = 443;
            break;
          default:
            urlPort = null;
        }
      }
      let request = {
        "serverName": url.host.replace(`:${url.port}`, ""),
        "port": urlPort,
      }
      if (response) {
        if (response.params && response.params.response) {
          if (response.params.response.remoteIPAddress) {
            request.responseIpAddress = response.params.response.remoteIPAddress;
            if (response.params.response.remotePort && request.port !== +response.params.response.remotePort) {
              request.responsePort = response.params.response.remotePort;
            }
          }
          if (response.params.response.url) {
            let remoteHost = (new URL(response.params.response.url)).host;
            if (remoteHost !== url.host) {
              if (response.params.response.remotePort) {
                request.responseHost = remoteHost.replace(`:${response.params.response.remotePort}`, "");
              } else {
                request.responseHost = remoteHost;
              }
            }
          }
        }
      }
      if (request.responseIpAddress) {
        if (!(requests.find(item =>
          item.serverName == request.serverName &&
          item.port == request.port &&
          item.responseIpAddress == request.responseIpAddress &&
          item.responsePort == request.responsePort
        ))) {
          requests.push(request)
        }
      } else {
        if (!(requests.find(item => item.serverName == request.serverName && item.requestPort == request.requestPort))) {
          requests.push(request)
        }
      }
    });
    return requests;
  } catch (error) {
    console.log("Error in networkUtils.saveNetworkInfo: " + error.message)
  }
};

