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

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.jeesharp.business.modules.pay.paysubmerchantapplyment.application.support.PaySubMerchantApplymentOperateSupport;
import com.jeesharp.business.modules.pay.paysubmerchantapplyment.convert.PaySubMerchantSettlementModifyConvert;
import com.jeesharp.business.modules.pay.paysubmerchantapplyment.dto.PaySubMerchantSettlementModifyDTO;
import com.jeesharp.business.modules.pay.paysubmerchantapplyment.entity.PaySubMerchantApplyment;
import com.jeesharp.business.modules.pay.paysubmerchantapplyment.entity.PaySubMerchantSettlementModify;
import com.jeesharp.business.modules.pay.paysubmerchantapplyment.enums.PaySubMerchantSettlementAccountTypeEnum;
import com.jeesharp.business.modules.pay.paysubmerchantapplyment.enums.PaySubMerchantSettlementModifyAuditEnum;
import com.jeesharp.business.modules.pay.paysubmerchantapplyment.service.PaySubMerchantApplymentService;
import com.jeesharp.business.modules.pay.paysubmerchantapplyment.service.PaySubMerchantSettlementModifyService;
import com.jeesharp.business.modules.pay.paysubmerchantapplyment.support.PayWechatSubMerchantApplymentClient;
import com.jeesharp.business.modules.pay.paysubmerchantapplyment.vo.PaySubMerchantSettlementInfoVO;
import com.jeesharp.business.modules.pay.paysubmerchantapplyment.vo.PaySubMerchantSettlementModifyListVO;
import com.jeesharp.business.modules.pay.paysubmerchantapplyment.vo.PaySubMerchantSettlementModifyStatusVO;
import com.jeesharp.business.modules.pay.paysubmerchantapplyment.vo.PaySubMerchantSettlementModifySubmitVO;
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;

/**
 * 修改结算账户应用服务：与进件主流程独立，仅编排微信 modify-settlement 相关接口。
 *
 * @author jeesharp
 * @since 2026-06-12
 */
@Service
@AllArgsConstructor
@Transactional(readOnly = true)
public class PaySubMerchantSettlementModifyApplicationService {

    private static final int DAILY_SUBMIT_LIMIT = 5;

    private final PaySubMerchantApplymentService paySubMerchantApplymentService;
    private final PaySubMerchantSettlementModifyService paySubMerchantSettlementModifyService;
    private final PayWechatSubMerchantApplymentClient payWechatSubMerchantApplymentClient;

    /**
     * 查询微信当前结算账户。
     */
    public PaySubMerchantSettlementInfoVO getCurrentSettlement(String applymentId) throws Exception {
        PaySubMerchantApplyment applyment = PaySubMerchantApplymentOperateSupport.requireFinishedApplymentWithSubMch(
                paySubMerchantApplymentService,
                applymentId,
                "该进件尚未获得特约商户号，无法修改结算账户",
                "进件尚未审核完成，无法修改结算账户",
                "该特约商户已注销，无法修改结算账户");
        String tenantKey = PaySubMerchantApplymentOperateSupport.resolveWechatConfigTenantKey(applyment.getApplymentTenantId());
        JSONObject wx = payWechatSubMerchantApplymentClient.getSettlement(applyment.getSubMchId(), tenantKey);
        String wxJson = wx != null ? wx.toString() : "";
        return PaySubMerchantSettlementModifyConvert.INSTANCE.toSettlementInfoVO(wx, wxJson);
    }

    /**
     * 提交修改结算账户到微信。
     */
    @Transactional(readOnly = false)
    public PaySubMerchantSettlementModifySubmitVO submitModify(PaySubMerchantSettlementModifyDTO dto) throws Exception {
        validateModifyDto(dto);
        PaySubMerchantApplyment applyment = PaySubMerchantApplymentOperateSupport.requireFinishedApplymentWithSubMch(
                paySubMerchantApplymentService,
                dto.getApplymentId(),
                "该进件尚未获得特约商户号，无法修改结算账户",
                "进件尚未审核完成，无法修改结算账户",
                "该特约商户已注销，无法修改结算账户");
        assertNoAuditingModify(applyment.getId());
        assertDailySubmitLimit(applyment.getId());

        String requestJson = buildModifyRequestJson(dto);
        String tenantKey = PaySubMerchantApplymentOperateSupport.resolveWechatConfigTenantKey(applyment.getApplymentTenantId());
        JSONObject wxResp = payWechatSubMerchantApplymentClient.modifySettlement(
                applyment.getSubMchId(), requestJson, tenantKey);

        PaySubMerchantSettlementModify record = new PaySubMerchantSettlementModify();
        record.setApplymentId(applyment.getId());
        record.setSubMchId(applyment.getSubMchId());
        record.setApplymentTenantId(applyment.getApplymentTenantId());
        record.setApplicationNo(wxResp.getStr("application_no"));
        record.setAccountType(dto.getAccountType().trim());
        record.setAccountBank(dto.getAccountBank().trim());
        record.setBankName(StrUtil.trimToNull(dto.getBankName()));
        record.setBankBranchId(StrUtil.trimToNull(dto.getBankBranchId()));
        record.setBankAddressCode(StrUtil.trimToNull(dto.getBankAddressCode()));
        record.setAuditStatus(PaySubMerchantSettlementModifyAuditEnum.AUDITING.getValue());
        record.setAuditStatusDesc(PaySubMerchantSettlementModifyAuditEnum.AUDITING.getDesc());
        record.setSubmitJson(requestJson);
        record.setSubmitDate(LocalDateTime.now());
        paySubMerchantSettlementModifyService.createSettlementModify(record);
        return PaySubMerchantSettlementModifyConvert.INSTANCE.toSubmitVO(record);
    }

