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

import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import com.jeesharp.business.modules.pay.config.weixin.PayWechatWxPayClientHolder;
import com.jeesharp.business.modules.pay.paychannelconfig.support.PayChannelTenantSupport;
import com.jeesharp.business.modules.pay.payfundbalance.enums.PayFundAccountTypeEnum;
import com.jeesharp.business.modules.pay.payfundwithdraw.convert.PayFundWithdrawConvert;
import com.jeesharp.business.modules.pay.payfundwithdraw.dto.PayFundWithdrawSubmitDTO;
import com.jeesharp.business.modules.pay.payfundwithdraw.entity.PayFundWithdraw;
import com.jeesharp.business.modules.pay.payfundwithdraw.enums.PayFundWithdrawScopeEnum;
import com.jeesharp.business.modules.pay.payfundwithdraw.enums.PayFundWithdrawStatusEnum;
import com.jeesharp.business.modules.pay.payfundwithdraw.service.PayFundWithdrawService;
import com.jeesharp.business.modules.pay.payfundwithdraw.support.PayWechatFundWithdrawClient;
import com.jeesharp.business.modules.pay.payfundwithdraw.vo.PayFundWithdrawInfoVO;
import com.jeesharp.business.modules.pay.payfundwithdraw.vo.PayFundWithdrawListVO;
import com.jeesharp.business.modules.pay.payfundwithdraw.vo.PayFundWithdrawQueryVO;
import com.jeesharp.business.modules.pay.payfundwithdraw.vo.PayFundWithdrawSubmitVO;
import com.jeesharp.business.modules.pay.paysubmerchantapplyment.application.support.PaySubMerchantApplymentOperateSupport;
import com.jeesharp.business.modules.pay.paysubmerchantapplyment.entity.PaySubMerchantApplyment;
import com.jeesharp.business.modules.pay.paysubmerchantapplyment.service.PaySubMerchantApplymentService;
import com.jeesharp.business.modules.pay.paywechatconfig.entity.PayWechatConfig;
import com.jeesharp.framework.exception.BizErrorException;
import com.jeesharp.framework.pojo.Page;
import com.jeesharp.framework.pojo.query.PageRespVO;
import com.jeesharp.framework.utils.WebFrameworkUtils;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

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

/**
 * 预约提现应用服务：提交微信提现、查单同步、本地记录分页。
 *
 * @author jeesharp
 * @since 2026-06-12
 */
@Service
@AllArgsConstructor
@Transactional(readOnly = true)
public class PayFundWithdrawApplicationService {

    private final PayFundWithdrawService payFundWithdrawService;
    private final PaySubMerchantApplymentService paySubMerchantApplymentService;
    private final PayWechatFundWithdrawClient payWechatFundWithdrawClient;
    private final PayWechatWxPayClientHolder payWechatWxPayClientHolder;

    @Transactional(readOnly = false)
    public PayFundWithdrawSubmitVO submitPlatform(PayFundWithdrawSubmitDTO dto) throws Exception {
        validateSubmitDto(dto);
        String configTenantId = PayChannelTenantSupport.PLATFORM_TENANT_ID;
        PayWechatConfig config = payWechatWxPayClientHolder.requireActiveConfig(configTenantId);
        String outRequestNo = allocateOutRequestNo(config.getMchId());
        String requestJson = buildPlatformRequestJson(outRequestNo, dto);
        JSONObject wxResp = payWechatFundWithdrawClient.submitPlatformWithdraw(requestJson, configTenantId);
        PayFundWithdraw record = buildBaseRecord(dto, PayFundWithdrawScopeEnum.PLATFORM.getValue());
        record.setOutRequestNo(outRequestNo);
        record.setSpMchId(config.getMchId());
        record.setApplymentTenantId(PayChannelTenantSupport.toStorageKey(null));
        record.setSubmitJson(requestJson);
        record.setQueryJson(wxResp.toString());
        applySubmitResponse(record, wxResp);
        payFundWithdrawService.createFundWithdraw(record);
        return PayFundWithdrawConvert.INSTANCE.toSubmitVO(record);
    }

    @Transactional(readOnly = false)
    public PayFundWithdrawSubmitVO submitMerchant(PayFundWithdrawSubmitDTO dto) throws Exception {
        validateSubmitDto(dto);
        PaySubMerchantApplyment applyment = requireLoginTenantApplyment();
        PaySubMerchantApplymentOperateSupport.assertNotMerchantCancelled(applyment, "该特约商户已注销，不可提现");
        String configTenantKey = PaySubMerchantApplymentOperateSupport.resolveWechatConfigTenantKey(applyment.getApplymentTenantId());
        PayWechatConfig config = payWechatWxPayClientHolder.requireActiveConfig(configTenantKey);
        String outRequestNo = allocateOutRequestNo(config.getMchId());
        String requestJson = buildMerchantRequestJson(applyment.getSubMchId(), outRequestNo, dto);
        JSONObject wxResp = payWechatFundWithdrawClient.submitSubMerchantWithdraw(requestJson, configTenantKey);
        PayFundWithdraw record = buildBaseRecord(dto, PayFundWithdrawScopeEnum.MERCHANT.getValue());
        record.setApplymentId(applyment.getId());
        record.setApplymentTenantId(applyment.getApplymentTenantId());
        record.setSubMchId(applyment.getSubMchId());
        record.setSpMchId(config.getMchId());
        record.setOutRequestNo(outRequestNo);
        record.setSubmitJson(requestJson);
        record.setQueryJson(wxResp.toString());
        applySubmitResponse(record, wxResp);
        payFundWithdrawService.createFundWithdraw(record);
        return PayFundWithdrawConvert.INSTANCE.toSubmitVO(record);
    }

