/*
 * Copyright 2018 Scytl Secure Electronic Voting SA
 *
 * All rights reserved
 *
 * See our extended copyright notice in *file 'Copyright.txt'
 * which is part of this source code package
 */

package com.scytl.slogger.validator;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;

import com.scytl.crypto.test.TestCertificateGenerator;
import com.scytl.crypto.test.TestKeyStoreGenerator;
import com.scytl.cryptolib.api.asymmetric.AsymmetricServiceAPI;
import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.asymmetric.service.AsymmetricServiceFactoryHelper;
import com.scytl.cryptolib.primitives.primes.utils.PemUtils;
import com.scytl.slogger.SecureAppender;
import com.scytl.slogger.SecureLoggerException;
import com.scytl.slogger.policy.LogFileNamePolicies;
import com.scytl.slogger.util.LogValidator;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.KeyPair;
import java.security.KeyStore;
import java.security.Security;
import java.security.cert.X509Certificate;
import javax.naming.InvalidNameException;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;

public class ValidatorITest {

  private static final String FILE_NAME = "secure.log";
  private static final String NAME = "AUDIT";
  private static final String VERIFY_PATTERN = "TIMESTAMP|LEVEL|THREAD|MESSAGE";
  private static final String PATTERN = "%d{yyyy-MM-dd HH:mm:ss,SSS}|%p|%t|%m%n";
  private static final String SIGNATURE_PKCS12_PASSWORD = "GXXCNJH48X5F649VRY52";
  private static final String CIPHER_PKCS12_PASSWORD = "649VRY52GXXCNJH48X5F";
  private static final String OK = "ok";
  private static final String WARN = "warn";
  private static final String ERROR = "error";
  private static final String SECURE_LOG_ENCRYPTION_PEM_FILENAME = "secure-log.crt";
  private static final LogFileNamePolicies POLICY = LogFileNamePolicies.ROLLING_SEQUENTIAL_FILENAME;

  private static KeyStore signingKeyStore;
  private static KeyStore encryptionKeyStore;

  @Rule
  public TemporaryFolder temporaryFolder = new TemporaryFolder();

  @BeforeClass
  public static void setUp() {
    Security.addProvider(new BouncyCastleProvider());
  }

  @Before
  public void setUpCryptographicData()
      throws GeneralCryptoLibException, InvalidNameException, IOException {

    AsymmetricServiceAPI asymmetricService =
        AsymmetricServiceFactoryHelper.getFactoryOfThreadSafeServices().create();

    TestCertificateGenerator testCertificateGenerator = TestCertificateGenerator.createDefault();

    X509Certificate caCertificate = testCertificateGenerator.getRootCertificate();

    KeyPair signingKeyPair = asymmetricService.getKeyPairForSigning();
    X509Certificate signingCertificate = testCertificateGenerator
        .createSigningLeafCertificate(signingKeyPair,
            testCertificateGenerator.getRootKeyPair().getPrivate(), caCertificate,
            "Secure log signing");

    KeyPair encryptionKeyPair = asymmetricService.getKeyPairForSigning();
    X509Certificate encryptionCertificate =
        testCertificateGenerator.createEncryptionLeafCertificate(encryptionKeyPair,
            testCertificateGenerator.getRootKeyPair().getPrivate(), caCertificate,
            "Secure log encryption");

    X509Certificate[] signingCertificateChain = {signingCertificate, caCertificate};
    signingKeyStore = TestKeyStoreGenerator
        .createKeystore(signingKeyPair.getPrivate(), "signingKey",
            SIGNATURE_PKCS12_PASSWORD.toCharArray(), signingCertificateChain);

    X509Certificate[] encryptionCertificateChain = {encryptionCertificate, caCertificate};
    encryptionKeyStore = TestKeyStoreGenerator
        .createKeystore(encryptionKeyPair.getPrivate(), "encryptionKey",
            CIPHER_PKCS12_PASSWORD.toCharArray(), encryptionCertificateChain);

    // Store the encryption certificate's PEM for the validator.
    Files.write(temporaryFolder.getRoot().toPath().resolve(SECURE_LOG_ENCRYPTION_PEM_FILENAME),
        PemUtils.certificateToPem(signingCertificate).getBytes());
  }

  @Test
  public void buildAndValidateSecureLog() throws SecureLoggerException {

    Path basePath = temporaryFolder.getRoot().toPath();
    buildSecureLog(basePath);
    validateSecureLog(basePath);

    assertTrue(getResultFile(OK).exists());
    assertTrue(getResultFile(WARN).exists());
    assertTrue(getResultFile(ERROR).exists());

    assertNotEquals(0, getResultFile(OK).length());
    assertEquals(0, getResultFile(WARN).length());
    assertEquals(0, getResultFile(ERROR).length());
  }

  private File getResultFile(String fileName) {
    return temporaryFolder.getRoot().toPath().resolve(fileName + ".log").toFile();
  }

  private void buildSecureLog(Path basePath) throws SecureLoggerException {
    Path logPath = basePath.resolve(FILE_NAME);

    SecureAppender appender = new SecureAppender();
    appender.setName(NAME);
    appender.setPattern(PATTERN);
    appender.setVerifyPattern(VERIFY_PATTERN);
    appender.setFile(logPath.toString());
    appender.setEncoding(StandardCharsets.UTF_8.name());
    appender.setNumberLogLines(5);
    appender.setTimerLogMilliseconds(4000);
    appender.setTriggeringTime(1000);
    appender.setLogFileNamePolicyName(POLICY.name());

    appender.setCipherPkcs12Certificate(encryptionKeyStore);
    appender.setCipherPkcs12Password(CIPHER_PKCS12_PASSWORD);

    appender.setSignaturePkcs12Certificate(signingKeyStore);
    appender.setSignaturePkcs12Password(SIGNATURE_PKCS12_PASSWORD);

    appender.activateOptions();

    Logger logger = Logger.getRootLogger();
    logger.setLevel(Level.INFO);
    logger.addAppender(appender);
    for (int i = 0; i < 20; i++) {
      logger.info("Test message");
    }

    appender.close();
  }

  private void validateSecureLog(Path path) {
    File[] files = path.toFile().listFiles(file -> file.getName().equals(FILE_NAME));

    if (files == null) {
      Assert.fail("No log files were generated");
    }

    for (File file : files) {
      String certificateFile = temporaryFolder.getRoot().toPath()
          .resolve(SECURE_LOG_ENCRYPTION_PEM_FILENAME)
          .toFile().getAbsolutePath();
      String[] args = {"-c", certificateFile, "-l", file.getAbsolutePath(), "-e",
          StandardCharsets.UTF_8.name(),
          "-p", PATTERN, "-v", VERIFY_PATTERN.replace("|", "\\|"), "-o", file.getParent(), "-n",
          POLICY.getPolicy().getName()};
      LogValidator.main(args);
    }
  }
}
