package com.aip.gateway.api;

import com.aip.gateway.api.aip.manager.AipFileManager;
import com.aip.gateway.api.aip.model.AipConfig;
import com.aip.gateway.api.aip.model.AipLabel;
import com.aip.gateway.api.aip.model.AipTemplate;
import com.aip.gateway.api.aip.utils.AipFileUtils;
import com.aip.gateway.api.aip.utils.MemoryTokenCacheWithEviction;
import com.google.common.base.Stopwatch;
import com.microsoft.aad.msal4j.ClientCredentialFactory;
import com.microsoft.aad.msal4j.ClientCredentialParameters;
import com.microsoft.aad.msal4j.ConfidentialClientApplication;
import com.microsoft.aad.msal4j.IAuthenticationResult;
import com.nimbusds.oauth2.sdk.http.HTTPResponse;
import lombok.extern.slf4j.Slf4j;
import org.jasypt.encryption.pbe.PooledPBEStringEncryptor;
import org.jasypt.encryption.pbe.config.SimpleStringPBEConfig;
import org.jasypt.salt.StringFixedSaltGenerator;
import org.junit.jupiter.api.Test;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

@Slf4j
//@SpringBootTest
class AipGatewayApiApplicationTests {
	private final String instance = "https://login.microsoftonline.com/";
	private final String tenantId = "2e58414a-c6ae-43ff-aaf5-45ab8b78a404";
	private final String clientId = "0e225915-3be3-419c-aa04-284d7de5e16b";
	private final String scope = "https://graph.microsoft.com/.default";
	private final String secret = "CvW8Q~0iANtLN1Y2EXR_nVyYb_tQTDwjW-Z7Ndg3";
	private final String secretFile = "C:\\Data\\SSL\\mip\\mip_gateway.pfx";

	private ConfidentialClientApplication app;

	@Test
	void test2() {
		String fileName = "c:\\data\\SSL\\mip\\mip_gateway.pfx";
		String ext = AipFileUtils.getExtension(fileName);
		String name = AipFileUtils.getFileName(fileName);
		log.error("[{}] , [{}] [{}] [{}]", fileName, ext, name, AipFileUtils.getFileNameWithoutExtension(fileName));
	}
	@Test
	void jasypt() {
		String encKey = "asdkjfaslkjflkajslfjkajlkf";
		PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor();
		SimpleStringPBEConfig config = new SimpleStringPBEConfig();

		// ==> SimpleStringPBEConfig 사용시 아래 3개 반드시 설정해야함
		config.setPassword(encKey);                                         // 암호화에 사용할 키
		config.setPoolSize(1);                                              // Pool Size
		config.setSaltGenerator(new StringFixedSaltGenerator("fixedSalt")); // 고정으로 암호화(Default: Random)
		//config.setSaltGeneratorClassName("org.jasypt.salt.RandomSaltGenerator");
		//config.setAlgorithm("PBEWithMD5AndTripleDES");
		config.setAlgorithm("PBEWithMD5AndDES"); // Jasypt 를 이용한 암호화 알고리즘
		config.setProviderName("SunJCE");
		config.setKeyObtentionIterations("10000");
		config.setStringOutputType("base64");
        /*private Boolean proxyPropertySources = false;
        private String bean = "jasyptStringEncryptor";
        private String password;
        private String algorithm = "PBEWithMD5AndDES";
        private String keyObtentionIterations = "1000";
        private String poolSize = "1";
        private String providerName = null;
        //config.setSaltGeneratorClassName("org.jasypt.salt.RandomSaltGenerator");
        private String saltGeneratorClassname = "org.jasypt.salt.RandomSaltGenerator";
        private String stringOutputType = "base64";*/
		encryptor.setConfig(config);

		String dbUser = encryptor.encrypt("aipuser");
		String dbPswd = encryptor.encrypt("aipuser");
		log.info("{}", dbUser);
		log.info("{}", dbPswd);
	}

