package com.gxwebsoft.payment.service;

import cn.hutool.core.util.StrUtil;
import com.gxwebsoft.common.system.entity.Payment;
import com.gxwebsoft.payment.exception.PaymentException;
import com.wechat.pay.java.core.Config;
import com.wechat.pay.java.core.cipher.PrivacyEncryptor;
import com.wechat.pay.java.core.http.Constant;
import com.wechat.pay.java.core.http.DefaultHttpClientBuilder;
import com.wechat.pay.java.core.http.HttpClient;
import com.wechat.pay.java.core.http.HttpHeaders;
import com.wechat.pay.java.core.http.HttpMethod;
import com.wechat.pay.java.core.http.HttpRequest;
import com.wechat.pay.java.core.http.HttpResponse;
import com.wechat.pay.java.core.http.JsonRequestBody;
import com.wechat.pay.java.core.http.MediaType;
import com.wechat.pay.java.core.exception.ServiceException;
import com.wechat.pay.java.core.util.GsonUtil;
import com.google.gson.annotations.SerializedName;
import com.google.gson.reflect.TypeToken;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.lang.reflect.Type;
import java.util.List;

/**
 * 微信支付-商家转账到零钱封装
 *
 * 注意：部分商户号开通了“商家转账（升级版）”后，会被微信侧限制使用旧版“批量转账到零钱”接口（/v3/transfer/batches），
 * 需改用升级版接口（/v3/fund-app/mch-transfer/transfer-bills）。
 */
@Slf4j
@Service
public class WxTransferService {

    // 商家转账（升级版）接口在 fund-app 域下
    private static final String TRANSFER_BILLS_API =
        "https://api.mch.weixin.qq.com/v3/fund-app/mch-transfer/transfer-bills";
    // 兼容少数文档/环境差异：如 fund-app 路径不可用时，尝试该路径（仅在 404 时重试）
    private static final String TRANSFER_BILLS_API_FALLBACK =
        "https://api.mch.weixin.qq.com/v3/transfer/bills";

    @Resource
    private WxPayConfigService wxPayConfigService;

    /**
     * 商家转账（升级版）场景ID。直连模式下在“产品中心-商家转账”配置后获得。
     */
    @Value("${wechatpay.transfer.scene-id:1005}")
    private String transferSceneId;

    /**
     * 转账场景报备信息（升级版必填项之一，内容需与商户平台该 transfer_scene_id 的报备信息对应）。
     *
     * 配置示例（YAML）：
     * wechatpay:
     *   transfer:
     *     scene-report-infos-json: '[{"info_type":"...","info_content":"..."}]'
     */
    @Value("${wechatpay.transfer.scene-report-infos-json:}")
    private String transferSceneReportInfosJson;

    /**
     * 发起单笔“商家转账到零钱”（升级版接口 /v3/fund-app/mch-transfer/transfer-bills）。
     *
     * 默认：不需要用户在小程序确认页二次确认（直接受理转账）。
     *
     * @param tenantId   租户ID（用于获取微信支付配置）
     * @param openid     收款用户openid（必须是该appid下的openid）
     * @param amountYuan 转账金额（单位：元）
     * @param outBillNo  商家单号（字母/数字，商户内唯一）
     * @param remark     备注（<=32字符）
     * @param userName   收款用户姓名（可选；金额>=2000元时强制要求）
     */
    public TransferBillsResponse initiateSingleTransfer(Integer tenantId,
                                                        String openid,
                                                        BigDecimal amountYuan,
                                                        String outBillNo,
                                                        String remark,
                                                        String userName) throws PaymentException {
        return initiateSingleTransferInternal(tenantId, openid, amountYuan, outBillNo, remark, userName);
    }

    /**
     * 发起单笔“商家转账到零钱（小程序前端拉起收款确认页）”。
     *
     * 返回的 response.packageInfo（JSON 字段 package_info）用于小程序端调用 wx.requestMerchantTransfer。
     */
    public TransferBillsResponse initiateSingleTransferWithUserConfirm(Integer tenantId,
                                                                       String openid,
                                                                       BigDecimal amountYuan,
                                                                       String outBillNo,
                                                                       String remark,
                                                                       String userName) throws PaymentException {
        // 注意：微信侧会严格校验请求体字段，传入未定义字段（如 user_confirm）会直接 400(PARAM_ERROR)。
        // 此处不再向请求体写入 user_confirm，仅保留该方法用于兼容调用方（若微信侧返回 package_info，可供小程序拉起确认页）。
        return initiateSingleTransferInternal(tenantId, openid, amountYuan, outBillNo, remark, userName);
    }

