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

import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.jeesharp.business.modules.pay.config.weixin.PayWechatWxPayClientHolder;
import com.jeesharp.business.modules.pay.paywechatconfig.entity.PayWechatConfig;
import com.jeesharp.business.modules.pay.paysubmerchantapplyment.application.support.PaySubMerchantApplymentOperateSupport;
import com.jeesharp.business.modules.pay.paysubmerchantapplyment.convert.PaySubMerchantCancelConvert;
import com.jeesharp.business.modules.pay.paysubmerchantapplyment.dto.PaySubMerchantCancelConfirmDTO;
import com.jeesharp.business.modules.pay.paysubmerchantapplyment.dto.PaySubMerchantCancelSubmitDTO;
import com.jeesharp.business.modules.pay.paysubmerchantapplyment.entity.PaySubMerchantApplyment;
import com.jeesharp.business.modules.pay.paysubmerchantapplyment.entity.PaySubMerchantCancel;
import com.jeesharp.business.modules.pay.paysubmerchantapplyment.enums.PaySubMerchantCancelStateEnum;
import com.jeesharp.business.modules.pay.paysubmerchantapplyment.enums.PaySubMerchantCancelValidateResultEnum;
import com.jeesharp.business.modules.pay.paysubmerchantapplyment.enums.PaySubMerchantCancelWithdrawEnum;
import com.jeesharp.business.modules.pay.paysubmerchantapplyment.enums.PaySubMerchantSettlementAccountTypeEnum;
import com.jeesharp.business.modules.pay.paysubmerchantapplyment.service.PaySubMerchantApplymentService;
import com.jeesharp.business.modules.pay.paysubmerchantapplyment.service.PaySubMerchantCancelService;
import com.jeesharp.business.modules.pay.paysubmerchantapplyment.support.PayWechatSubMerchantCancelClient;
import com.jeesharp.business.modules.pay.paysubmerchantapplyment.vo.PaySubMerchantCancelListVO;
import com.jeesharp.business.modules.pay.paysubmerchantapplyment.vo.PaySubMerchantCancelStatusVO;
import com.jeesharp.business.modules.pay.paysubmerchantapplyment.vo.PaySubMerchantCancelSubmitVO;
import com.jeesharp.business.modules.pay.paysubmerchantapplyment.vo.PaySubMerchantCancelValidateVO;
import com.jeesharp.framework.exception.BizErrorException;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.List;

/**
 * 商户注销应用服务：编排微信 apply-cancel-withdraw 资格校验、提交、查单与代确认。
 *
 * @author jeesharp
 * @since 2026-06-15
 */
@Service
@AllArgsConstructor
@Transactional(readOnly = true)
public class PaySubMerchantCancelApplicationService {

    private final PaySubMerchantApplymentService paySubMerchantApplymentService;
    private final PaySubMerchantCancelService paySubMerchantCancelService;
    private final PayWechatSubMerchantCancelClient payWechatSubMerchantCancelClient;
    private final PayWechatWxPayClientHolder payWechatWxPayClientHolder;

    /**
     * 注销资格校验。
     */
    public PaySubMerchantCancelValidateVO validateCancel(String applymentId) throws Exception {
        PaySubMerchantApplyment applyment = PaySubMerchantApplymentOperateSupport.requireOperableApplymentWithSubMch(
                paySubMerchantApplymentService,
                applymentId,
                "该进件尚未获得特约商户号，无法发起商户注销",
                "该特约商户已注销，不可重复发起");
        String tenantKey = PaySubMerchantApplymentOperateSupport.resolveWechatConfigTenantKey(applyment.getApplymentTenantId());
        JSONObject wx = payWechatSubMerchantCancelClient.validateCancel(applyment.getSubMchId(), tenantKey);
        String wxJson = wx != null ? wx.toString() : "";
        return PaySubMerchantCancelConvert.INSTANCE.toValidateVO(wx, wxJson);
    }