    @Transactional(readOnly = false)
    public PayFundWithdrawInfoVO syncStatus(String id) throws Exception {
        PayFundWithdraw record = requireRecord(id);
        assertScopeAccess(record);
        String configTenantKey = resolveConfigTenantKey(record);
        JSONObject wxResp;
        if (PayFundWithdrawScopeEnum.PLATFORM.getValue().equals(record.getScopeType())) {
            wxResp = payWechatFundWithdrawClient.queryPlatformWithdrawByOutNo(record.getOutRequestNo(), configTenantKey);
        } else {
            if (StrUtil.isBlank(record.getSubMchId())) {
                throw new BizErrorException("缺少特约商户号，无法同步");
            }
            wxResp = payWechatFundWithdrawClient.querySubMerchantWithdrawByOutNo(
                    record.getSubMchId(), record.getOutRequestNo(), configTenantKey);
        }
        applyQueryResponse(record, wxResp);
        record.setLastSyncDate(LocalDateTime.now());
        payFundWithdrawService.updateFundWithdraw(record);
        return PayFundWithdrawConvert.INSTANCE.toInfoVO(record);
    }

    public PayFundWithdrawInfoVO get(String id) {
        PayFundWithdraw record = requireRecord(id);
        assertScopeAccess(record);
        return PayFundWithdrawConvert.INSTANCE.toInfoVO(record);
    }

    public PageRespVO<PayFundWithdrawListVO> findPlatformPage(Page<PayFundWithdraw> page, PayFundWithdrawQueryVO query) {
        if (query == null) {
            query = new PayFundWithdrawQueryVO();
        }
        query.setScopeType(PayFundWithdrawScopeEnum.PLATFORM.getValue());
        query.setApplymentTenantId(null);
        return toPageVo(payFundWithdrawService.findScopePage(page, query));
    }

    public PageRespVO<PayFundWithdrawListVO> findMerchantPage(Page<PayFundWithdraw> page, PayFundWithdrawQueryVO query) {
        if (query == null) {
            query = new PayFundWithdrawQueryVO();
        }
        query.setScopeType(PayFundWithdrawScopeEnum.MERCHANT.getValue());
        query.setApplymentTenantId(WebFrameworkUtils.getLoginUserTenantId());
        return toPageVo(payFundWithdrawService.findScopePage(page, query));
    }

    private PageRespVO<PayFundWithdrawListVO> toPageVo(Page<PayFundWithdraw> result) {
        List<PayFundWithdrawListVO> rows = PayFundWithdrawConvert.INSTANCE.toListVOList(result.getRecords());
        PageRespVO<PayFundWithdrawListVO> vo = new PageRespVO<>();
        vo.setRecords(rows);
        vo.setTotal(result.getTotal());
        vo.setPageNo(result.getPageNo());
        vo.setPageSize(result.getPageSize());
        vo.setPages(result.getPages());
        return vo;
    }

    private PaySubMerchantApplyment requireLoginTenantApplyment() {
        String loginTenantId = WebFrameworkUtils.getLoginUserTenantId();
        PaySubMerchantApplyment applyment = paySubMerchantApplymentService.findByApplymentTenantId(loginTenantId);
        if (applyment == null) {
            throw new BizErrorException("当前租户尚未提交特约商户进件，无法提现");
        }
        PaySubMerchantApplymentOperateSupport.assertNotMerchantCancelled(applyment, "该特约商户已注销，不可提现");
        if (!PaySubMerchantApplymentOperateSupport.isEffectiveSubMerchant(applyment)) {
            if (StrUtil.isBlank(applyment.getSubMchId())) {
                throw new BizErrorException("进件尚未分配特约商户号，请等待微信审核通过后再提现");
            }
            throw new BizErrorException("进件尚未完成（须 FINISH），暂不可提现");
        }
        return applyment;
    }

    private PayFundWithdraw requireRecord(String id) {
        if (StrUtil.isBlank(id)) {
            throw new BizErrorException("提现记录 ID 不能为空");
        }
        PayFundWithdraw record = payFundWithdrawService.get(id.trim());
        if (record == null) {
            throw new BizErrorException("提现记录不存在");
        }
        return record;
    }

    private void assertScopeAccess(PayFundWithdraw record) {
        if (PayFundWithdrawScopeEnum.MERCHANT.getValue().equals(record.getScopeType())) {
            String loginTenantId = WebFrameworkUtils.getLoginUserTenantId();
            String storageLogin = PayChannelTenantSupport.toStorageKey(loginTenantId);
            if (!storageLogin.equals(record.getApplymentTenantId())) {
                throw new BizErrorException("无权查看或操作该提现记录");
            }
        }
    }

