package com.quarantyne.proxy;

import com.google.common.collect.Lists;
import com.google.common.hash.BloomFilter;
import com.quarantyne.assets.AssetException;
import com.quarantyne.assets.AssetRegistry;
import com.quarantyne.classifiers.HttpRequestClassifier;
import com.quarantyne.classifiers.MainClassifier;
import com.quarantyne.classifiers.impl.CompromisedPasswordClassifier;
import com.quarantyne.classifiers.impl.DisposableEmailClassifier;
import com.quarantyne.classifiers.impl.FastAgentClassifier;
import com.quarantyne.classifiers.impl.GeoDiscrepancyClassifier;
import com.quarantyne.classifiers.impl.IpRotationClassifier;
import com.quarantyne.classifiers.impl.LargeBodySizeClassifier;
import com.quarantyne.classifiers.impl.PublicCloudExecutionClassifier;
import com.quarantyne.classifiers.impl.SuspiciousRequestHeadersClassifier;
import com.quarantyne.classifiers.impl.SuspiciousUserAgentClassifier;
import com.quarantyne.config.ConfigArgs;
import com.quarantyne.config.ConfigRetrieverOptionsSupplier;
import com.quarantyne.config.ConfigSupplier;
import com.quarantyne.geoip4j.GeoIp4j;
import com.quarantyne.geoip4j.GeoIp4jImpl;
import com.quarantyne.proxy.verticles.AdminVerticle;
import com.quarantyne.proxy.verticles.ProxyVerticle;
import com.quarantyne.proxy.verticles.WarmupVerticle;
import com.quarantyne.util.BloomFilters;
import com.quarantyne.util.CidrMembership;
import io.netty.util.internal.logging.InternalLoggerFactory;
import io.netty.util.internal.logging.Slf4JLoggerFactory;
import io.vertx.core.DeploymentOptions;
import io.vertx.core.Vertx;
import io.vertx.core.VertxOptions;
import io.vertx.core.impl.cpu.CpuCoreSensor;
import io.vertx.ext.dropwizard.DropwizardMetricsOptions;
import java.util.List;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class Main {

  private static BloomFilter<String> weakOrBreachedPwBf = null;
  private static BloomFilter<String> disposableMxBf = null;
  private static CidrMembership<String> awsIpMembership = null;
  private static CidrMembership<String> gcpIpMembership = null;

  public static void main(String...args) {
    InternalLoggerFactory.setDefaultFactory(Slf4JLoggerFactory.INSTANCE);

    ConfigArgs configArgs = ConfigArgs.parse(args);

    // load assets or die
    try {
      weakOrBreachedPwBf = BloomFilters.deserialize(AssetRegistry.getCompromisedPasswords());
      disposableMxBf = BloomFilters.deserialize(AssetRegistry.getDisposableEmails());
      awsIpMembership = new CidrMembership<>(AssetRegistry.getAwsIps(), "aws");
      gcpIpMembership = new CidrMembership<>(AssetRegistry.getGcpIps(), "gcp");
    } catch (AssetException ex) {
      log.error("error while reading asset", ex);
      System.exit(-1);
    }

    final GeoIp4j geoIp4j = new GeoIp4jImpl();

    log.info("{} <= quarantyne => {}", configArgs.getIngress().toHuman(), configArgs.getEgress().toHuman());

    configArgs.getAdminIpPort().ifPresent(ipPort -> {
      log.info("==> admin @ http://{}:{}", ipPort.getIp(), ipPort.getPort());
    });

    log.info("see available options with --help");
    int numCpus = CpuCoreSensor.availableProcessors();

    VertxOptions vertxOptions = new VertxOptions();
    vertxOptions.setPreferNativeTransport(true);
    vertxOptions.setMetricsOptions(
        new DropwizardMetricsOptions().setEnabled(true)
    );

    log.debug("==> event loop size is {}", vertxOptions.getEventLoopPoolSize());
    log.debug("==> detected {} cpus core", numCpus);
    Vertx vertx = Vertx.vertx(vertxOptions);

    ConfigSupplier configSupplier;
    if (configArgs.getConfigFile().isPresent()) {
      configSupplier = new ConfigSupplier(vertx,
          new ConfigRetrieverOptionsSupplier(configArgs.getConfigFile().get()));
    } else {
      log.info("No configuration file was specified, using default settings");
      configSupplier = new ConfigSupplier();
    }

    // quarantyne classifiers
    List<HttpRequestClassifier> httpRequestClassifierList = Lists.newArrayList(
        new FastAgentClassifier(),
        new IpRotationClassifier(),
        new SuspiciousRequestHeadersClassifier(),
        new SuspiciousUserAgentClassifier(),
        new LargeBodySizeClassifier(),
        new CompromisedPasswordClassifier(weakOrBreachedPwBf, configSupplier),
        new DisposableEmailClassifier(disposableMxBf, configSupplier),
        new GeoDiscrepancyClassifier(geoIp4j, configSupplier),
        new PublicCloudExecutionClassifier(awsIpMembership, gcpIpMembership)
        // new SuspiciousLoginActivityClassifier(geoIp4j)
    );

    MainClassifier mainClassifier = new MainClassifier(httpRequestClassifierList);

    if (configArgs.getAdminIpPort().isPresent()) {
      vertx.deployVerticle(new AdminVerticle(configArgs.getAdminIpPort().get()));
    }

    vertx.deployVerticle(() -> new ProxyVerticle(configArgs, mainClassifier,
            configSupplier),
        new DeploymentOptions().setInstances(numCpus * 2 + 1));

    vertx.deployVerticle(() -> new WarmupVerticle(configArgs),
        new DeploymentOptions(),
        warmupVerticle -> {
          vertx.undeploy(warmupVerticle.result());
        });

    vertx.exceptionHandler(ex -> {
      log.error("uncaught exception", ex);
    });
  }

}