package gov.va.med.esr.webservices.client;

import gov.va.med.esr.mvc.model.EEModel;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;
import java.util.TreeMap;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;

import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.UsernamePasswordCredentials;
import org.apache.commons.httpclient.auth.AuthPolicy;
import org.apache.commons.httpclient.auth.AuthScope;
import org.apache.commons.httpclient.methods.GetMethod;
import org.springframework.http.client.CommonsClientHttpRequestFactory;
import org.springframework.security.authentication.encoding.Md5PasswordEncoder;
import org.springframework.security.core.codec.Base64;
import org.springframework.web.client.RestTemplate;


/**
 * When invoking Webservice over https/SSL, client must first run InstallCert to
 * include the server cert into their local cert trust store.
 * 
 * <p>CAUTION: This "sample" is not production quality.  It does not  necessarily close
 * all necessary connections, streams, etc. as it should if not a test sample client.
 * 
 * @author DNS   BOHMEG
 * 
 */
public class SampleClientREST {
	public static final String REALM_NAME = "ESR Webservices";
	public static final String CHAR_SET = "UTF-8";
	public static final String URI_EE = "https://isa-kavuritr-lt:7002/esr-ws/webservices/veteran/{key}/ee";
	public static final String CLIENT_CERT_DEFAULT = "tester1.p12";
	
	static String username;
	static String password;
	static String key;
	static String serverAuthenticationMode;
	static boolean mode_OutputHashAndExit;

	public static void main(String[] args) throws Exception {
		/* 1 way SSL (connection over https, verify server cert) requires "trust store"
		 * 
		 * Note: The value for
		 * "javax.net.ssl.trustStore" MUST be a path, not something in the
		 * classpath
		 * 
		 * Options to get the path for something that is not in classpath: 1) if
		 * using standard certStoreName: System.getProperty("java.home") +
		 * "/lib/security/cacerts".replace('/', File.separatorChar); 2) else //
		 * hard coded absolute System.setProperty("javax.net.ssl.trustStore",
		 * "D:/data/projects/esr/webservices/code/HECMS_webservices_spring/jssecacerts"
		 * .replace('/', File.separatorChar));
		 */

		if(System.getProperty("javax.net.ssl.trustStore") == null) {
			String trustStoreName = System.getProperty("trustStoreName",
					"D:/jssecacerts");//.replace('/', File.separatorChar);
			String trustStorePassword = System.getProperty(
					"javax.net.ssl.trustStorePassword", "changeit");
	
			System.out.println("trustStoreName =" + trustStoreName);
			
			URL url = ClassLoader.getSystemResource(
					trustStoreName);
			
			System.out.println("url =" + url);
					
			
			TreeMap map = new TreeMap();
			String s = null;
			map.get(s);
			
			System.out.println("got s ");
			
			
			//String detectedAbsolutePath = ClassLoader.getSystemResource(
			//		trustStoreName).getFile(); // assumes classpath can find it
			
			
			File file = new File("jssecacerts");
			String detectedAbsolutePath =  file.getAbsolutePath();
			System.out.println("detectedAbsolutePath =" + detectedAbsolutePath);
			
			System.setProperty("javax.net.ssl.trustStore", detectedAbsolutePath);
			System.setProperty("javax.net.ssl.trustStorePassword",
					trustStorePassword);
		}
		
		// part of this depends on how the WAR file and server (Weblogic) is configured
		username = System.getProperty("webservice.username", "tester1");
		password = System.getProperty("webservice.password", "Password1!");
		serverAuthenticationMode = System.getProperty("serverAuthenticationMode", "x509");
		key = System.getProperty("targetKey", "2000005753V741305");
		mode_OutputHashAndExit = Boolean.getBoolean("mode.OutputHashAndExit");
		
		//if("Basic".equalsIgnoreCase(serverAuthenticationMode)) {
		if(1 == 1) {
			//invokeBasicAuthenticationWithoutSpring();
			invokeAuthenticationWithSpring(); // note works the same for Basic/Digest/x509 auth
		} else if("Digest".equalsIgnoreCase(serverAuthenticationMode)) {
			invokeDigestAuthenticationWithoutSpring();
			invokeAuthenticationWithSpring(); // note works the same for Basic/Digest/x509 auth
		} else if("x509".equalsIgnoreCase(serverAuthenticationMode)) {
			//System.setProperty("javax.net.debug", "ssl"); // enable ssl debug
			// 2 way SSL set up for client authentication
			if(System.getProperty("javax.net.ssl.keyStore") == null) {
				String keyStoreName = System.getProperty("keyStoreName",
						CLIENT_CERT_DEFAULT);
				String keyStorePassword = System.getProperty(
						"javax.net.ssl.keyStorePassword", "password");
				String keyStoreType = System.getProperty(
						"javax.net.ssl.keyStoreType", "PKCS12");
		
				String detectedAbsolutePath = ClassLoader.getSystemResource(
						keyStoreName).getFile(); // assumes classpath can find it				
				System.setProperty("javax.net.ssl.keyStore", detectedAbsolutePath);
				System.setProperty("javax.net.ssl.keyStorePassword",
						keyStorePassword);
				System.setProperty("javax.net.ssl.keyStoreType", keyStoreType);
			}
			
			invokeX509AuthenticationWithoutSpring();
			invokeAuthenticationWithSpring(); // note works the same for Basic/Digest/x509 auth
		}
	}
	
