package com.eactive.eai.authserver.custom;

import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.Principal;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Signature;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;

import javax.crypto.Cipher;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.util.OAuth2Utils;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.OAuth2Request;
import org.springframework.security.oauth2.provider.endpoint.TokenEndpoint;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

import com.eactive.eai.adapter.http.dynamic.filter.JwtAuthException;
import com.eactive.eai.authserver.service.OAuth2Manager;
import com.eactive.eai.authserver.util.BeanUtils;
import com.eactive.eai.authserver.vo.ClientVO;
import com.eactive.eai.common.util.Logger;

@Controller
public class SnapOAuth2Controller {
	public static Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT);

	private static final String HEADER_TIMESTAMP = "X-TIMESTAMP";
	private static final String HEADER_CLIENT_ID = "X-CLIENT-KEY";
	private static final String HEADER_SIGNATURE = "X-SIGNATURE";

	private static final String SERVICE_CODE_ACCESS_TOKEN = "00"; // 임시설정

	@RequestMapping(value = "/bukopin_snap_api/v1.0/access-token/b2b", method = RequestMethod.POST, produces = "application/json; charset=\"UTF-8\"")
	@ResponseBody
	public SnapOAuth2AccessTokenResponse token(@RequestBody SnapOAuth2AccessTokenRequest tokenRequest,
			HttpServletRequest request, HttpServletResponse response) {
		if (logger.isDebug()) {
			logger.debug(tokenRequest.toString());
		}

		SnapOAuth2AccessTokenResponse responseToken = new SnapOAuth2AccessTokenResponse();
		try {
			if (!StringUtils.equals(tokenRequest.getGrantType(), "client_credentials")) {
				throw new JwtAuthException(
						String.format("%d%s%s", HttpStatus.BAD_REQUEST.value(), SERVICE_CODE_ACCESS_TOKEN, "02"),
						"Invalid Mandatory Field {grantType}");
			}

			String timeStamp = request.getHeader(HEADER_TIMESTAMP);
			String clientId = request.getHeader(HEADER_CLIENT_ID);
			String signature = request.getHeader(HEADER_SIGNATURE);

			ClientDetails clientDetails = OAuth2Manager.getInstance().getClientDeatilsStore().get(clientId);
			verifyClient(clientDetails, timeStamp, clientId, signature);

			response.setHeader(HEADER_TIMESTAMP, timeStamp);
			response.setHeader(HEADER_CLIENT_ID, clientId);

			HashMap<String, String> authorizationParameters = new HashMap<String, String>();
			authorizationParameters.put(OAuth2Utils.GRANT_TYPE, tokenRequest.getGrantType());
			authorizationParameters.put(OAuth2Utils.CLIENT_ID, clientId);
			authorizationParameters.put("client_secret", clientDetails.getClientSecret());

			Set<String> responseType = new HashSet<String>();
			responseType.add(tokenRequest.getGrantType());

			OAuth2Request authorizationRequest = new OAuth2Request(authorizationParameters, clientId, null, true, null,
					null, "", responseType, null);

			Principal principal = new OAuth2Authentication(authorizationRequest, null);
			ResponseEntity<OAuth2AccessToken> result = tokenEndpoint().postAccessToken(principal,
					authorizationParameters);
			OAuth2AccessToken token = result.getBody();
			responseToken.setAccessToken(token.getValue());
			responseToken.setExpiresIn(String.valueOf(token.getExpiresIn()));
			responseToken.setTokenType(token.getTokenType());
		} catch (JwtAuthException e) {
			response.setStatus(NumberUtils.toInt(StringUtils.left(e.getCode(), 3), HttpStatus.UNAUTHORIZED.value()));
			responseToken.setResponseCode(e.getCode());
			responseToken.setResponseMessage(e.getMessage());
		} catch (Exception e) {
			logger.error(e.getMessage());
			response.setStatus(HttpStatus.UNAUTHORIZED.value());
			responseToken.setResponseCode(
					String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_ACCESS_TOKEN, "00"));
			responseToken.setResponseMessage("Unauthorized. [Unknown]");
		}

		return responseToken;
	}

	private void verifyClient(ClientDetails clientDetails, String timeStamp, String clientId, String signatureStr)
			throws JwtAuthException {
		if (StringUtils.isBlank(timeStamp)) {
			throw new JwtAuthException(
					String.format("%d%s%s", HttpStatus.BAD_REQUEST.value(), SERVICE_CODE_ACCESS_TOKEN, "02"),
					"Invalid Mandatory Field {" + HEADER_TIMESTAMP + "}");
		}

		if (StringUtils.isBlank(clientId)) {
			throw new JwtAuthException(
					String.format("%d%s%s", HttpStatus.BAD_REQUEST.value(), SERVICE_CODE_ACCESS_TOKEN, "02"),
					"Invalid Mandatory Field {" + HEADER_CLIENT_ID + "}");
		}

		if (StringUtils.isBlank(signatureStr)) {
			throw new JwtAuthException(
					String.format("%d%s%s", HttpStatus.BAD_REQUEST.value(), SERVICE_CODE_ACCESS_TOKEN, "02"),
					"Invalid Mandatory Field {" + HEADER_SIGNATURE + "}");
		}

		if (clientDetails == null) {
			throw new JwtAuthException(
					String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_ACCESS_TOKEN, "00"),
					"Unauthorized. [client not found]");
		}
		
		String publicKeyStr = ((ClientVO) clientDetails).getSecurityKey();
		if (StringUtils.isBlank(publicKeyStr)) {
			throw new JwtAuthException(
					String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_ACCESS_TOKEN, "00"),
					"Unauthorized. [public key not found]");
		}

		try {
			String message = clientId + "|" + timeStamp;
			X509EncodedKeySpec keySpec = new X509EncodedKeySpec(Base64.getDecoder().decode(publicKeyStr));
			KeyFactory keyFactory = KeyFactory.getInstance("RSA");
			PublicKey publicKey = keyFactory.generatePublic(keySpec);

			Signature signature = Signature.getInstance("SHA256withRSA");
			signature.initVerify(publicKey);
			signature.update(message.getBytes(StandardCharsets.UTF_8));
			if (!signature.verify(Base64.getDecoder().decode(signatureStr))) {
				throw new JwtAuthException(
						String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_ACCESS_TOKEN, "00"),
						"Unauthorized. [Signature not matched]");
			}
		} catch (JwtAuthException e) {
			throw e;
		} catch (Exception e) {
			logger.error(e.getMessage());
			throw new JwtAuthException(
					String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_ACCESS_TOKEN, "00"),
					"Unauthorized. [Signature fail]");
		}
	}

	private TokenEndpoint tokenEndpoint() {
		return BeanUtils.getBean("tokenEndpoint", TokenEndpoint.class);
	}

	public static void main(String[] args) throws Exception {
		// First generate a public/private key pair
		KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
		// generator.initialize(512, new SecureRandom());
		//generator.initialize(1024, new SecureRandom());
		generator.initialize(2048, new SecureRandom());
		KeyPair pair = generator.generateKeyPair();

		// The private key can be used to sign (not encrypt!) a message. The public key
		// holder can then verify the message.

		String message = "sSGJDwlJsel5WeXodzd3J3MQ20KYCZpL|2022-12-21T14:36:19+07:00";

		// Let's sign our message
		Signature privateSignature = Signature.getInstance("SHA256withRSA");
		privateSignature.initSign(pair.getPrivate());
		privateSignature.update(message.getBytes(StandardCharsets.UTF_8));
		System.out.println(
				"private key=" + Base64.getEncoder().encodeToString(pair.getPrivate().getEncoded()));

		byte[] signature = privateSignature.sign();
		//System.out.println("signature=" + new String(signature, StandardCharsets.UTF_8));
		System.out.println("signature=" + Base64.getEncoder().encodeToString(signature));

		// Let's check the signature
		Signature publicSignature = Signature.getInstance("SHA256withRSA");
		publicSignature.initVerify(pair.getPublic());
		publicSignature.update(message.getBytes(StandardCharsets.UTF_8));
		boolean isCorrect = publicSignature.verify(signature);
		System.out.println("public key=" + Base64.getEncoder().encodeToString(pair.getPublic().getEncoded()));

		System.out.println("Signature correct: " + isCorrect);

		// The public key can be used to encrypt a message, the private key can be used
		// to decrypt it.
		// Encrypt the message
		Cipher encryptCipher = Cipher.getInstance("RSA");
		encryptCipher.init(Cipher.ENCRYPT_MODE, pair.getPublic());

		byte[] cipherText = encryptCipher.doFinal(message.getBytes(StandardCharsets.UTF_8));

		// Now decrypt it
		Cipher decriptCipher = Cipher.getInstance("RSA");
		decriptCipher.init(Cipher.DECRYPT_MODE, pair.getPrivate());

		String decipheredMessage = new String(decriptCipher.doFinal(cipherText), StandardCharsets.UTF_8);

		System.out.println(decipheredMessage);
	}
}