	boolean AipFileInitialize(AipConfig aipConfig) {
		AipFileManager aipFileManager = new AipFileManager(aipConfig);
		if (!aipFileManager.initialize()) {
			log.error("AipFileManager.initialize Failed, {}, {}", aipFileManager.getLastErrNo(), aipFileManager.getLastErrMsg());
			return false;
		}
		if (!aipFileManager.createProfile()) {
			log.error("AipFileManager.createProfile Failed, {}, {}", aipFileManager.getLastErrNo(), aipFileManager.getLastErrMsg());
			return false;
		}
		if (!aipFileManager.createEngine()) {
			log.error("AipFileManager.createEngine Failed, {}, {}", aipFileManager.getLastErrNo(), aipFileManager.getLastErrMsg());
			return false;
		}
		log.info("AipFileManager Initialize And Create Success.");

		List<AipLabel> labels = aipFileManager.sensitivityLabels();
		log.info("{}", labels);
		List<AipLabel> policies = aipFileManager.listSensitivityLabels();
		log.info("{}", policies);
		List<AipTemplate> templates = aipFileManager.getTemplates();
		log.info("{}", templates);

		return true;
	}
	@Test
	void aipTest() {
		AipConfig aipConfig = AipConfig.builder()
				.appName("AIP Gateway RESTFull Service")
				.appVersion("1.0.0.0")
				.clientId("0e225915-3be3-419c-aa04-284d7de5e16b")
				.tenantId("2e58414a-c6ae-43ff-aaf5-45ab8b78a404")
				.mipData("App_Data\\\\mip_data")
				.loginType(AipConfig.authLoginCert)
				.domain("AIP.Gateway")
				.eMail("seungho@ms365.hanteinfo.com")
				.secretValue("CvW8Q~0iANtLN1Y2EXR_nVyYb_tQTDwjW-Z7Ndg3")
				.certThumbPrint("C:\\\\Data\\\\SSL\\\\mip\\\\mip_gateway.pfx")
				.build();

		AipFileInitialize(aipConfig);
	}
	private IAuthenticationResult getAccessTokenByClientCredentialGrant(String instance, String clientId, String tenantId, String secret, String scope) throws Exception {

		// Setup the token cache first. It is configured to allow 100k token entries, which at 2-3KB per entry, will take under 500MB of memory
		// Note that size calculations are approximate and depend on the JVM
		String cacheKey = clientId + "_" + tenantId + "_AppTokenCache";
		MemoryTokenCacheWithEviction memoryTokenCacheWithEviction = new MemoryTokenCacheWithEviction(cacheKey);

		ConfidentialClientApplication cca = ConfidentialClientApplication.builder(
						clientId,
						ClientCredentialFactory.createFromSecret(secret))
				.authority(instance + tenantId)
				.setTokenCacheAccessAspect(memoryTokenCacheWithEviction)
				.build();

		// Important: point the CCA object to a token cache

		ClientCredentialParameters clientCredentialParam = ClientCredentialParameters.builder(
						Collections.singleton(scope))
				.build();

		Stopwatch stopwatch = Stopwatch.createStarted();
		CompletableFuture<IAuthenticationResult> future = cca.acquireToken(clientCredentialParam);
		IAuthenticationResult result = future.get();

		log.info("Time to fetch the token: {} ms.", stopwatch.elapsed(TimeUnit.MILLISECONDS));

		return result;
	}

	private void getOrCreateApp(String clientId, String authority, String certPath) throws Exception {

		if (app == null) {
			//PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(Files.readAllBytes(Paths.get(keyPath)));
			//PrivateKey key = KeyFactory.getInstance("RSA").generatePrivate(spec);

			InputStream certStream = new ByteArrayInputStream(Files.readAllBytes(Paths.get(certPath)));
			//X509Certificate cert = (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(certStream);

			String password = "hanteinfo1234!";
			app = ConfidentialClientApplication.builder(
							clientId,
							ClientCredentialFactory.createFromCertificate(certStream, password))
							//ClientCredentialFactory.createFromCertificate(key, cert))
					.authority(authority)
					.build();
		}
	}

	private String getUsersListFromGraph(String accessToken) throws IOException {
		URL url = new URL("https://graph.microsoft.com/v1.0/users");
		HttpURLConnection conn = (HttpURLConnection) url.openConnection();

		conn.setRequestMethod("GET");
		conn.setRequestProperty("Authorization", "Bearer " + accessToken);
		conn.setRequestProperty("Accept","application/json");

		int httpResponseCode = conn.getResponseCode();
		if(httpResponseCode == HTTPResponse.SC_OK) {

			StringBuilder response;
			try(BufferedReader in = new BufferedReader(
					new InputStreamReader(conn.getInputStream()))){

				String inputLine;
				response = new StringBuilder();
				while (( inputLine = in.readLine()) != null) {
					response.append(inputLine);
				}
			}
			return response.toString();
		} else {
			return String.format("Connection returned HTTP code: %s with message: %s",
					httpResponseCode, conn.getResponseMessage());
		}
	}
	@Test
	void testSecretAuth() {
		try {

			IAuthenticationResult result = getAccessTokenByClientCredentialGrant(instance, clientId, tenantId, secret, scope);

			// If you try to fetch the same token again, it will hit the token cache and it will be much faster.
			// In case AAD has an outage, cached tokens are still available, increasing your app's resiliency.
			result = getAccessTokenByClientCredentialGrant(instance, clientId, tenantId, secret, scope);



			String usersListFromGraph = getUsersListFromGraph(result.accessToken());
			log.info("Users in the Tenant = {}", usersListFromGraph);
			System.out.println("Press any key to exit ...");
			System.in.read();

		} catch(Exception ex){
			System.out.println("Oops! We have an exception of type - " + ex.getClass());
			System.out.println("Exception message - " + ex.getMessage());
			try {
				throw ex;
			} catch (Exception e) {
				throw new RuntimeException(e);
			}
		}
	}

