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

import cn.hutool.core.util.StrUtil;
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.config.weixin.PayWechatWxPayClientHolder;
import com.jeesharp.business.modules.pay.core.IJPayHttpResponse;
import com.jeesharp.business.modules.pay.core.enums.RequestMethod;
import com.jeesharp.business.modules.pay.paywechatconfig.entity.PayWechatConfig;
import com.jeesharp.business.modules.pay.weixin.support.PayWechatInterfaceLogScenario;
import com.jeesharp.business.modules.pay.wxpay.WxPayApi;
import com.jeesharp.business.modules.pay.wxpay.enums.WxApiType;
import com.jeesharp.business.modules.pay.wxpay.enums.WxDomain;
import com.jeesharp.framework.log.api.entity.InterfaceLogEntity;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.nio.file.Files;
import java.security.MessageDigest;
import java.util.HexFormat;

/**
 * 微信支付特约商户进件 V3 接口客户端。
 *
 * @author jeesharp
 * @since 2026-05-26
 */
@Component
@RequiredArgsConstructor
public class PayWechatSubMerchantApplymentClient {

    private final PayWechatWxPayClientHolder payWechatWxPayClientHolder;
    private final PayWechatInterfaceLogRecorder interfaceLogRecorder;

    /**
     * 提交申请单 POST /v3/applyment4sub/applyment/
     */
    public JSONObject submitApplyment(String requestJson, String tenantId) throws Exception {
        PayWechatConfig config = payWechatWxPayClientHolder.requireActiveConfig(tenantId);
        ensureWxReady(config);
        String body = PayWechatApplymentCryptoSupport.encryptSensitiveFields(requestJson, config);
        String suffix = WxApiType.APPLY_4_SUB.getType();
        String businessId = extractBusinessCode(requestJson);
        return executeLogged(
                PayWechatInterfaceLogScenario.APPLYMENT_SUBMIT,
                RequestMethod.POST.name(),
                suffix,
                body,
                businessId,
                tenantId,
                "提交进件失败",
                () -> v3Post(config, suffix, body));
    }

    /**
     * 按业务申请编号查询状态。
     */
    public JSONObject queryByBusinessCode(String businessCode, String tenantId) throws Exception {
        PayWechatConfig config = payWechatWxPayClientHolder.requireActiveConfig(tenantId);
        ensureWxReady(config);
        String suffix = String.format(WxApiType.GET_APPLY_STATE.getType(), businessCode);
        return executeLogged(
                PayWechatInterfaceLogScenario.APPLYMENT_QUERY_BY_BUSINESS_CODE,
                RequestMethod.GET.name(),
                suffix,
                null,
                businessCode,
                tenantId,
                "查询进件状态失败",
                () -> v3Get(config, suffix));
    }

    /**
     * 按申请单号查询状态。
     */
    public JSONObject queryByApplymentId(Long applymentId, String tenantId) throws Exception {
        PayWechatConfig config = payWechatWxPayClientHolder.requireActiveConfig(tenantId);
        ensureWxReady(config);
        String suffix = String.format(WxApiType.GET_APPLY_STATE_BY_ID.getType(), applymentId);
        return executeLogged(
                PayWechatInterfaceLogScenario.APPLYMENT_QUERY_BY_ID,
                RequestMethod.GET.name(),
                suffix,
                null,
                String.valueOf(applymentId),
                tenantId,
                "查询进件状态失败",
                () -> v3Get(config, suffix));
    }

    /**
     * 上传图片资料，返回 media_id。
     */
    public String uploadImage(MultipartFile file, String tenantId) throws Exception {
        PayWechatConfig config = payWechatWxPayClientHolder.requireActiveConfig(tenantId);
        ensureWxReady(config);
        if (file == null || file.isEmpty()) {
            throw new IllegalArgumentException("请上传图片文件");
        }
        String originalName = StrUtil.blankToDefault(file.getOriginalFilename(), "image.jpg");
        File temp = Files.createTempFile("wx-applyment-", "-" + originalName).toFile();
        try {
            file.transferTo(temp);
            byte[] bytes = Files.readAllBytes(temp.toPath());
            String sha256 = sha256Hex(bytes);
            JSONObject meta = new JSONObject();
            meta.set("filename", temp.getName());
            meta.set("sha256", sha256);
            String metaJson = meta.toString();
            String suffix = WxApiType.MERCHANT_UPLOAD_MEDIA.getType();
            JSONObject json = executeLogged(
                    PayWechatInterfaceLogScenario.APPLYMENT_UPLOAD_MEDIA,
                    RequestMethod.POST.name(),
                    suffix,
                    metaJson,
                    temp.getName(),
                    tenantId,
                    "上传进件资料失败",
                    () -> v3Upload(config, suffix, metaJson, temp));
            return json.getStr("media_id");
        } finally {
            Files.deleteIfExists(temp.toPath());
        }
    }

