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

import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

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

import org.apache.commons.lang3.StringUtils;

import com.eactive.eai.adapter.http.filter.AbstractCryptoFilter;

/**
 * HTTP 서버 어댑터용 암복호화 필터 (수신 방향).
 *
 * doPreFilter  : 수신 요청 복호화 (CRYPTO_DEC_* 프로퍼티 기반)
 * doPostFilter : 송신 응답 암호화 (CRYPTO_ENC_* 프로퍼티 기반)
 *
 * DYNAMIC 키 컨텍스트가 필요한 경우 {@link #buildRuntimeContext}를 오버라이드한다.
 * STATIC 키 모듈을 사용하는 경우 빈 Map이 기본값이므로 오버라이드 불필요.
 *
 * 프로퍼티 설명은 {@link AbstractCryptoFilter} 참조.
 */
public class InCryptoFilter extends AbstractCryptoFilter implements HttpAdapterFilter {

	public static final String ERROR_DEC_FAIL = "E.DECRYPT_FAIL";
	public static final String ERROR_ENC_FAIL = "E.ENCRYPT_FAIL";
	/**
     * DYNAMIC 키 도출용 컨텍스트를 구성한다.
     * STATIC 키 모듈이면 빈 Map({@code new HashMap<>()})을 반환한다.
     * 서브클래스에서 오버라이드하여 HttpServletRequest로부터 값을 추출할 수 있다.
     */
    protected Map<String, String> buildRuntimeContext(Properties prop, HttpServletRequest request) {
    	Map<String, String> context = new HashMap<>();
    	for (String key : prop.stringPropertyNames()) {
    		context.put(key, prop.getProperty(key));
    	}
    	return context;
    }

    /**
     * CRYPTO_AAD_HEADER 프로퍼티에 지정된 헤더값을 UTF-8 바이트로 반환한다.
     * 미설정 또는 헤더값 없으면 null을 반환하여 GCM 기본 동작(IV를 AAD 대체값으로 사용)을 따른다.
     */
    protected byte[] buildAad(Properties prop, HttpServletRequest request) {
        String headerName = prop.getProperty(PROP_AAD_HEADER);
        if (StringUtils.isBlank(headerName) || request == null) {
            return null;
        }
        Properties inboundHeaderProp = (Properties)prop.get("INBOUND_HEADER");
        String headerValue = inboundHeaderProp.getProperty(headerName);
//        String headerValue = request.getHeader(headerName);
        return StringUtils.isBlank(headerValue) ? null : headerValue.getBytes(StandardCharsets.UTF_8);
    }

    @Override
    public Object doPreFilter(String adptGrpName, String adptName, Object message, Properties prop,
            HttpServletRequest request, HttpServletResponse response) throws Exception {

        String moduleName = required(prop, PROP_MODULE_NAME);
        String scope      = getProp(prop, PROP_SCOPE, SCOPE_FIELD).toUpperCase();
        String body       = toBodyString(message);
        Map<String, String> runtimeCtx = buildRuntimeContext(prop, request);
        byte[] aad        = buildAad(prop, request);

        if (SCOPE_FIELD.equals(scope)) {
            return decryptField(body, moduleName, runtimeCtx, aad,
                    getProp(prop, PROP_DEC_FROM_PATH, PATH_ENCDATA),
                    getProp(prop, PROP_DEC_TO_PATH, PATH_ROOT));
        }
        return decryptBody(body, moduleName, runtimeCtx, aad);
    }

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

        String moduleName = required(prop, PROP_MODULE_NAME);
        String scope      = getProp(prop, PROP_SCOPE, SCOPE_FIELD).toUpperCase();
        String body       = toBodyString(resultMessage);
        Map<String, String> runtimeCtx = buildRuntimeContext(prop, request);
        byte[] aad        = buildAad(prop, request);

        if (SCOPE_FIELD.equals(scope)) {
            return encryptField(body, moduleName, runtimeCtx, aad,
                    getProp(prop, PROP_ENC_FROM_PATH, PATH_ROOT),
                    getProp(prop, PROP_ENC_TO_PATH, PATH_ENCDATA));
        }
        return encryptBody(body, moduleName, runtimeCtx, aad);
    }
}