    private TransferBillsResponse initiateSingleTransferInternal(Integer tenantId,
                                                                 String openid,
                                                                 BigDecimal amountYuan,
                                                                 String outBillNo,
                                                                 String remark,
                                                                 String userName) throws PaymentException {

        if (tenantId == null) {
            throw PaymentException.paramError("租户ID不能为空");
        }
        if (StrUtil.isBlank(openid)) {
            throw PaymentException.paramError("收款用户openid不能为空");
        }
        if (amountYuan == null || amountYuan.compareTo(BigDecimal.ZERO) <= 0) {
            throw PaymentException.amountError("转账金额必须大于0");
        }
        if (StrUtil.isBlank(transferSceneId)) {
            throw PaymentException.paramError("transfer_scene_id未配置，无法发起商家转账（升级版）");
        }
        if (StrUtil.isBlank(outBillNo) || !outBillNo.matches("^[0-9A-Za-z]+$")) {
            throw PaymentException.paramError("outBillNo不合法（仅允许数字/大小写字母）");
        }
        // 保守校验：多数微信单号字段限制在 5-32（你此前 out_batch_no 也是 5-32）
        if (outBillNo.length() < 5 || outBillNo.length() > 32) {
            throw PaymentException.paramError("outBillNo长度不合法（要求 5-32）");
        }

        // 微信要求金额单位为“分”，必须为整数
        long amountFen = amountYuan
            .movePointRight(2)
            .setScale(0, RoundingMode.HALF_UP)
            .longValueExact();

        if (amountFen <= 0) {
            throw PaymentException.amountError("转账金额换算为分后必须大于0");
        }

        // 金额 >= 2000 元时，必须传收款用户姓名；金额 < 0.3 元时，不允许传姓名
        boolean needUserName = amountFen >= 2000L * 100L;
        boolean forbidUserName = amountFen < 30L;
        if (needUserName && StrUtil.isBlank(userName)) {
            throw PaymentException.paramError("转账金额>=2000元时必须提供收款用户姓名");
        }
        if (forbidUserName) {
            userName = null;
        }

        // 升级版接口必填：transfer_scene_report_infos（且必须与 transfer_scene_id 的报备信息一致）
        List<TransferSceneReportInfo> sceneReportInfos = parseTransferSceneReportInfos();
        if (sceneReportInfos == null
            || sceneReportInfos.isEmpty()
            || sceneReportInfos.stream().anyMatch(i -> i == null
                || StrUtil.isBlank(i.getInfoType())
                || StrUtil.isBlank(i.getInfoContent()))) {
            throw PaymentException.paramError(
                "未传入完整且对应的转账场景报备信息：请在配置中设置 wechatpay.transfer.scene-report-infos-json（需与 transfer_scene_id="
                    + transferSceneId + " 的报备信息一致）");
        }

        Payment paymentConfig = wxPayConfigService.getPaymentConfigForStrategy(tenantId);
        Config wxPayConfig = wxPayConfigService.getWxPayConfig(tenantId);

        try {
            HttpClient httpClient = new DefaultHttpClientBuilder().config(wxPayConfig).build();
            PrivacyEncryptor encryptor = wxPayConfig.createEncryptor();

            TransferBillsRequest request = new TransferBillsRequest();
            request.setAppid(paymentConfig.getAppId());
            request.setOutBillNo(outBillNo);
            request.setTransferSceneId(transferSceneId);
            request.setOpenid(openid);
            request.setTransferAmount(amountFen);
            request.setTransferRemark(limitLen(remark, 32));
            request.setTransferSceneReportInfos(sceneReportInfos);
            log.debug("微信商家转账(升级版)请求参数: tenantId={}, outBillNo={}, transferSceneId={}, sceneReportInfosCount={}",
                tenantId, outBillNo, transferSceneId, sceneReportInfos.size());

            // 可选：转账结果通知地址（必须 https）
            if (StrUtil.isNotBlank(paymentConfig.getNotifyUrl()) && paymentConfig.getNotifyUrl().startsWith("https://")) {
                request.setNotifyUrl(paymentConfig.getNotifyUrl());
            }

            // 需要姓名时按平台证书加密，并带上 Wechatpay-Serial
            if (StrUtil.isNotBlank(userName)) {
                request.setUserName(encryptor.encrypt(userName));
            }

            HttpHeaders headers = new HttpHeaders();
            headers.addHeader(Constant.ACCEPT, MediaType.APPLICATION_JSON.getValue());
            headers.addHeader(Constant.CONTENT_TYPE, MediaType.APPLICATION_JSON.getValue());
            headers.addHeader(Constant.WECHAT_PAY_SERIAL, encryptor.getWechatpaySerial());

            HttpRequest httpRequest = buildTransferBillsHttpRequest(TRANSFER_BILLS_API, headers, request);
            HttpResponse<TransferBillsResponse> httpResponse;
            try {
                httpResponse = httpClient.execute(httpRequest, TransferBillsResponse.class);
            } catch (ServiceException se) {
                // 404 且无 body 通常意味着接口路径不正确；做一次兜底重试。
                if (se.getHttpStatusCode() == 404) {
                    log.warn("商家转账接口返回404，尝试fallback路径: tenantId={}, outBillNo={}, url={}",
                        tenantId, outBillNo, TRANSFER_BILLS_API_FALLBACK);
                    HttpRequest fallbackReq = buildTransferBillsHttpRequest(TRANSFER_BILLS_API_FALLBACK, headers, request);
                    httpResponse = httpClient.execute(fallbackReq, TransferBillsResponse.class);
                } else if (se.getHttpStatusCode() == 400
                    && "PARAM_ERROR".equals(se.getErrorCode())
                    && se.getErrorMessage() != null
                    && se.getErrorMessage().contains("转账场景报备信息")) {
                    // 常见：升级版商家转账需带 transfer_scene_report_infos，且内容必须与商户平台场景报备一致
                    throw PaymentException.paramError(
                        "未传入完整且对应的转账场景报备信息：请在配置中设置 wechatpay.transfer.scene-report-infos-json（需与 transfer_scene_id="
                            + transferSceneId + " 的报备信息一致）");
                } else {
                    throw se;
                }
            }
            TransferBillsResponse response = httpResponse.getServiceResponse();
            log.info("微信商家转账已受理(升级版): tenantId={}, outBillNo={}, transferBillNo={}, state={}",
                tenantId, outBillNo,
                response != null ? response.getTransferBillNo() : null,
                response != null ? response.getState() : null);
            return response;
        } catch (PaymentException e) {
            // 业务/参数错误保持原样抛出，避免被包装成 systemError
            throw e;
        } catch (Exception e) {
            log.error("微信商家转账失败(升级版): tenantId={}, outBillNo={}, openid={}, amountFen={}, err={}",
                tenantId, outBillNo, openid, amountFen, e.getMessage(), e);
            throw PaymentException.systemError("微信商家转账失败: " + e.getMessage(), e);
        }
    }