    /**
     * 修改结算账户 POST /v3/apply4sub/sub_merchants/{sub_mchid}/modify-settlement
     */
    public JSONObject modifySettlement(String subMchId, String requestJson, String tenantId) throws Exception {
        PayWechatConfig config = payWechatWxPayClientHolder.requireActiveConfig(tenantId);
        ensureWxReady(config);
        String body = PayWechatApplymentCryptoSupport.encryptSettlementModifyFields(requestJson, config);
        String suffix = String.format(WxApiType.MODIFY_SETTLEMENT.getType(), subMchId);
        return executeLogged(
                PayWechatInterfaceLogScenario.SETTLEMENT_MODIFY_SUBMIT,
                RequestMethod.POST.name(),
                suffix,
                body,
                subMchId,
                tenantId,
                "修改结算账户失败",
                () -> v3Post(config, suffix, body));
    }

    /**
     * 查询当前结算账户 GET /v3/apply4sub/sub_merchants/{sub_mchid}/settlement
     */
    public JSONObject getSettlement(String subMchId, String tenantId) throws Exception {
        PayWechatConfig config = payWechatWxPayClientHolder.requireActiveConfig(tenantId);
        ensureWxReady(config);
        String suffix = String.format(WxApiType.GET_SETTLEMENT.getType(), subMchId);
        return executeLogged(
                PayWechatInterfaceLogScenario.SETTLEMENT_GET,
                RequestMethod.GET.name(),
                suffix,
                null,
                subMchId,
                tenantId,
                "查询结算账户失败",
                () -> v3Get(config, suffix));
    }

    /**
     * 查询修改结算账户审核结果。
     */
    public JSONObject queryModifySettlementApplication(String subMchId, String applicationNo, String tenantId) throws Exception {
        PayWechatConfig config = payWechatWxPayClientHolder.requireActiveConfig(tenantId);
        ensureWxReady(config);
        String suffix = String.format(WxApiType.GET_MODIFY_SETTLEMENT_APPLICATION.getType(), subMchId, applicationNo);
        return executeLogged(
                PayWechatInterfaceLogScenario.SETTLEMENT_MODIFY_QUERY,
                RequestMethod.GET.name(),
                suffix,
                null,
                applicationNo,
                tenantId,
                "查询修改结算账户审核状态失败",
                () -> v3Get(config, suffix));
    }

    private JSONObject executeLogged(
            String scenarioType,
            String httpMethod,
            String urlSuffix,
            String requestParam,
            String businessId,
            String tenantId,
            String action,
            V3Call call) throws Exception {
        String requestUrl = WxDomain.CHINA.getType() + urlSuffix;
        long startMs = System.currentTimeMillis();
        InterfaceLogEntity logEntity = interfaceLogRecorder.begin(
                scenarioType, httpMethod, requestUrl, requestParam, businessId, "applymentTenantId=" + StrUtil.blankToDefault(tenantId, ""));
        try {
            IJPayHttpResponse response = call.execute();
            int status = response != null ? response.getStatus() : 0;
            if (status >= 200 && status < 300) {
                interfaceLogRecorder.completeSuccess(logEntity, response, startMs);
            } else {
                interfaceLogRecorder.completeHttpFail(logEntity, response, startMs, buildHttpErrorMessage(response, action));
            }
            // parseResponse 在非 2xx 时会抛异常；须在落库之后再抛，避免 catch 中重复 insert 同一主键
            JSONObject result = parseResponse(response, action);
            interfaceLogRecorder.saveAsync(logEntity);
            return result;
        } catch (Exception e) {
            interfaceLogRecorder.completeError(logEntity, startMs, e.getMessage());
            interfaceLogRecorder.saveAsync(logEntity);
            throw e;
        }
    }