    private static String resolveConfigTenantKey(PayFundWithdraw record) {
        if (PayFundWithdrawScopeEnum.PLATFORM.getValue().equals(record.getScopeType())) {
            return PayChannelTenantSupport.PLATFORM_TENANT_ID;
        }
        return PaySubMerchantApplymentOperateSupport.resolveWechatConfigTenantKey(record.getApplymentTenantId());
    }

    private static void validateSubmitDto(PayFundWithdrawSubmitDTO dto) {
        if (PayFundAccountTypeEnum.fromValue(dto.getAccountType()) == null) {
            throw new BizErrorException("出款账户类型无效");
        }
        if (dto.getAmount() == null || dto.getAmount() <= 0) {
            throw new BizErrorException("提现金额必须大于 0");
        }
    }

    private static PayFundWithdraw buildBaseRecord(PayFundWithdrawSubmitDTO dto, String scopeType) {
        PayFundWithdraw record = new PayFundWithdraw();
        record.setScopeType(scopeType);
        record.setAccountType(dto.getAccountType().trim());
        record.setAmount(dto.getAmount());
        record.setRemark(StrUtil.trimToNull(dto.getRemark()));
        record.setBankMemo(StrUtil.trimToNull(dto.getBankMemo()));
        record.setWithdrawStatus(PayFundWithdrawStatusEnum.CREATE_SUCCESS.getValue());
        record.setWithdrawStatusDesc(PayFundWithdrawStatusEnum.CREATE_SUCCESS.getDesc());
        record.setSubmitDate(LocalDateTime.now());
        return record;
    }

    private static String buildPlatformRequestJson(String outRequestNo, PayFundWithdrawSubmitDTO dto) {
        JSONObject body = new JSONObject();
        body.set("out_request_no", outRequestNo);
        body.set("amount", dto.getAmount());
        body.set("account_type", dto.getAccountType().trim());
        if (StrUtil.isNotBlank(dto.getRemark())) {
            body.set("remark", dto.getRemark().trim());
        }
        if (StrUtil.isNotBlank(dto.getBankMemo())) {
            body.set("bank_memo", dto.getBankMemo().trim());
        }
        return body.toString();
    }

    private static String buildMerchantRequestJson(String subMchId, String outRequestNo, PayFundWithdrawSubmitDTO dto) {
        JSONObject body = new JSONObject();
        body.set("sub_mchid", subMchId);
        body.set("out_request_no", outRequestNo);
        body.set("amount", dto.getAmount());
        body.set("account_type", dto.getAccountType().trim());
        if (StrUtil.isNotBlank(dto.getRemark())) {
            body.set("remark", dto.getRemark().trim());
        }
        if (StrUtil.isNotBlank(dto.getBankMemo())) {
            body.set("bank_memo", dto.getBankMemo().trim());
        }
        return body.toString();
    }

    private static void applySubmitResponse(PayFundWithdraw record, JSONObject wxResp) {
        if (wxResp == null) {
            return;
        }
        String withdrawId = wxResp.getStr("withdraw_id");
        if (StrUtil.isNotBlank(withdrawId)) {
            record.setWithdrawId(withdrawId);
        }
        String outNo = wxResp.getStr("out_request_no");
        if (StrUtil.isNotBlank(outNo)) {
            record.setOutRequestNo(outNo);
        }
    }

    private static void applyQueryResponse(PayFundWithdraw record, JSONObject wxResp) {
        if (wxResp == null) {
            return;
        }
        record.setQueryJson(wxResp.toString());
        String withdrawId = wxResp.getStr("withdraw_id");
        if (StrUtil.isNotBlank(withdrawId)) {
            record.setWithdrawId(withdrawId);
        }
        String status = wxResp.getStr("status");
        if (StrUtil.isNotBlank(status)) {
            record.setWithdrawStatus(status.trim());
            record.setWithdrawStatusDesc(PayFundWithdrawStatusEnum.getDescByValue(status));
        }
        String reason = wxResp.getStr("reason");
        if (StrUtil.isNotBlank(reason)) {
            record.setReason(reason);
            if (PayFundWithdrawStatusEnum.FAIL.getValue().equals(record.getWithdrawStatus())
                    || PayFundWithdrawStatusEnum.REFUND.getValue().equals(record.getWithdrawStatus())) {
                record.setWithdrawStatusDesc(reason);
            }
        }
        record.setWxCreateTime(wxResp.getStr("create_time"));
        record.setWxUpdateTime(wxResp.getStr("update_time"));
    }

    private String allocateOutRequestNo(String mchId) {
        String prefix = StrUtil.blankToDefault(mchId, "W").trim().replaceAll("[^A-Za-z0-9]", "");
        if (StrUtil.isBlank(prefix)) {
            prefix = "W";
        }
        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)) : "W") + suffix;
            }
            if (payFundWithdrawService.findByOutRequestNo(code) == null) {
                return code;
            }
        }
        throw new IllegalStateException("生成提现单号失败，请重试");
    }
}