    /**
     * 同步修改结算账户审核状态。
     */
    @Transactional(readOnly = false)
    public PaySubMerchantSettlementModifyStatusVO syncModifyStatus(String modifyId) throws Exception {
        PaySubMerchantSettlementModify record = requireModifyRecord(modifyId);
        if (StrUtil.isBlank(record.getApplicationNo())) {
            throw new BizErrorException("缺少微信修改申请单号，无法同步");
        }
        PaySubMerchantApplyment applyment = paySubMerchantApplymentService.get(record.getApplymentId());
        if (applyment == null) {
            throw new BizErrorException("关联进件记录不存在");
        }
        String tenantKey = PaySubMerchantApplymentOperateSupport.resolveWechatConfigTenantKey(applyment.getApplymentTenantId());
        JSONObject wxResp = payWechatSubMerchantApplymentClient.queryModifySettlementApplication(
                record.getSubMchId(), record.getApplicationNo(), tenantKey);
        applyWxAuditToRecord(record, wxResp);
        record.setLastSyncDate(LocalDateTime.now());
        paySubMerchantSettlementModifyService.updateSettlementModify(record);

        PaySubMerchantSettlementModifyStatusVO vo = PaySubMerchantSettlementModifyConvert.INSTANCE.toStatusVO(record);
        vo.setWxResponseJson(wxResp.toString());
        return vo;
    }

    /**
     * 查询某进件下的修改结算账户历史记录。
     */
    public List<PaySubMerchantSettlementModifyListVO> listModifyRecords(String applymentId) {
        if (StrUtil.isBlank(applymentId)) {
            return List.of();
        }
        List<PaySubMerchantSettlementModify> list = paySubMerchantSettlementModifyService.listByApplymentId(applymentId.trim());
        return PaySubMerchantSettlementModifyConvert.INSTANCE.toListVOList(list);
    }

    private PaySubMerchantSettlementModify requireModifyRecord(String modifyId) {
        if (StrUtil.isBlank(modifyId)) {
            throw new BizErrorException("修改记录 ID 不能为空");
        }
        PaySubMerchantSettlementModify record = paySubMerchantSettlementModifyService.get(modifyId.trim());
        if (record == null) {
            throw new BizErrorException("修改结算账户记录不存在");
        }
        return record;
    }

    private void assertNoAuditingModify(String applymentId) {
        PaySubMerchantSettlementModify auditing = paySubMerchantSettlementModifyService.findAuditingByApplymentId(applymentId);
        if (auditing != null) {
            throw new BizErrorException("存在审核中的修改结算账户申请，请等待审核完成后再提交");
        }
    }

    private void assertDailySubmitLimit(String applymentId) {
        long count = paySubMerchantSettlementModifyService.countTodaySubmitByApplymentId(applymentId);
        if (count >= DAILY_SUBMIT_LIMIT) {
            throw new BizErrorException("每个进件每日最多提交 " + DAILY_SUBMIT_LIMIT + " 次修改结算账户申请");
        }
    }

    private static void validateModifyDto(PaySubMerchantSettlementModifyDTO dto) {
        if (PaySubMerchantSettlementAccountTypeEnum.fromValue(dto.getAccountType()) == null) {
            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 buildModifyRequestJson(PaySubMerchantSettlementModifyDTO dto) {
        JSONObject body = new JSONObject();
        body.set("account_type", dto.getAccountType().trim());
        body.set("account_bank", dto.getAccountBank().trim());
        body.set("account_number", dto.getAccountNumber().trim());
        body.set("account_name", dto.getAccountName().trim());
        if (StrUtil.isNotBlank(dto.getBankAddressCode())) {
            body.set("bank_address_code", dto.getBankAddressCode().trim());
        }
        if (StrUtil.isNotBlank(dto.getBankName())) {
            body.set("bank_name", dto.getBankName().trim());
        }
        if (StrUtil.isNotBlank(dto.getBankBranchId())) {
            body.set("bank_branch_id", dto.getBankBranchId().trim());
        }
        return body.toString();
    }

    private static void applyWxAuditToRecord(PaySubMerchantSettlementModify record, JSONObject wxResp) {
        String auditStatus = wxResp.getStr("audit_status");
        if (StrUtil.isNotBlank(auditStatus)) {
            record.setAuditStatus(auditStatus.trim());
            record.setAuditStatusDesc(PaySubMerchantSettlementModifyAuditEnum.getDescByValue(auditStatus));
        }
        JSONObject auditDetail = wxResp.getJSONObject("audit_detail");
        if (auditDetail != null) {
            String rejectReason = auditDetail.getStr("reject_reason");
            if (StrUtil.isBlank(rejectReason)) {
                rejectReason = auditDetail.getStr("param_name");
            }
            if (StrUtil.isNotBlank(rejectReason)) {
                record.setAuditStatusDesc(rejectReason);
            }
        }
        record.setQueryJson(wxResp.toString());
    }
}