    /**
     * 提交商户注销/注销提现申请。
     */
    @Transactional(readOnly = false)
    public PaySubMerchantCancelSubmitVO submitCancel(PaySubMerchantCancelSubmitDTO dto) throws Exception {
        validateSubmitDto(dto);
        PaySubMerchantApplyment applyment = PaySubMerchantApplymentOperateSupport.requireApplymentWithSubMch(
                paySubMerchantApplymentService,
                dto.getApplymentId(),
                "该进件尚未获得特约商户号，无法发起商户注销");
        assertNoInProgressCancel(applyment.getId());

        PaySubMerchantCancelValidateVO validate = validateCancel(applyment.getId());
        if (!PaySubMerchantCancelValidateResultEnum.ALLOW_CANCEL_WITHDRAW.getValue().equals(validate.getValidateResult())) {
            throw new BizErrorException("当前商户不满足注销条件，请先处理阻塞原因");
        }

        String outRequestNo = allocateOutRequestNo(applyment.getApplymentTenantId());
        String requestJson = buildSubmitRequestJson(applyment.getSubMchId(), outRequestNo, dto);
        String tenantKey = PaySubMerchantApplymentOperateSupport.resolveWechatConfigTenantKey(applyment.getApplymentTenantId());
        JSONObject wxResp = payWechatSubMerchantCancelClient.submitCancelWithdraw(requestJson, tenantKey);

        PaySubMerchantCancel record = new PaySubMerchantCancel();
        record.setApplymentId(applyment.getId());
        record.setSubMchId(applyment.getSubMchId());
        record.setApplymentTenantId(applyment.getApplymentTenantId());
        record.setOutRequestNo(outRequestNo);
        record.setWxApplymentId(wxResp.getStr("applyment_id"));
        record.setWithdraw(dto.getWithdraw().trim());
        record.setCancelState(StrUtil.blankToDefault(wxResp.getStr("cancel_state"), PaySubMerchantCancelStateEnum.ACCEPTED.getValue()));
        record.setCancelStateDesc(PaySubMerchantCancelStateEnum.getDescByValue(record.getCancelState()));
        record.setValidateJson(JSONUtil.toJsonStr(validate));
        record.setSubmitJson(requestJson);
        record.setQueryJson(wxResp.toString());
        record.setSubmitDate(LocalDateTime.now());
        applyWxCancelToRecord(record, wxResp);
        paySubMerchantCancelService.createCancel(record);
        syncApplymentIfCancelFinished(record);
        return PaySubMerchantCancelConvert.INSTANCE.toSubmitVO(record);
    }

    /**
     * 同步注销申请状态。
     */
    @Transactional(readOnly = false)
    public PaySubMerchantCancelStatusVO syncCancelStatus(String cancelId) throws Exception {
        PaySubMerchantCancel record = requireCancelRecord(cancelId);
        PaySubMerchantApplyment applyment = paySubMerchantApplymentService.get(record.getApplymentId());
        if (applyment == null) {
            throw new BizErrorException("关联进件记录不存在");
        }
        String tenantKey = PaySubMerchantApplymentOperateSupport.resolveWechatConfigTenantKey(applyment.getApplymentTenantId());
        JSONObject wxResp;
        if (StrUtil.isNotBlank(record.getWxApplymentId())) {
            wxResp = payWechatSubMerchantCancelClient.queryByWxApplymentId(record.getWxApplymentId(), tenantKey);
        } else if (StrUtil.isNotBlank(record.getOutRequestNo())) {
            wxResp = payWechatSubMerchantCancelClient.queryByOutRequestNo(record.getOutRequestNo(), tenantKey);
        } else {
            throw new BizErrorException("缺少注销申请单号，无法同步");
        }
        applyWxCancelToRecord(record, wxResp);
        record.setLastSyncDate(LocalDateTime.now());
        record.setQueryJson(wxResp.toString());
        paySubMerchantCancelService.updateCancel(record);
        syncApplymentIfCancelFinished(record);

        PaySubMerchantCancelStatusVO vo = PaySubMerchantCancelConvert.INSTANCE.toStatusVO(record);
        vo.setWxResponseJson(wxResp.toString());
        return vo;
    }

