/*
 * 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.calcite.avatica;

import org.apache.calcite.avatica.jdbc.JdbcMeta;
import org.apache.calcite.avatica.remote.LocalService;
import org.apache.calcite.avatica.server.HttpServer;
import org.apache.calcite.avatica.util.DateTimeUtils;

import org.apache.kerby.kerberos.kerb.KrbException;

import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x500.style.IETFUtils;
import org.bouncycastle.asn1.x500.style.RFC4519Style;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.KeyUsage;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.junit.AfterClass;
import org.junit.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Security;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;

import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeNotNull;

/**
 * Common Base for HTTP End2End Tests
 */
public abstract class HttpBaseTest {
  protected static final Logger LOG = LoggerFactory.getLogger(HttpBaseTest.class);

  protected static final String KEYSTORE_PASSWORD = "avaticasecret";
  protected static final ConnectionSpec CONNECTION_SPEC = ConnectionSpec.HSQLDB;
  protected static final List<HttpServer> SERVERS_TO_STOP = new ArrayList<>();

  protected static final String TARGET_DIR_NAME = System.getProperty("target.dir", "target");
  protected static final File TARGET_DIR =
          new File(System.getProperty("user.dir"), TARGET_DIR_NAME);
  protected static final File KEYSTORE = new File(TARGET_DIR, "avatica-test.jks");
  protected static LocalService localService;

  protected final String jdbcUrl;

  public static void setupClass() throws SQLException {
    // Create a self-signed cert
    File target = SpnegoTestUtil.TARGET_DIR;
    File keystore = new File(target, "avatica-test.jks");
    if (keystore.isFile()) {
      assertTrue("Failed to delete keystore: " + keystore, keystore.delete());
    }
    new CertTool().createSelfSignedCert(keystore, "avatica", KEYSTORE_PASSWORD);

    // Create a LocalService around HSQLDB
    JdbcMeta jdbcMeta;
    jdbcMeta = null;
    jdbcMeta = new JdbcMeta(CONNECTION_SPEC.url,
        CONNECTION_SPEC.username, CONNECTION_SPEC.password);
    localService = new LocalService(jdbcMeta);
  }

  @AfterClass public static void stopServers() throws KrbException {
    for (HttpServer server : SERVERS_TO_STOP) {
      server.stop();
    }
    SERVERS_TO_STOP.clear();
  }

  @Before public void checkUrl() {
    //We signal that we skip the test because of the IBM Java issue by specifying a Null URL
    assumeNotNull(jdbcUrl);
  }

  public HttpBaseTest(String jdbcUrl) {
    this.jdbcUrl = jdbcUrl;
  }

  /**
   * Utility class for creating certificates for testing.
   */
  private static class CertTool {
    private static final String SIGNING_ALGORITHM = "SHA256WITHRSA";
    private static final String ENC_ALGORITHM = "RSA";

    static {
      Security.addProvider(new BouncyCastleProvider());
    }

    private void createSelfSignedCert(File targetKeystore, String keyName,
        String keystorePassword) {
      if (targetKeystore.exists()) {
        throw new RuntimeException("Keystore already exists: " + targetKeystore);
      }

      try {
        KeyPair kp = generateKeyPair();

        X509Certificate cert = generateCert(keyName, kp, true, kp.getPublic(),
            kp.getPrivate());

        char[] password = keystorePassword.toCharArray();
        KeyStore keystore = KeyStore.getInstance("JKS");
        keystore.load(null, null);
        keystore.setCertificateEntry(keyName + "Cert", cert);
        keystore.setKeyEntry(keyName + "Key", kp.getPrivate(), password, new Certificate[] {cert});
        try (FileOutputStream fos = new FileOutputStream(targetKeystore)) {
          keystore.store(fos, password);
        }
      } catch (Exception e) {
        throw new RuntimeException(e);
      }
    }

    private KeyPair generateKeyPair() throws NoSuchAlgorithmException {
      KeyPairGenerator gen = KeyPairGenerator.getInstance(ENC_ALGORITHM);
      gen.initialize(2048);
      return gen.generateKeyPair();
    }

    private X509Certificate generateCert(String keyName, KeyPair kp, boolean isCertAuthority,
                                         PublicKey signerPublicKey, PrivateKey signerPrivateKey)
        throws IOException, OperatorCreationException, CertificateException,
        NoSuchAlgorithmException {
      Calendar startDate = DateTimeUtils.calendar();
      Calendar endDate = DateTimeUtils.calendar();
      endDate.add(Calendar.YEAR, 100);

      BigInteger serialNumber = BigInteger.valueOf(startDate.getTimeInMillis());
      X500Name issuer = new X500Name(
          IETFUtils.rDNsFromString("cn=localhost", RFC4519Style.INSTANCE));
      JcaX509v3CertificateBuilder certGen = new JcaX509v3CertificateBuilder(issuer,
          serialNumber, startDate.getTime(), endDate.getTime(), issuer, kp.getPublic());
      JcaX509ExtensionUtils extensionUtils = new JcaX509ExtensionUtils();
      certGen.addExtension(Extension.subjectKeyIdentifier, false,
          extensionUtils.createSubjectKeyIdentifier(kp.getPublic()));
      certGen.addExtension(Extension.basicConstraints, false,
          new BasicConstraints(isCertAuthority));
      certGen.addExtension(Extension.authorityKeyIdentifier, false,
          extensionUtils.createAuthorityKeyIdentifier(signerPublicKey));
      if (isCertAuthority) {
        certGen.addExtension(Extension.keyUsage, true, new KeyUsage(KeyUsage.keyCertSign));
      }
      X509CertificateHolder certificateHolder = certGen.build(
          new JcaContentSignerBuilder(SIGNING_ALGORITHM).build(signerPrivateKey));
      return new JcaX509CertificateConverter().getCertificate(certificateHolder);
    }
  }
}

// End HttpBaseTest.java