    private static String buildHttpErrorMessage(IJPayHttpResponse response, String action) {
        if (response == null) {
            return action + "：无响应";
        }
        String body = response.getBody();
        if (StringUtils.hasText(body) && JSONUtil.isTypeJSON(body)) {
            JSONObject err = JSONUtil.parseObj(body);
            String msg = err.getStr("message", body);
            if (err.containsKey("code")) {
                return err.getStr("code") + ": " + msg;
            }
            return msg;
        }
        return action + "（HTTP " + response.getStatus() + "）";
    }

    private static String extractBusinessCode(String requestJson) {
        if (!StringUtils.hasText(requestJson) || !JSONUtil.isTypeJSON(requestJson)) {
            return null;
        }
        try {
            return JSONUtil.parseObj(requestJson).getStr("business_code");
        } catch (Exception ignored) {
            return null;
        }
    }

    private static void ensureWxReady(PayWechatConfig config) {
        if (!PayWechatCredentialSupport.isWxEnabled(config)) {
            throw new IllegalStateException("请先在【支付 · 微信配置】中启用「调用真实微信 V3」");
        }
        if (!StringUtils.hasText(config.getPublicKeyId())) {
            throw new IllegalStateException("进件需配置微信支付公钥 ID（publicKeyId）用于敏感字段加密");
        }
        if (!StringUtils.hasText(PayWechatCredentialSupport.resolveWechatPayPublicKeyPem(config))) {
            throw new IllegalStateException("进件需配置微信支付公钥 PEM 或路径");
        }
        if (!StringUtils.hasText(config.getCertPrivateKeyPath())) {
            throw new IllegalStateException("进件需配置商户 API 私钥路径 certPrivateKeyPath");
        }
    }

    private IJPayHttpResponse v3Post(PayWechatConfig config, String urlSuffix, String body) throws Exception {
        return WxPayApi.v3(RequestMethod.POST, WxDomain.CHINA.getType(), urlSuffix,
                config.getMchId(),
                PayWechatCredentialSupport.resolveMerchantSerialNo(config),
                config.getPublicKeyId(),
                config.getCertPrivateKeyPath().trim(),
                body);
    }

    private IJPayHttpResponse v3Get(PayWechatConfig config, String urlSuffix) throws Exception {
        return WxPayApi.v3(RequestMethod.GET, WxDomain.CHINA.getType(), urlSuffix,
                config.getMchId(),
                PayWechatCredentialSupport.resolveMerchantSerialNo(config),
                config.getPublicKeyId(),
                config.getCertPrivateKeyPath().trim(),
                "");
    }

    private IJPayHttpResponse v3Upload(PayWechatConfig config, String urlSuffix, String meta, File file) throws Exception {
        return WxPayApi.v3(WxDomain.CHINA.getType(), urlSuffix,
                config.getMchId(),
                PayWechatCredentialSupport.resolveMerchantSerialNo(config),
                config.getPublicKeyId(),
                config.getCertPrivateKeyPath().trim(),
                meta, file);
    }

    private static JSONObject parseResponse(IJPayHttpResponse response, String action) {
        if (response == null) {
            throw new IllegalStateException(action + "：无响应");
        }
        int status = response.getStatus();
        String body = response.getBody();
        if (status >= 200 && status < 300) {
            return StringUtils.hasText(body) ? JSONUtil.parseObj(body) : new JSONObject();
        }
        String msg = body;
        if (StringUtils.hasText(body) && JSONUtil.isTypeJSON(body)) {
            JSONObject err = JSONUtil.parseObj(body);
            msg = err.getStr("message", body);
            if (err.containsKey("code")) {
                msg = err.getStr("code") + ": " + msg;
            }
        }
        throw new IllegalStateException(action + "（HTTP " + status + "）：" + msg);
    }

    private static String sha256Hex(byte[] data) throws Exception {
        MessageDigest digest = MessageDigest.getInstance("SHA-256");
        return HexFormat.of().formatHex(digest.digest(data));
    }

    @FunctionalInterface
    private interface V3Call {
        IJPayHttpResponse execute() throws Exception;
    }
}
