/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 org.apache.lucene.replicator;

import com.carrotsearch.randomizedtesting.annotations.ThreadLeakLingering;
import java.util.Random;
import org.apache.http.conn.HttpClientConnectionManager;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.lucene.tests.util.LuceneTestCase;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.SecureRequestCustomizer;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.SslConnectionFactory;
import org.eclipse.jetty.server.session.DefaultSessionIdManager;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.junit.AfterClass;

@ThreadLeakLingering(linger = 80000) // Jetty might ignore interrupt for a minute
public abstract class ReplicatorTestCase extends LuceneTestCase {

  private static HttpClientConnectionManager clientConnectionManager;

  @AfterClass
  public static void afterClassReplicatorTestCase() throws Exception {
    if (clientConnectionManager != null) {
      clientConnectionManager.shutdown();
      clientConnectionManager = null;
    }
  }

  /**
   * Returns a new {@link Server HTTP Server} instance. To obtain its port, use {@link
   * #serverPort(Server)}.
   */
  public static synchronized Server newHttpServer(Handler handler) throws Exception {
    // if this property is true, then jetty will be configured to use SSL
    // leveraging the same system properties as java to specify
    // the keystore/truststore if they are set
    //
    // This means we will use the same truststore, keystore (and keys) for
    // the server as well as any client actions taken by this JVM in
    // talking to that server, but for the purposes of testing that should
    // be good enough
    final boolean useSsl = Boolean.getBoolean("tests.jettySsl");
    final SslContextFactory.Server sslcontext = new SslContextFactory.Server();

    if (useSsl) {
      if (null != System.getProperty("javax.net.ssl.keyStore")) {
        sslcontext.setKeyStorePath(System.getProperty("javax.net.ssl.keyStore"));
      }
      if (null != System.getProperty("javax.net.ssl.keyStorePassword")) {
        sslcontext.setKeyStorePassword(System.getProperty("javax.net.ssl.keyStorePassword"));
      }
      if (null != System.getProperty("javax.net.ssl.trustStore")) {
        sslcontext.setKeyStorePath(System.getProperty("javax.net.ssl.trustStore"));
      }
      if (null != System.getProperty("javax.net.ssl.trustStorePassword")) {
        sslcontext.setTrustStorePassword(System.getProperty("javax.net.ssl.trustStorePassword"));
      }
      sslcontext.setNeedClientAuth(Boolean.getBoolean("tests.jettySsl.clientAuth"));
    }

    final QueuedThreadPool threadPool = new QueuedThreadPool();
    threadPool.setDaemon(true);
    threadPool.setMaxThreads(10000);
    threadPool.setIdleTimeout(5000);
    threadPool.setStopTimeout(30000);

    Server server = new Server(threadPool);
    server.setStopAtShutdown(true);
    server.manage(threadPool);

    final ServerConnector connector;
    if (useSsl) {
      HttpConfiguration configuration = new HttpConfiguration();
      configuration.setSecureScheme("https");
      configuration.addCustomizer(new SecureRequestCustomizer());
      @SuppressWarnings("resource")
      ServerConnector c =
          new ServerConnector(
              server,
              new SslConnectionFactory(sslcontext, "http/1.1"),
              new HttpConnectionFactory(configuration));
      connector = c;
    } else {
      @SuppressWarnings("resource")
      ServerConnector c = new ServerConnector(server, new HttpConnectionFactory());
      connector = c;
    }

    connector.setPort(0);
    connector.setHost("127.0.0.1");

    server.setConnectors(new Connector[] {connector});
    server.setSessionIdManager(
        new DefaultSessionIdManager(server, new Random(random().nextLong())));
    server.setHandler(handler);

    server.start();

    return server;
  }

  /** Returns a {@link Server}'s port. */
  public static int serverPort(Server server) {
    return ((ServerConnector) server.getConnectors()[0]).getLocalPort();
  }

  /** Returns a {@link Server}'s host. */
  public static String serverHost(Server server) {
    return ((ServerConnector) server.getConnectors()[0]).getHost();
  }

  /**
   * Stops the given HTTP Server instance. This method does its best to guarantee that no threads
   * will be left running following this method.
   */
  public static void stopHttpServer(Server httpServer) throws Exception {
    httpServer.stop();
    httpServer.join();
  }

  /**
   * Returns a {@link HttpClientConnectionManager}.
   *
   * <p><b>NOTE:</b> do not {@link HttpClientConnectionManager#shutdown()} this connection manager,
   * it will be close automatically after all tests have finished.
   */
  public static synchronized HttpClientConnectionManager getClientConnectionManager() {
    if (clientConnectionManager == null) {
      PoolingHttpClientConnectionManager ccm = new PoolingHttpClientConnectionManager();
      ccm.setDefaultMaxPerRoute(128);
      ccm.setMaxTotal(128);
      clientConnectionManager = ccm;
    }

    return clientConnectionManager;
  }
}