    /**
     * 平台代商户确认注销。
     */
    @Transactional(readOnly = false)
    public PaySubMerchantCancelStatusVO confirmCancel(PaySubMerchantCancelConfirmDTO dto) throws Exception {
        PaySubMerchantCancel record = requireCancelRecord(dto.getCancelId());
        if (!PaySubMerchantCancelStateEnum.WAITING_MERCHANT_CONFIRM.getValue().equals(record.getCancelState())) {
            throw new BizErrorException("当前状态不是待商户确认，无法代确认");
        }
        PaySubMerchantApplyment applyment = paySubMerchantApplymentService.get(record.getApplymentId());
        if (applyment == null) {
            throw new BizErrorException("关联进件记录不存在");
        }

        JSONObject body = new JSONObject();
        body.set("sub_mchid", record.getSubMchId());
        if (StrUtil.isNotBlank(record.getWxApplymentId())) {
            body.set("applyment_id", record.getWxApplymentId());
        }
        body.set("out_request_no", record.getOutRequestNo());
        body.set("cancel_contract_version", dto.getCancelContractVersion().trim());

        String tenantKey = PaySubMerchantApplymentOperateSupport.resolveWechatConfigTenantKey(applyment.getApplymentTenantId());
        JSONObject wxResp = payWechatSubMerchantCancelClient.confirmCancel(body.toString(), tenantKey);

        record.setCancelContractVersion(dto.getCancelContractVersion().trim());
        applyWxCancelToRecord(record, wxResp);
        record.setLastSyncDate(LocalDateTime.now());
        record.setQueryJson(wxResp.toString());
        paySubMerchantCancelService.updateCancel(record);
        syncApplymentIfCancelFinished(record);

        PaySubMerchantCancelStatusVO vo = PaySubMerchantCancelConvert.INSTANCE.toStatusVO(record);
        vo.setWxResponseJson(wxResp.toString());
        return vo;
    }

    /**
     * 查询某进件下的注销历史记录。
     */
    public List<PaySubMerchantCancelListVO> listCancelRecords(String applymentId) {
        if (StrUtil.isBlank(applymentId)) {
            return List.of();
        }
        List<PaySubMerchantCancel> list = paySubMerchantCancelService.listByApplymentId(applymentId.trim());
        return PaySubMerchantCancelConvert.INSTANCE.toListVOList(list);
    }

    private void syncApplymentIfCancelFinished(PaySubMerchantCancel record) {
        if (record == null || !PaySubMerchantCancelStateEnum.FINISH.getValue().equals(record.getCancelState())) {
            return;
        }
        String desc = StrUtil.blankToDefault(record.getCancelStateDesc(), PaySubMerchantCancelStateEnum.FINISH.getDesc());
        paySubMerchantApplymentService.markSubMerchantCancelled(record.getApplymentId(), desc);
    }

    private PaySubMerchantCancel requireCancelRecord(String cancelId) {
        if (StrUtil.isBlank(cancelId)) {
            throw new BizErrorException("注销记录 ID 不能为空");
        }
        PaySubMerchantCancel record = paySubMerchantCancelService.get(cancelId.trim());
        if (record == null) {
            throw new BizErrorException("商户注销记录不存在");
        }
        return record;
    }

    private void assertNoInProgressCancel(String applymentId) {
        PaySubMerchantCancel inProgress = paySubMerchantCancelService.findInProgressByApplymentId(applymentId);
        if (inProgress != null) {
            throw new BizErrorException("存在进行中的商户注销申请，请等待完成后再提交");
        }
    }

    private static void validateSubmitDto(PaySubMerchantCancelSubmitDTO dto) {
        if (PaySubMerchantCancelWithdrawEnum.fromValue(dto.getWithdraw()) == null) {
            throw new BizErrorException("是否提取资金参数无效");
        }
        if (PaySubMerchantCancelWithdrawEnum.APPLY_WITHDRAW.getValue().equals(dto.getWithdraw())) {
            if (PaySubMerchantSettlementAccountTypeEnum.fromValue(dto.getAccountType()) == null) {
                throw new BizErrorException("注销并提现时账户类型无效");
            }
            if (StrUtil.isBlank(dto.getAccountBank())) {
                throw new BizErrorException("注销并提现时开户银行不能为空");
            }
            if (StrUtil.isBlank(dto.getAccountNumber())) {
                throw new BizErrorException("注销并提现时银行账号不能为空");
            }
            if (StrUtil.isBlank(dto.getAccountName())) {
                throw new BizErrorException("注销并提现时开户名称不能为空");
            }
            if ("其他银行".equals(StrUtil.trim(dto.getAccountBank()))) {
                boolean hasBranch = StrUtil.isNotBlank(dto.getBankBranchId());
                boolean hasBankName = StrUtil.isNotBlank(dto.getBankName());
                if (!hasBranch && !hasBankName) {
                    throw new BizErrorException("选择「其他银行」时，联行号与开户银行全称须至少填写一项");
                }
            }
        }
    }