    private static String limitLen(String s, int maxLen) {
        if (s == null) {
            return null;
        }
        if (s.length() <= maxLen) {
            return s;
        }
        return s.substring(0, maxLen);
    }

    private static HttpRequest buildTransferBillsHttpRequest(String url, HttpHeaders headers, TransferBillsRequest request) {
        return new HttpRequest.Builder()
            .httpMethod(HttpMethod.POST)
            .url(url)
            .headers(headers)
            .body(new JsonRequestBody.Builder().body(GsonUtil.toJson(request)).build())
            .build();
    }

    private List<TransferSceneReportInfo> parseTransferSceneReportInfos() throws PaymentException {
        if (StrUtil.isBlank(transferSceneReportInfosJson)) {
            return null;
        }
        try {
            Type t = new TypeToken<List<TransferSceneReportInfo>>() {}.getType();
            return GsonUtil.getGson().fromJson(transferSceneReportInfosJson, t);
        } catch (Exception e) {
            throw PaymentException.paramError("转账场景报备信息配置解析失败：wechatpay.transfer.scene-report-infos-json");
        }
    }

    /**
     * 商家转账（升级版）请求体：POST /v3/fund-app/mch-transfer/transfer-bills
     * 字段命名使用 GsonUtil 的 lower_case_with_underscores 策略自动转换。
     */
    @lombok.Data
    private static class TransferBillsRequest {
        private String appid;
        private String outBillNo;
        private String transferSceneId;
        private String openid;
        private Long transferAmount;
        private String transferRemark;
        private String userName;
        private String notifyUrl;
        private List<TransferSceneReportInfo> transferSceneReportInfos;
    }

    @lombok.Data
    private static class TransferSceneReportInfo {
        @SerializedName(value = "info_type", alternate = {"infoType"})
        private String infoType;
        @SerializedName(value = "info_content", alternate = {"infoContent"})
        private String infoContent;
    }

    /**
     * 商家转账（升级版）响应体（按需字段）
     */
    @lombok.Data
    public static class TransferBillsResponse {
        private String outBillNo;
        private String transferBillNo;
        private String createTime;
        private String state;
        @SerializedName(value = "package_info", alternate = {"packageInfo"})
        private String packageInfo;
    }
}
