package com.jeesharp.business.modules.pay.paysubmerchantapplyment.support;

import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.jeesharp.business.modules.pay.config.weixin.PayWechatCredentialSupport;
import com.jeesharp.business.modules.pay.paywechatconfig.entity.PayWechatConfig;
import com.jeesharp.business.modules.pay.weixinpay.v3.util.RsaCryptoUtil;
import org.springframework.util.StringUtils;

import java.security.PublicKey;
import java.util.List;

/**
 * 特约商户进件敏感字段加密（微信支付公钥 OAEP）。
 *
 * @author jeesharp
 * @since 2026-05-26
 */
public final class PayWechatApplymentCryptoSupport {

    /** 需加密的 JSON 路径（点分，数组项用 [] 表示） */
    private static final List<String> SENSITIVE_PATHS = List.of(
            "contact_info.contact_name",
            "contact_info.contact_id_number",
            "contact_info.mobile_phone",
            "contact_info.contact_email",
            "contact_info.openid",
            "subject_info.identity_info.id_card_info.id_card_name",
            "subject_info.identity_info.id_card_info.id_card_number",
            "subject_info.identity_info.id_card_info.id_card_address",
            "subject_info.identity_info.id_doc_info.id_doc_name",
            "subject_info.identity_info.id_doc_info.id_doc_number",
            "subject_info.identity_info.id_doc_info.id_doc_address",
            "bank_account_info.account_name",
            "bank_account_info.account_number",
            "subject_info.ubo_info_list[].ubo_id_doc_name",
            "subject_info.ubo_info_list[].ubo_id_doc_number",
            "subject_info.ubo_info_list[].ubo_id_doc_address"
    );

    /** 修改结算账户接口顶层敏感字段 */
    private static final List<String> SETTLEMENT_MODIFY_SENSITIVE_PATHS = List.of(
            "account_number",
            "account_name"
    );

    /** 商户注销提现 bank_account_info 敏感字段 */
    private static final List<String> CANCEL_WITHDRAW_SENSITIVE_PATHS = List.of(
            "bank_account_info.account_number",
            "bank_account_info.account_name"
    );

    /** 电商进件 ecommerce/applyments 敏感字段（扁平结构，对齐 4012713017） */
    private static final List<String> ECOMMERCE_SENSITIVE_PATHS = List.of(
            "contact_info.contact_name",
            "contact_info.contact_id_card_number",
            "contact_info.mobile_phone",
            "id_card_info.id_card_name",
            "id_card_info.id_card_number",
            "id_doc_info.id_doc_name",
            "id_doc_info.id_doc_number",
            "id_doc_info.id_doc_address",
            "account_info.account_name",
            "account_info.account_number",
            "ubo_info_list[].ubo_id_doc_name",
            "ubo_info_list[].ubo_id_doc_number",
            "ubo_info_list[].ubo_id_doc_address"
    );

    private PayWechatApplymentCryptoSupport() {
    }

    /**
     * 修改结算账户请求 JSON 加密（顶层 account_number、account_name）。
     */
    public static String encryptSettlementModifyFields(String requestJson, PayWechatConfig config) {
        if (!StringUtils.hasText(requestJson)) {
            throw new IllegalArgumentException("修改结算账户 JSON 不能为空");
        }
        String pem = PayWechatCredentialSupport.resolveWechatPayPublicKeyPem(config);
        if (!StringUtils.hasText(pem)) {
            throw new IllegalStateException("未配置微信支付公钥，无法加密敏感字段");
        }
        PublicKey publicKey = PayWechatCredentialSupport.parseWechatPayPublicKey(pem);
        JSONObject root = JSONUtil.parseObj(requestJson);
        for (String path : SETTLEMENT_MODIFY_SENSITIVE_PATHS) {
            encryptStringFieldByPath(root, path, publicKey);
        }
        return root.toString();
    }