    private static String buildSubmitRequestJson(String subMchId, String outRequestNo, PaySubMerchantCancelSubmitDTO dto) {
        JSONObject body = new JSONObject();
        body.set("sub_mchid", subMchId);
        body.set("out_request_no", outRequestNo);
        body.set("withdraw", dto.getWithdraw().trim());
        if (PaySubMerchantCancelWithdrawEnum.APPLY_WITHDRAW.getValue().equals(dto.getWithdraw())) {
            JSONObject bank = new JSONObject();
            bank.set("account_type", dto.getAccountType().trim());
            bank.set("account_bank", dto.getAccountBank().trim());
            bank.set("account_number", dto.getAccountNumber().trim());
            bank.set("account_name", dto.getAccountName().trim());
            if (StrUtil.isNotBlank(dto.getBankAddressCode())) {
                bank.set("bank_address_code", dto.getBankAddressCode().trim());
            }
            if (StrUtil.isNotBlank(dto.getBankName())) {
                bank.set("bank_name", dto.getBankName().trim());
            }
            if (StrUtil.isNotBlank(dto.getBankBranchId())) {
                bank.set("bank_branch_id", dto.getBankBranchId().trim());
            }
            body.set("bank_account_info", bank);
        }
        return body.toString();
    }

    private static void applyWxCancelToRecord(PaySubMerchantCancel record, JSONObject wxResp) {
        if (wxResp == null) {
            return;
        }
        String cancelState = wxResp.getStr("cancel_state");
        if (StrUtil.isNotBlank(cancelState)) {
            record.setCancelState(cancelState.trim());
            record.setCancelStateDesc(PaySubMerchantCancelStateEnum.getDescByValue(cancelState));
        }
        String wxApplymentId = wxResp.getStr("applyment_id");
        if (StrUtil.isNotBlank(wxApplymentId)) {
            record.setWxApplymentId(wxApplymentId);
        }
        String withdrawState = wxResp.getStr("withdraw_state");
        if (StrUtil.isNotBlank(withdrawState)) {
            record.setWithdrawState(withdrawState);
        }
        JSONObject confirmCancel = wxResp.getJSONObject("confirm_cancel");
        if (confirmCancel != null) {
            String url = confirmCancel.getStr("confirm_cancel_url");
            if (StrUtil.isNotBlank(url)) {
                record.setConfirmCancelUrl(url);
            }
        }
    }

    /**
     * 生成唯一 out_request_no：仅大小写字母与数字，最长 32。
     */
    private String allocateOutRequestNo(String storageApplymentTenantId) {
        String tenantForConfig = PaySubMerchantApplymentOperateSupport.resolveWechatConfigTenantKey(storageApplymentTenantId);
        PayWechatConfig config = payWechatWxPayClientHolder.requireActiveConfig(tenantForConfig);
        String prefix = StrUtil.blankToDefault(config.getMchId(), "C").trim();
        prefix = prefix.replaceAll("[^A-Za-z0-9]", "");
        if (StrUtil.isBlank(prefix)) {
            prefix = "C";
        }
        for (int i = 0; i < 5; i++) {
            String suffix = IdUtil.getSnowflakeNextIdStr();
            String code = prefix + suffix;
            if (code.length() > 32) {
                int maxPrefix = 32 - suffix.length();
                code = (maxPrefix > 0 ? prefix.substring(0, Math.min(prefix.length(), maxPrefix)) : "C") + suffix;
            }
            if (paySubMerchantCancelService.findByOutRequestNo(code) == null) {
                return code;
            }
        }
        throw new IllegalStateException("生成商户注销申请单号失败，请重试");
    }
}
