package com.eactive.eai.adapter.http.dynamic.filter.custom;

import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Properties;

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

import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.codec.digest.HmacAlgorithms;
import org.apache.commons.codec.digest.HmacUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpStatus;

import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceSupport;
import com.eactive.eai.adapter.http.dynamic.filter.HttpAdapterFilter;
import com.eactive.eai.adapter.http.dynamic.filter.JwtAuthException;
import com.eactive.eai.authserver.service.OAuth2Manager;
import com.eactive.eai.authserver.vo.ClientVO;
import com.eactive.eai.common.util.Logger;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

public class SnapHmacSha512VerifyFilter implements HttpAdapterFilter {
	static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);

	private static final String SERVICE_CODE_UNKNOWN = "00";
	private static final int X_TIMESTAMP_EXPIRATION_SECONDS = 60 * 5;

	@Override
	public Object doPreFilter(String adptGrpName, String adptName, Object message, Properties prop,
			HttpServletRequest request, HttpServletResponse response) throws Exception {
		// @formatter:off
		/*
		 * HMAC_SHA512 (clientSecret, stringToSign)
		 * 
		 * stringToSign = HTTPMethod:EndpointUrl:AccessToken
		 *                :Lowercase(HexEncode(SHA-256(minify(RequestBody)))):TimeStamp
		 */
		try {
			String xSignature = request.getHeader("X-SIGNATURE");
			if (StringUtils.isBlank(xSignature)) {
				throw new JwtAuthException(String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "00"), "Can not find X-SIGNATURE");
			}
			
			String clientSecret = assignClientSecret(prop, request);
			
			StringBuilder sb = new StringBuilder();
			sb.append(request.getMethod())
					.append(":")
					.append(getEndpointUrl(request))
					.append(":")
					.append(SnapSimpleOauth2Filter.extractToken(request))
					.append(":")
					.append(getRequestBody((String)message))
					.append(":")
					.append(getTimeStamp(request));
			// @formatter:on

			if (logger.isDebug()) {
				logger.debug(sb.toString());
			}

			String hmac = new HmacUtils(HmacAlgorithms.HMAC_SHA_512, clientSecret).hmacHex(sb.toString());

			if (!StringUtils.equals(xSignature, hmac)) {
				throw new JwtAuthException(
						String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "00"),
						"Signature verifying failed");
			}
		} catch (JwtAuthException e) {
			logger.debug(e.getMessage());
			throw e;
		} catch (Exception e) {
			logger.error(e.getMessage());
			throw new JwtAuthException(
					String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "00"),
					"Signature verifying failed(unkown)");
		}

		return message;
	}

	private String assignClientSecret(Properties prop, HttpServletRequest request) throws Exception {
		String clientId = assignClientId(prop, request);
		if (StringUtils.isBlank(clientId)) {
			throw new JwtAuthException(
					String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "00"),
					"Can not find clientId info");
		}

		ClientVO client = (ClientVO) OAuth2Manager.getInstance().getClientDeatilsStore().get(clientId);
		if (client == null) {
			throw new JwtAuthException(
					String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "00"),
					"Can not find client info");
		}

		return client.getClientSecret();
	}

	private String assignClientId(Properties prop, HttpServletRequest request) {
		String clientId = null;
		try {
			// 이전 filter에서 셋팅한 clientId 확보
			clientId = prop.getProperty(HttpAdapterServiceSupport.PROPERTIES_NAME_CLIENT_ID);

			// 이전 filter에서 셋팅한 값이 없다면 header 정보에서 확보
			if (StringUtils.isBlank(clientId)) {
				clientId = request.getHeader(HttpAdapterServiceSupport.HEADER_NAME_CLIENT_ID);
			}
		} catch (Exception e) {
			logger.error(e.getMessage());
		}

		return clientId;
	}

	private String getEndpointUrl(HttpServletRequest request) {
		String servletPath = request.getServletPath();
		String pathInfo = request.getPathInfo();
		String queryString = request.getQueryString();

		StringBuilder url = new StringBuilder();
		url.append(servletPath);

		if (pathInfo != null) {
			url.append(pathInfo);
		}

		if (queryString != null) {
			url.append("?").append(queryString);
		}

		return url.toString();
	}

	private String getRequestBody(String message) throws Exception {
		if (StringUtils.isEmpty(message)) {
			return "";
		}

		// Lowercase(HexEncode(SHA-256(minify(RequestBody))))
		String body = minifyJson(message);
		try {
			body = DigestUtils.sha256Hex(body);
		} catch (Exception e) {
			throw new JwtAuthException(
					String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "00"),
					"SHA-256 digest error");
		}
		return body.toLowerCase();
	}

	private String minifyJson(String json) {
		try {
			ObjectMapper objectMapper = new ObjectMapper();
			JsonNode jsonNode = objectMapper.readValue(json, JsonNode.class);
			return jsonNode.toString();
		} catch (Exception e) {
			// json이 아닐경우
			return json;
		}
	}

	private String getTimeStamp(HttpServletRequest request) throws Exception {
		String timeStamp = request.getHeader("X-TIMESTAMP");
		if (StringUtils.isBlank(timeStamp)) {
			throw new JwtAuthException(
					String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "00"),
					"Can not find X-TIMESTAMP");
		}

		// 2020-12-23T09:10:11+07:00(현재시간이랑 비교로직 추가-보안)
		ZonedDateTime xTimeStamp = ZonedDateTime.parse(timeStamp, DateTimeFormatter.ISO_DATE_TIME);
		xTimeStamp = xTimeStamp.plusSeconds(X_TIMESTAMP_EXPIRATION_SECONDS);
		ZonedDateTime now = ZonedDateTime.now();
		if (xTimeStamp.isBefore(now.withZoneSameInstant(xTimeStamp.getZone()))) {
			throw new JwtAuthException(
					String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "00"),
					"Too old X-TIMESTAMP");
		}

		return timeStamp;
	}

	@Override
	public Object doPostFilter(String adptGrpName, String adptName, Object resultMessage, Properties prop,
			HttpServletRequest request, HttpServletResponse response) throws Exception {
		return resultMessage;
	}

	public static void main(String[] args) {
		String str = "POST:/api/test/aaa/type02/555:"
				+ "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzY29wZSI6WyJzbmFwIl0sImV4cCI6MTY3NjUyNDI1MSwianRpIjoiNzI0MWMyZGUtZjJhNi00NTFmLWExMjQtZGM3NmI0ZGY1OThjIiwiY2xpZW50X2lkIjoic1NHSkR3bEpzZWw1V2VYb2R6ZDNKM01RMjBLWUNacEwifQ.Obe8t8IwuQ3P7i4yCSASYze-9DLmVwXe0g_gBYlwv_Jzc7V8-ooTwp6SVnaNsM6pp1GV-9lxMpAzQO_CRHQq_hdMeiPI_CXHh3gETvjQThn57QGEGdCNp_lUEVYtMPUxi5u3X6Of07o0_OB83WI7rA1zD0DaP8V67pgfq-jMRe_3IXztOYu_nwMgREbGvs9tqcsjxppRKkEHcK1S8Eq4HzaLwjwyHJAhIlTba27yd4T8ELC7IpFphJBfEPF_t0ZpYW15lOrg5pHPjy1xpTV0Kgipog5wycTZlFUeCWWjfxTrpVmt_1XlEOlZH9X6xAefWrH93_fpbt3OcxwP4u9KhA"
				+ ":424058b889b9d860d13b79d90df52ddcbefba2fc9d27607e999c7c3c4d61d9c8:" + "2023-02-16T09:47:07+07:00";

		String hmac = new HmacUtils(HmacAlgorithms.HMAC_SHA_512,
				"o3Hre3voTrHbLueDrHC0LYM5effHQZILI8t23htbD12TKD5QJUMONqFqv4494iQS46bzHS0xW1fBC5LHo3D0SzhZvQcPUaJZw0iQ79NnzYFUCTrEsoFJRL300dp3Ql4y")
				.hmacHex(str);

		// System.out.println(hmac);
	}
}
