package com.eactive.eai.adapter.http.client.impl.filter;

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

import org.apache.commons.lang3.StringUtils;

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

/**
 * HTTP 클라이언트 어댑터용 암복호화 필터 (송신 방향).
 *
 * doPreFilter  : 외부 시스템으로 보내는 요청 암호화 (CRYPTO_ENC_* 프로퍼티 기반)
 * doPostFilter : 외부 시스템에서 받은 응답 복호화 (CRYPTO_DEC_* 프로퍼티 기반)
 *
 * DYNAMIC 키 컨텍스트가 필요한 경우 {@link #buildRuntimeContext}를 오버라이드한다.
 * STATIC 키 모듈을 사용하는 경우 빈 Map이 기본값이므로 오버라이드 불필요.
 *
 * 프로퍼티 설명은 {@link AbstractCryptoFilter} 참조.
 * AAD 관련 프로퍼티:
 *   PROP_AAD_HEADER  tempProp에서 AAD 값을 조회할 키 이름.
 *                    미설정 또는 해당 키 없으면 aad=null → IV를 AAD 대체값으로 사용.
 */
public class OutCryptoFilter extends AbstractCryptoFilter implements HttpClientAdapterFilter {

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

    /**
     * CRYPTO_AAD_PROP 프로퍼티에 지정된 키로 tempProp에서 AAD 값을 조회한다.
     * 미설정 또는 값 없으면 null을 반환하여 GCM 기본 동작(IV를 AAD 대체값으로 사용)을 따른다.
     */
    protected byte[] buildAad(Properties prop, Properties tempProp) {
    	if (tempProp == null) {
    		return null;
    	}
        String value = tempProp.getProperty(PROP_AAD_HEADER);
        return StringUtils.isBlank(value) ? null : value.getBytes(StandardCharsets.UTF_8);
    }

    @Override
    public Object doPreFilter(String adptGrpName, String adptName, Properties prop, Object message,
            Properties tempProp) 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, tempProp);
        byte[] aad        = buildAad(prop, tempProp);

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

    @Override
    public Object doPostFilter(String adptGrpName, String adptName, Properties prop, Object message,
            Properties tempProp) 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, tempProp);
        byte[] aad        = buildAad(prop, tempProp);

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