	// alternative way to load PKCS12 keystore, preferred way is to just set in system prop
	private static SSLSocketFactory getFactoryForP12KeyStore( File pKeyFile, String pKeyPassword ) throws Exception {
		  KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
		  KeyStore keyStore = KeyStore.getInstance("PKCS12");

		  InputStream keyInput = new FileInputStream(pKeyFile);
		  keyStore.load(keyInput, pKeyPassword.toCharArray());
		  keyInput.close();

		  keyManagerFactory.init(keyStore, pKeyPassword.toCharArray());

		  SSLContext context = SSLContext.getInstance("TLS");
		  context.init(keyManagerFactory.getKeyManagers(), null, new SecureRandom());

		  return context.getSocketFactory();
		}
	

	
	private static void invokeBasicAuthenticationWithoutSpring() throws Exception {
		String uri = URI_EE.replace("{key}", key);
		URL url = new URL(uri);

		// note, this does not use HttpSURLConnection
		HttpURLConnection conn = (HttpURLConnection) url.openConnection();
		conn.setRequestProperty("Accept", "application/xml");
		conn.setRequestProperty("Authorization", "Basic "
				+ new String(encodeAuthorizationAsBase64(), CHAR_SET));
		InputStream is = conn.getInputStream();
		BufferedReader reader = new BufferedReader(new InputStreamReader(is));
		String data = null;
		while ((data = reader.readLine()) != null) {
			System.out.println("Result (invokeBasicAuthenticationWithoutSpring): " + data);
		}
	}
	
	private static void invokeX509AuthenticationWithoutSpring() throws Exception {
		String uri = URI_EE.replace("{key}", key);
		URL url = new URL(uri);

		HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
		conn.setRequestProperty("Accept", "application/xml");
		/*conn.setSSLSocketFactory(getFactoryForP12KeyStore(
			new File(System.getProperty("keyStoreName", CLIENT_CERT_DEFAULT)), "password"));*/
		conn.setSSLSocketFactory(SSLContext.getDefault().getSocketFactory());
		try {
			InputStream is = conn.getInputStream();
			BufferedReader reader = new BufferedReader(new InputStreamReader(is));
			String data = null;
			while ((data = reader.readLine()) != null) {
				System.out.println("Result (invokeX509AuthenticationWithoutSpring): " + data);
			}			
		} catch(Exception e) {
			e.printStackTrace();
		}		
		
		System.out.println("### Local Principal: " + conn.getLocalPrincipal());
		System.out.println("### Peer Principal: " + conn.getPeerPrincipal());
				
		/* Certificate[] localCerts = conn.getLocalCertificates();
		if(localCerts != null && localCerts.length > 0) {
			for(int i=0; i<localCerts.length; i++) {
				System.out.println("### Local Cert " + i + ": " + localCerts[i]);
			}
		}*/
	}

	private static void invokeDigestAuthenticationWithoutSpring() throws Exception {
		String uri = URI_EE.replace("{key}", key);
		URL url = new URL(uri);

		HttpClient httpClient = new HttpClient();
		List<String> authPrefs = new ArrayList<String>(1);
		authPrefs.add(AuthPolicy.DIGEST);
		httpClient.getParams().setParameter(AuthPolicy.AUTH_SCHEME_PRIORITY, authPrefs);
		UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(
				username, password);
		httpClient.getState().setCredentials(AuthScope.ANY, credentials);
		GetMethod get = new GetMethod(url.toExternalForm());
		get.addRequestHeader("Accept", "application/xml");
		int returnCode = httpClient.executeMethod(get);
		System.out.println("Digest Authentication - result code: " + returnCode);
		System.out.println("Result (invokeDigestAuthenticationWithoutSpring): " + get.getResponseBodyAsString());
   }
	
	private static void hashPassword() {
		System.out.println("md5: realm[" + REALM_NAME + "] username[" + username +
				"] cleartext password[" + password + "]");
		Md5PasswordEncoder md5 = new Md5PasswordEncoder();
		System.out.println("md5: [for Http Basic authentication] hashed password with username as salt: " +
				md5.encodePassword(password, username));
		System.out.println("md5: [for Http Digest authentication] H(A1) hashed username:realm:password: " +
				md5.encodePassword(username + ":" + REALM_NAME + ":" + password, null));
		
		if(mode_OutputHashAndExit)
			System.exit(0);
	}

	private static String getCredential() {
		hashPassword();
		return password;
	}

	private static byte[] encodeAuthorizationAsBase64() throws Exception {
		String data = username + ":" + getCredential();
		System.out.println("...encoding: " + data);
		byte[] encodedHeader = Base64.encode(stringToBytes(data));
		System.out.println("...encodeAuthorizationAsBase64 - new String: "
				+ new String(encodedHeader, CHAR_SET));
		/*
		 * System.out.println("...encodeAuthorizationAsBase64 - new String(decoded): "
		 * + new String(Base64.decode(encodedHeader), CHAR_SET));
		 */
		return encodedHeader;
	}

	private static byte[] stringToBytes(String msg) {
		int len = msg.length();
		byte[] bytes = new byte[len];
		for (int i = 0; i < len; i++)
			bytes[i] = (byte) (msg.charAt(i) & 0xff);
		return bytes;
	}

	private static void invokeAuthenticationWithSpring() throws Exception {	
		UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(
				username, password);
		CommonsClientHttpRequestFactory commons = new CommonsClientHttpRequestFactory();
		/* 
		 * note: the below line is not necessary if solely relying on x509 client certs
		 * (but IS required if not doing exclusive x509 auth) 
		 */
		//commons.getHttpClient().getState().setCredentials(AuthScope.ANY, credentials);

		RestTemplate template = new RestTemplate(commons);
		Object result = template.getForObject(URI_EE,
				EEModel.class, key);
		System.out.println("Result (invokeAuthenticationWithSpring): " + result);
		//System.out.println("Result (invokeAuthenticationWithSpring) - getEnrollmentPriorityGroup: " +
		//		((EESummary) ((ServiceDataWrapper) result).getServiceData()).getEnrollmentPriorityGroup());
	}
}