	@Test
	void testCertFile() {
		try {

			// Ensure the app object is not re-created on each request, as it holds a token cache
			// If you are getting tokens for many tenants (millions), see the msal-client-credential-secret-high-availability sample
			// which shows how to use an in-memory token cache with eviction based on a size limit
			getOrCreateApp(clientId, instance+tenantId, "C:\\Data\\SSL\\mip\\mip_gateway.pfx");

			ClientCredentialParameters clientCredentialParam = ClientCredentialParameters.builder(
							Collections.singleton(scope))
					.build();

			// The first time this is called, the app will make an HTTP request to the token issuer, so this is slow. Latency can be >1s
			IAuthenticationResult result = app.acquireToken(clientCredentialParam).get();

			// On subsequent calls, the app will return a token from its cache. It is important to reuse the app object

			// Now that we have a Bearer token, call the protected API
			String usersListFromGraph = getUsersListFromGraph(result.accessToken());

			System.out.println("Users in the Tenant = " + usersListFromGraph);
			System.out.println("Press any key to exit ...");
			System.in.read();

		} catch (Exception ex) {
			System.out.println("Oops! We have an exception of type - " + ex.getClass());
			System.out.println("Exception message - " + ex.getMessage());
			try {
				throw ex;
			} catch (Exception e) {
				throw new RuntimeException(e);
			}
		}

	}
//	@Test
//	void testAip() throws ExecutionException, InterruptedException {
//		InputStreamReader input = new InputStreamReader(System.in);
//		BufferedReader reader = new BufferedReader(input);
//
//		// ApplicationInfo is used to store the application name, clientId, and version.
//		ApplicationInfo appInfo = new ApplicationInfo();
//		FileOptions options = new FileOptions();
//
//		appInfo.setApplicationId("0e225915-3be3-419c-aa04-284d7de5e16b");
//		appInfo.setApplicationName("MIP SDK Java Sample");
//		appInfo.setApplicationVersion("1.11");
//
//		String userName = "shjung@ms365.hanteinfo.com";
//
//		Action action = new Action(appInfo, userName);
//
//		// Fetch the list of labels for the authenticated user and display.
//		action.ListLabels();
//
//		// Copy a label Id from the output and paste into the prompt.
//		options.LabelId = "1098bdb0-f2bc-43d3-b9c7-e610431fc1a4";
//
//		// Provide an input file that should be labeled.
//		options.InputFilePath = "C:\\Data\\Source\\source.pptx";
//
//		// Provide the output path for the file. The original file remains intact and a
//		// copy is created.
//		options.OutputFilePath = "C:\\Data\\Source\\source-out.pptx";
//
//		// The privileged AssignmentMethod is used when users label files, and can
//		// override STANDARD.
//		options.AssignmentMethod = AssignmentMethod.PRIVILEGED;
//
//		// Information on datastate is used to populate audit information. This field
//		// doesn't impact the SDK behavior.
//		options.DataState = DataState.REST;
//
//		// Sets whether the sample should generate an audit event.
//		options.IsAuditDiscoveryEnabled = true;
//
//		// Apply the chosen label to the input file.
//
//		boolean result = action.SetLabel(options);
//
//		if (result) {
//			// This section attempts to read the label from the file, if one was applied.
//			log.error("Reading label from file...");
//
//			options.InputFilePath = options.OutputFilePath;
//
//			log.error("File Name: " + options.InputFilePath);
//			log.error("File Label: " + action.GetLabel(options).label.getName());
//
//			log.error("Reading owner from file...");
//			log.error("File Label: " + action.GetProtection(options).getOwner());
//		}
//		else {
//			log.error("No changes were written to the input file.");
//		}
//
//	}
}