    /**
     * 商户注销提现请求 JSON 加密（bank_account_info 下 account_number、account_name）。
     */
    public static String encryptCancelWithdrawFields(String requestJson, PayWechatConfig config) {
        if (!StringUtils.hasText(requestJson)) {
            throw new IllegalArgumentException("商户注销 JSON 不能为空");
        }
        String pem = PayWechatCredentialSupport.resolveWechatPayPublicKeyPem(config);
        if (!StringUtils.hasText(pem)) {
            throw new IllegalStateException("未配置微信支付公钥，无法加密敏感字段");
        }
        PublicKey publicKey = PayWechatCredentialSupport.parseWechatPayPublicKey(pem);
        JSONObject root = JSONUtil.parseObj(requestJson);
        for (String path : CANCEL_WITHDRAW_SENSITIVE_PATHS) {
            encryptStringFieldByPath(root, path, publicKey);
        }
        return root.toString();
    }

    /**
     * 电商进件请求 JSON 敏感字段加密。
     */
    public static String encryptEcommerceSensitiveFields(String requestJson, PayWechatConfig config) {
        return encryptByPaths(requestJson, config, ECOMMERCE_SENSITIVE_PATHS);
    }

    /**
     * 对进件请求 JSON 中敏感字段加密（已是密文则跳过）。
     */
    public static String encryptSensitiveFields(String requestJson, PayWechatConfig config) {
        return encryptByPaths(requestJson, config, SENSITIVE_PATHS);
    }

    private static String encryptByPaths(String requestJson, PayWechatConfig config, List<String> paths) {
        if (!StringUtils.hasText(requestJson)) {
            throw new IllegalArgumentException("进件 JSON 不能为空");
        }
        String pem = PayWechatCredentialSupport.resolveWechatPayPublicKeyPem(config);
        if (!StringUtils.hasText(pem)) {
            throw new IllegalStateException("未配置微信支付公钥，无法加密敏感字段");
        }
        PublicKey publicKey = PayWechatCredentialSupport.parseWechatPayPublicKey(pem);
        JSONObject root = JSONUtil.parseObj(requestJson);
        for (String path : paths) {
            encryptPath(root, path, publicKey);
        }
        return root.toString();
    }

    private static void encryptPath(JSONObject root, String path, PublicKey publicKey) {
        if (path.contains("[]")) {
            String[] parts = path.split("\\[\\]");
            String arrayPath = parts[0];
            String field = parts[1].startsWith(".") ? parts[1].substring(1) : parts[1];
            JSONArray arr = getArrayByPath(root, arrayPath);
            if (arr == null) {
                return;
            }
            for (int i = 0; i < arr.size(); i++) {
                Object item = arr.get(i);
                if (item instanceof JSONObject obj) {
                    encryptStringField(obj, field, publicKey);
                }
            }
            return;
        }
        encryptStringFieldByPath(root, path, publicKey);
    }

    private static void encryptStringFieldByPath(JSONObject root, String path, PublicKey publicKey) {
        String[] segments = path.split("\\.");
        JSONObject current = root;
        for (int i = 0; i < segments.length - 1; i++) {
            Object next = current.get(segments[i]);
            if (!(next instanceof JSONObject)) {
                return;
            }
            current = (JSONObject) next;
        }
        encryptStringField(current, segments[segments.length - 1], publicKey);
    }

    private static JSONArray getArrayByPath(JSONObject root, String path) {
        String[] segments = path.split("\\.");
        JSONObject current = root;
        for (int i = 0; i < segments.length - 1; i++) {
            Object next = current.get(segments[i]);
            if (!(next instanceof JSONObject)) {
                return null;
            }
            current = (JSONObject) next;
        }
        Object arr = current.get(segments[segments.length - 1]);
        return arr instanceof JSONArray ? (JSONArray) arr : null;
    }

    private static void encryptStringField(JSONObject obj, String field, PublicKey publicKey) {
        if (obj == null || !obj.containsKey(field)) {
            return;
        }
        String plain = obj.getStr(field);
        if (!StringUtils.hasText(plain) || looksEncrypted(plain)) {
            return;
        }
        try {
            obj.set(field, RsaCryptoUtil.encryptOAEP(plain.trim(), publicKey));
        } catch (Exception e) {
            throw new IllegalStateException("加密字段失败: " + field, e);
        }
    }

    /** 已加密串通常较长且为 Base64 */
    private static boolean looksEncrypted(String value) {
        if (value.length() < 80) {
            return false;
        }
        return value.matches("^[A-Za-z0-9+/=]+$");
    }
}
