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

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

import org.apache.commons.lang3.StringUtils;

import com.eactive.eai.common.security.CryptoModuleService;
import com.eactive.eai.common.util.Logger;
import com.eactive.eai.util.JsonPathUtil;
import com.fasterxml.jackson.databind.JsonNode;

/**
 * InCryptoFilter / OutCryptoFilter 공통 기반 클래스.
 *
 * BODY/FIELD 범위 암복호화 로직, 프로퍼티 상수, 유틸리티 메서드를 제공한다.
 * HTTP 서버 어댑터용은 {@link InCryptoFilter}, 클라이언트 어댑터용은 {@link OutCryptoFilter} 참조.
 *
 * 프로퍼티 키:
 *   CRYPTO_MODULE_NAME   필수. DB에 등록된 암호화 모듈명.
 *   CRYPTO_SCOPE         BODY | FIELD (기본값: FIELD)
 *                        BODY  : 메시지 전체를 암복호화 (Base64 인코딩/디코딩)
 *                        FIELD : 특정 JSON 경로의 값만 암복호화
 *
 *   FIELD 범위 전용:
 *   CRYPTO_DEC_FROM_PATH 복호화 대상 JSON 경로 (기본값: /encrypted_data)
 *   CRYPTO_DEC_TO_PATH   복호화 결과 반영 경로. "/" = 전체 body 교체.
 *   CRYPTO_ENC_FROM_PATH 암호화 대상 JSON 경로. "/" = 전체 body 암호화.
 *   CRYPTO_ENC_TO_PATH   암호화 결과(Base64)를 넣을 JSON 경로 (기본값: /encrypted_data)
 *
 *   GCM AAD 전용 (선택):
 *   CRYPTO_AAD_HEADER    AAD로 사용할 HTTP 요청 헤더명 (InCryptoFilter 전용).
 *                        미설정 또는 헤더값 없으면 aad=null → IV를 AAD 대체값으로 사용.
 */
public abstract class AbstractCryptoFilter {

    protected static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);

    public static final String PROP_MODULE_NAME   = "CRYPTO_MODULE_NAME";
    public static final String PROP_SCOPE         = "CRYPTO_SCOPE";
    public static final String PROP_DEC_FROM_PATH = "CRYPTO_DEC_FROM_PATH";
    public static final String PROP_DEC_TO_PATH   = "CRYPTO_DEC_TO_PATH";
    public static final String PROP_ENC_FROM_PATH = "CRYPTO_ENC_FROM_PATH";
    public static final String PROP_ENC_TO_PATH   = "CRYPTO_ENC_TO_PATH";
    public static final String PROP_AAD_HEADER    = "CRYPTO_AAD_HEADER";

    public static final String SCOPE_BODY   = "BODY";
    public static final String SCOPE_FIELD  = "FIELD";
    public static final String PATH_ROOT    = "/";
    public static final String PATH_ENCDATA = "/encrypted_data";

    // ------------------------------------------------------------------
    // 복호화 구현
    // ------------------------------------------------------------------

    protected String decryptBody(String body, String moduleName, Map<String, String> runtimeCtx,
            byte[] aad) throws Exception {
        byte[] cipherBytes = Base64.getDecoder().decode(body.trim());
        byte[] plainBytes  = CryptoModuleService.getInstance().decrypt(moduleName, runtimeCtx, aad, cipherBytes);
        return new String(plainBytes, StandardCharsets.UTF_8);
    }

    /**
     * fromPath의 Base64 값을 복호화하여 toPath에 반영. JSON 파싱 1회.
     * toPath = "/" → fromPath 필드를 제거하고 복호화된 JSON을 body에 병합.
     */
    protected Object decryptField(String body, String moduleName, Map<String, String> runtimeCtx,
            byte[] aad, String fromPath, String toPath) throws Exception {
        JsonNode root = JsonPathUtil.toTree(body);
        String encBase64 = JsonPathUtil.getAt(root, fromPath);
        if (StringUtils.isBlank(encBase64)) {
            logger.warn("CryptoFilter] 복호화 대상 필드 없음: path=" + fromPath);
            return body;
        }
        byte[] cipherBytes = Base64.getDecoder().decode(encBase64.trim());
        byte[] plainBytes  = CryptoModuleService.getInstance().decrypt(moduleName, runtimeCtx, aad, cipherBytes);
        String plainText   = new String(plainBytes, StandardCharsets.UTF_8);
        if (PATH_ROOT.equals(toPath)) {
            return JsonPathUtil.mergeAtRoot(root, fromPath, plainText);
        }
        JsonPathUtil.setAt(root, toPath, plainText);
        return root;
    }

    // ------------------------------------------------------------------
    // 암호화 구현
    // ------------------------------------------------------------------

    protected String encryptBody(String body, String moduleName, Map<String, String> runtimeCtx,
            byte[] aad) throws Exception {
        byte[] cipherBytes = CryptoModuleService.getInstance().encrypt(moduleName, runtimeCtx, aad,
                body.getBytes(StandardCharsets.UTF_8));
        return Base64.getEncoder().encodeToString(cipherBytes);
    }

    /**
     * fromPath 값을 암호화하여 toPath(Base64)에 반영. JSON 파싱 1회.
     * fromPath = "/" → body 전체를 암호화하여 toPath 필드명으로 래핑 (파싱 불필요).
     */
    protected Object encryptField(String body, String moduleName, Map<String, String> runtimeCtx,
            byte[] aad, String fromPath, String toPath) throws Exception {
        if (PATH_ROOT.equals(fromPath)) {
            byte[] cipherBytes = CryptoModuleService.getInstance().encrypt(moduleName, runtimeCtx, aad,
                    body.getBytes(StandardCharsets.UTF_8));
            String encBase64 = Base64.getEncoder().encodeToString(cipherBytes);
            String fieldName = toPath.replaceFirst("^/", "");
            return "{\"" + fieldName + "\":\"" + encBase64 + "\"}";
        }
        JsonNode root = JsonPathUtil.toTree(body);
        String plainText = JsonPathUtil.getAt(root, fromPath);
        if (StringUtils.isBlank(plainText)) {
            logger.warn("CryptoFilter] 암호화 대상 필드 없음: path=" + fromPath);
            return root;
        }
        byte[] cipherBytes = CryptoModuleService.getInstance().encrypt(moduleName, runtimeCtx, aad,
                plainText.getBytes(StandardCharsets.UTF_8));
        String encBase64 = Base64.getEncoder().encodeToString(cipherBytes);
        JsonPathUtil.setAt(root, toPath, encBase64);
        return root;
    }

    // ------------------------------------------------------------------
    // 유틸
    // ------------------------------------------------------------------

    /**
     * 필터로 전달되는 message 객체를 JSON 문자열로 변환한다.
     */
    protected String toBodyString(Object message) {
        if (message instanceof String) {
            return (String) message;
        }
        if (message instanceof byte[]) {
            return new String((byte[]) message, StandardCharsets.UTF_8);
        }
        return message.toString();
    }

    protected String required(Properties prop, String key) {
        String v = prop.getProperty(key);
        if (StringUtils.isBlank(v)) {
            throw new IllegalArgumentException("CryptoFilter 필수 프로퍼티 누락: " + key);
        }
        return v;
    }

    protected String getProp(Properties prop, String key, String defaultVal) {
        String v = prop.getProperty(key);
        return StringUtils.isBlank(v) ? defaultVal : v;
    }
}
