/**
* Copyright 2017 ZuInnoTe (Jörn Franke) <[email protected]>
*
* Licensed 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.zuinnote.hadoop.office.format.common;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.io.File;
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.security.InvalidAlgorithmParameterException;
import java.security.Key;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableEntryException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.security.cert.PKIXParameters;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.util.List;
import java.util.Set;
import java.security.cert.Certificate;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.mapreduce.TaskAttemptID;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

/**
 * @author jornfranke
 *
 */
public class HadoopKeyStoreManagerTest {

private static Configuration defaultConf = new Configuration();
private static FileSystem localFs = null; 
private static final String attempt = "attempt_201612311111_0001_m_000000_0";
private static final String taskAttempt = "task_201612311111_0001_m_000000";
private static final TaskAttemptID taskID = TaskAttemptID.forName(attempt);
private static final String tmpPrefix = "hadoopofficetest";
private static final String outputbaseAppendix = "-m-00000";
private static java.nio.file.Path tmpPath;

   @BeforeAll
    public static void oneTimeSetUp() throws IOException {
      // one-time initialization code   
      defaultConf.set("fs.defaultFS", "file:///");
      localFs = FileSystem.getLocal(defaultConf);
      // create temp directory
      tmpPath = Files.createTempDirectory(tmpPrefix);

      // create shutdown hook to remove temp files after shutdown, may need to rethink to avoid many threads are created
	Runtime.getRuntime().addShutdownHook(new Thread(
    	new Runnable() {
      	@Override
      	public void run() {
        	try {
          		Files.walkFileTree(tmpPath, new SimpleFileVisitor<java.nio.file.Path>() {
	
            		@Override
            		public FileVisitResult visitFile(java.nio.file.Path file,BasicFileAttributes attrs)
                		throws IOException {
              			Files.delete(file);
             			return FileVisitResult.CONTINUE;
        			}

        		@Override
        		public FileVisitResult postVisitDirectory(java.nio.file.Path dir, IOException e) throws IOException {
          			if (e == null) {
            				Files.delete(dir);
            				return FileVisitResult.CONTINUE;
          			}
          			throw e;
        	}
        	});
      	} catch (IOException e) {
        throw new RuntimeException("Error temporary files in following path could not be deleted "+tmpPath, e);
      }
    }}));
    }

    @AfterAll
    public static void oneTimeTearDown() {
        // one-time cleanup code
      }

    @BeforeEach
    public void setUp() {
    }

    @AfterEach
    public void tearDown() {  
    }
    
    @Test
    public void checkKeystoreAvailable() {
		ClassLoader classLoader = getClass().getClassLoader();
		String fileName="keystore.jceks";
		String fileNameKeyStore=classLoader.getResource(fileName).getFile();	
		assertNotNull(fileNameKeyStore,"Test Data File \""+fileName+"\" is not null in resource path");
		File file = new File(fileNameKeyStore);
		assertTrue( file.exists(),"Test Data File \""+fileName+"\" exists");
		assertFalse( file.isDirectory(),"Test Data File \""+fileName+"\" is not a directory");
    }
    
    @Test
    public void checkCertificateAvailable() {
		ClassLoader classLoader = getClass().getClassLoader();
		String fileName="testsigning.pfx";
		String fileNameKeyStore=classLoader.getResource(fileName).getFile();	
		assertNotNull(fileNameKeyStore,"Test Data File \""+fileName+"\" is not null in resource path");
		File file = new File(fileNameKeyStore);
		assertTrue( file.exists(),"Test Data File \""+fileName+"\" exists");
		assertFalse( file.isDirectory(),"Test Data File \""+fileName+"\" is not a directory");
    }
    
    @Test
    public void loadExistingKeyStore() throws IOException, NoSuchAlgorithmException, CertificateException, KeyStoreException, UnrecoverableEntryException, InvalidKeySpecException {
	    	Configuration conf = new Configuration(HadoopKeyStoreManagerTest.defaultConf);
	    	ClassLoader classLoader = getClass().getClassLoader();
	    	String fileName="keystore.jceks";
		String fileNameKeyStore=classLoader.getResource(fileName).getFile();	
	    	Path file = new Path(fileNameKeyStore);
    		HadoopKeyStoreManager hksm = new HadoopKeyStoreManager(conf);
    		hksm.openKeyStore(file, "JCEKS", "changeit");
    		String expectedPassword="test";
    		String password=hksm.getPassword("test.xlsx", "changeit");
    		assertEquals(expectedPassword,password,"Password is correctly read from keystore");
    }
    
   

    
    @Test
    public void createKeyStoreforPasswords() throws IOException, NoSuchAlgorithmException, CertificateException, KeyStoreException, InvalidKeySpecException, UnrecoverableEntryException {
     	Configuration conf = new Configuration(HadoopKeyStoreManagerTest.defaultConf);
       	String tmpDir=tmpPath.toString();	
       	Path outputFile= new Path(tmpDir,"keystore2.jceks");
       	HadoopKeyStoreManager hksm = new HadoopKeyStoreManager(conf);
       	// create new key store
       	hksm.openKeyStore(null, "JCEKS", "changeit");
       	hksm.setPassword("test.xlsx", "test2", "changeit");
       	hksm.store(outputFile, "changeit");
       	// open existing keystore
       	hksm.openKeyStore(outputFile, "JCEKS", "changeit");
       	String expectedPassword="test2";
       	String password=hksm.getPassword("test.xlsx", "changeit");
  		assertEquals(expectedPassword,password,"Password is correctly read from new keystore");
    }
 
    
    @Test
    public void getPrivateKeyAndCertificate() throws IOException, UnrecoverableKeyException, KeyStoreException, NoSuchAlgorithmException, CertificateException {
    		Configuration conf = new Configuration(HadoopKeyStoreManagerTest.defaultConf);
    		ClassLoader classLoader = getClass().getClassLoader();
    		String fileName="testsigning.pfx";
    		String fileNameKeyStore=classLoader.getResource(fileName).getFile();	
		Path file = new Path(fileNameKeyStore);
     	HadoopKeyStoreManager hksm = new HadoopKeyStoreManager(conf);
       	hksm.openKeyStore(file, "PKCS12", "changeit");
    		// load private key
       	Key privateKey = hksm.getPrivateKey("testalias", "changeit");
       	assertNotNull(privateKey,"Private key could be loaded");
       	// load certificate
       	Certificate certificate = hksm.getCertificate("testalias");
       	assertNotNull(certificate,"Certificate for private key could be loaded");
    }
    
    @Test
    public void getAllX509CertificatesFromTrustStore() throws IOException, NoSuchAlgorithmException, CertificateException, KeyStoreException, InvalidAlgorithmParameterException {
    	Configuration conf = new Configuration(HadoopKeyStoreManagerTest.defaultConf);
		ClassLoader classLoader = getClass().getClassLoader();
		String fileName="cacerts";
		String fileNameKeyStore=classLoader.getResource(fileName).getFile();	
		Path file = new Path(fileNameKeyStore);
		HadoopKeyStoreManager hksm = new HadoopKeyStoreManager(conf);
		hksm.openKeyStore(file, "JKS", "changeit");
		// get most trusted certificates
		Set<X509Certificate> allX509Certificates=hksm.getAllX509Certificates();
		assertEquals(104,allX509Certificates.size(),"All X509 Certificate list has length 104");
    }
}