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.paychannelconfig.support.PayChannelTenantSupport;
import com.jeesharp.business.modules.pay.paywechatconfig.entity.PayWechatConfig;
import com.jeesharp.business.modules.pay.paysubmerchantapplyment.application.support.PayEcommerceApplymentPayloadAssembler;
import com.jeesharp.business.modules.pay.paysubmerchantapplyment.application.support.PaySubMerchantApplymentOperateSupport;
import com.jeesharp.business.modules.pay.paysubmerchantapplyment.support.PaySubMerchantApplyStatusSupport;
import com.jeesharp.business.modules.pay.paysubmerchantapplyment.convert.PaySubMerchantApplymentConvert;
import com.jeesharp.business.modules.pay.paysubmerchantapplyment.dto.PaySubMerchantApplymentSaveDTO;
import com.jeesharp.business.modules.pay.paysubmerchantapplyment.entity.PaySubMerchantApplyment;
import com.jeesharp.business.modules.pay.paysubmerchantapplyment.enums.PaySubMerchantApplyLocalStatusEnum;
import com.jeesharp.business.modules.pay.paysubmerchantapplyment.enums.PaySubMerchantApplymentApiTypeEnum;
import com.jeesharp.business.modules.pay.paysubmerchantapplyment.service.PaySubMerchantApplymentService;
import com.jeesharp.business.modules.pay.profitsharing.application.support.PayProfitSharingReceiverBootstrapSupport;
import com.jeesharp.business.modules.pay.paysubmerchantapplyment.support.PayWechatEcommerceApplymentClient;
import com.jeesharp.business.modules.pay.paysubmerchantapplyment.support.PayWechatSubMerchantApplymentClient;
import com.jeesharp.business.modules.pay.paysubmerchantapplyment.vo.PaySubMerchantApplymentInfoVO;
import com.jeesharp.business.modules.pay.paysubmerchantapplyment.vo.PaySubMerchantApplymentListVO;
import com.jeesharp.business.modules.pay.paysubmerchantapplyment.vo.PaySubMerchantApplymentQueryVO;
import com.jeesharp.business.modules.pay.paysubmerchantapplyment.vo.PaySubMerchantApplymentSubmitResultVO;
import com.jeesharp.business.modules.pay.paysubmerchantapplyment.vo.PaySubMerchantBusinessCodeVO;
import com.jeesharp.business.modules.pay.paysubmerchantapplyment.vo.PaySubMerchantMediaUploadVO;
import com.jeesharp.framework.exception.BizErrorException;
import com.jeesharp.framework.pojo.Page;
import com.jeesharp.framework.pojo.query.PageRespVO;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.Objects;

/**
 * 特约商户进件应用服务：草稿 CRUD、提交微信、同步状态、资料上传。
 *
 * @author jeesharp
 * @since 2026-05-26
 */
@Slf4j
@Service
@AllArgsConstructor
@Transactional(readOnly = true)
public class PaySubMerchantApplymentApplicationService {

    private final PaySubMerchantApplymentService paySubMerchantApplymentService;
    private final PayWechatSubMerchantApplymentClient payWechatSubMerchantApplymentClient;
    private final PayWechatEcommerceApplymentClient payWechatEcommerceApplymentClient;
    private final PayWechatWxPayClientHolder payWechatWxPayClientHolder;
    private final PayProfitSharingReceiverBootstrapSupport payProfitSharingReceiverBootstrapSupport;

    public PaySubMerchantApplymentInfoVO get(String id) {
        return PaySubMerchantApplymentConvert.INSTANCE.toInfoVO(paySubMerchantApplymentService.get(id));
    }

    public PageRespVO<PaySubMerchantApplymentListVO> findPage(Page<PaySubMerchantApplyment> page, PaySubMerchantApplymentQueryVO query) {
        if (query != null && StrUtil.isNotBlank(query.getApplymentTenantId())) {
            query.setApplymentTenantId(PayChannelTenantSupport.toStorageKey(query.getApplymentTenantId()));
        }
        Page<PaySubMerchantApplyment> result = paySubMerchantApplymentService.findPage(page, query);
        return PaySubMerchantApplymentConvert.INSTANCE.toPageRespVO(result);
    }

    /**
     * 预生成业务申请编号（打开新增表单时调用，不落库）。
     */
    public PaySubMerchantBusinessCodeVO nextBusinessCode(String applymentTenantId) {
        String storageApplymentTenantId = requireApplymentTenantId(applymentTenantId);
        if (paySubMerchantApplymentService.findByApplymentTenantId(storageApplymentTenantId) != null) {
            throw new BizErrorException("该租户已存在进件申请，每个租户仅允许一条记录");
        }
        PaySubMerchantBusinessCodeVO vo = new PaySubMerchantBusinessCodeVO();
        vo.setBusinessCode(allocateBusinessCode(storageApplymentTenantId));
        return vo;
    }

    /**
     * 指定进件租户是否已有进件记录（每租户仅一条）。
     */
    public boolean existsByApplymentTenant(String applymentTenantId) {
        return paySubMerchantApplymentService.findByApplymentTenantId(requireApplymentTenantId(applymentTenantId)) != null;
    }

    @Transactional(readOnly = false)
    public void save(PaySubMerchantApplymentSaveDTO dto) {
        validateApplymentTenantFromRequest(dto);
        PaySubMerchantApplyment entity = buildEntityFromDto(dto);
        if (StrUtil.isBlank(entity.getId())) {
            createDraft(entity);
        } else {
            updateDraft(entity);
        }
    }

    /**
     * 新增草稿：核心约束为「每个 applyment_tenant_id 仅允许一条未删除进件」。
     */
    private void createDraft(PaySubMerchantApplyment entity) {
        if (StrUtil.isNotBlank(entity.getId())) {
            throw new BizErrorException("新增请求不应携带主键 id");
        }
        assertApplymentTenantUnique(entity.getApplymentTenantId(), null);
        entity.setApplyStatus(PaySubMerchantApplyLocalStatusEnum.DRAFT.getValue());
        entity.setApplymentApiType(PaySubMerchantApplymentApiTypeEnum.ECOMMERCE.getValue());
        if (StrUtil.isBlank(entity.getBusinessCode())) {
            entity.setBusinessCode(allocateBusinessCode(entity.getApplymentTenantId()));
        } else {
            ensureBusinessCodeUnique(entity.getBusinessCode(), entity.getApplymentTenantId(), null);
        }
        assertDraftJsonValid(entity.getDraftJson());
        extractSummaryFromDraft(entity);
        paySubMerchantApplymentService.createPaySubMerchantApplyment(entity);
    }

    /**
     * 编辑草稿：进件所属租户（业务字段）不可修改。
     */
    private void updateDraft(PaySubMerchantApplyment entity) {
        PaySubMerchantApplyment existing = requireExistingDraft(entity.getId());
        assertApplymentTenantUnchanged(existing, entity);
        assertApplymentTenantUnique(entity.getApplymentTenantId(), entity.getId());
        entity.setBusinessCode(existing.getBusinessCode());
        entity.setApplymentApiType(existing.getApplymentApiType());
        entity.setApplymentTenantId(existing.getApplymentTenantId());
        entity.setApplymentTenantCode(existing.getApplymentTenantCode());
        assertDraftJsonValid(entity.getDraftJson());
        extractSummaryFromDraft(entity);
        paySubMerchantApplymentService.updatePaySubMerchantApplyment(entity);
    }

    private PaySubMerchantApplyment requireExistingDraft(String id) {
        PaySubMerchantApplyment existing = paySubMerchantApplymentService.get(id);
        if (existing == null) {
            throw new BizErrorException("进件申请单不存在");
        }
        if (!PaySubMerchantApplyLocalStatusEnum.DRAFT.getValue().equals(existing.getApplyStatus())) {
            throw new BizErrorException("仅草稿状态可编辑，请先同步状态或新建申请单");
        }
        return existing;
    }

    private static PaySubMerchantApplyment buildEntityFromDto(PaySubMerchantApplymentSaveDTO dto) {
        PaySubMerchantApplyment entity = PaySubMerchantApplymentConvert.INSTANCE.toEntity(dto);
        applyApplymentTenantFromRequest(entity, dto.getApplymentTenantId(), dto.getApplymentTenantCode());
        return entity;
    }

    /** 以前端所选企业租户写入业务字段 applyment_tenant_id / applyment_tenant_code。 */
    private static void applyApplymentTenantFromRequest(PaySubMerchantApplyment entity, String applymentTenantId, String applymentTenantCode) {
        entity.setApplymentTenantId(PayChannelTenantSupport.toStorageKey(applymentTenantId));
        if (StrUtil.isNotBlank(applymentTenantCode)) {
            entity.setApplymentTenantCode(applymentTenantCode.trim());
        } else if (PayChannelTenantSupport.isPlatformLevel(applymentTenantId)) {
            entity.setApplymentTenantCode(PayChannelTenantSupport.PLATFORM_TENANT_CODE);
        } else {
            throw new BizErrorException("进件所属租户编号不能为空，请重新选择所属租户");
        }
    }

    /**
     * 进件租户全局唯一：新增时 excludeId 为空；编辑时排除当前行，若仍有其它行相同则拒绝保存。
     */
    private void assertApplymentTenantUnique(String applymentTenantId, String excludeId) {
        long dupCount = paySubMerchantApplymentService.countByApplymentTenantId(applymentTenantId, excludeId);
        if (dupCount > 0) {
            throw new BizErrorException(StrUtil.isBlank(excludeId)
                    ? "该租户已存在进件申请，每个租户仅允许一条记录"
                    : "该租户存在多条进件记录，请先删除重复数据后再保存");
        }
    }

    @Transactional(readOnly = false)
    public void delete(String ids) {
        paySubMerchantApplymentService.batchDelete(Arrays.asList(ids.split(",")));
    }

    /**
     * 向微信提交进件申请单。
     */
    @Transactional(readOnly = false)
    public PaySubMerchantApplymentSubmitResultVO submit(String id) throws Exception {
        PaySubMerchantApplyment entity = paySubMerchantApplymentService.get(id);
        if (entity == null) {
            throw new IllegalArgumentException("申请单不存在");
        }
        if (!StringUtils.hasText(entity.getDraftJson())) {
            throw new IllegalArgumentException("进件资料为空");
        }
        String configTenantKey = PaySubMerchantApplymentOperateSupport.resolveWechatConfigTenantKey(entity.getApplymentTenantId());
        PayWechatConfig config = payWechatWxPayClientHolder.requireActiveConfig(configTenantKey);
        PaySubMerchantApplymentApiTypeEnum apiType = resolveApplymentApiType(entity);
        String requestJson;
        JSONObject wxResp;
        if (apiType == PaySubMerchantApplymentApiTypeEnum.ECOMMERCE) {
            requestJson = PayEcommerceApplymentPayloadAssembler.assemble(entity.getDraftJson(), entity.getBusinessCode());
            wxResp = payWechatEcommerceApplymentClient.submitApplyment(requestJson, configTenantKey);
        } else {
            JSONObject draft = JSONUtil.parseObj(entity.getDraftJson());
            draft.set("business_code", entity.getBusinessCode());
            requestJson = draft.toString();
            wxResp = payWechatSubMerchantApplymentClient.submitApplyment(requestJson, configTenantKey);
        }
        Long applymentId = wxResp.getLong("applyment_id");

        entity.setWechatConfigId(config.getId());
        entity.setLastSubmitJson(requestJson);
        entity.setApplymentId(applymentId);
        entity.setApplyStatus(PaySubMerchantApplyLocalStatusEnum.SUBMITTED.getValue());
        entity.setApplyStatusDesc("已提交微信，请同步审核状态");
        entity.setSubmitDate(LocalDateTime.now());
        paySubMerchantApplymentService.updatePaySubMerchantApplyment(entity);

        return buildResult(entity, wxResp.toString());
    }

    /**
     * 从微信同步申请状态。
     */
    @Transactional(readOnly = false)
    public PaySubMerchantApplymentSubmitResultVO syncStatus(String id) throws Exception {
        PaySubMerchantApplyment entity = paySubMerchantApplymentService.get(id);
        if (entity == null) {
            throw new IllegalArgumentException("申请单不存在");
        }
        String configTenantKey = PaySubMerchantApplymentOperateSupport.resolveWechatConfigTenantKey(entity.getApplymentTenantId());
        PaySubMerchantApplymentApiTypeEnum apiType = resolveApplymentApiType(entity);
        JSONObject wxResp;
        if (apiType == PaySubMerchantApplymentApiTypeEnum.ECOMMERCE) {
            if (entity.getApplymentId() != null) {
                wxResp = payWechatEcommerceApplymentClient.queryByApplymentId(entity.getApplymentId(), configTenantKey);
            } else if (StrUtil.isNotBlank(entity.getBusinessCode())) {
                wxResp = payWechatEcommerceApplymentClient.queryByOutRequestNo(entity.getBusinessCode(), configTenantKey);
            } else {
                throw new IllegalStateException("缺少 business_code 或 applyment_id，无法查询");
            }
        } else if (entity.getApplymentId() != null) {
            wxResp = payWechatSubMerchantApplymentClient.queryByApplymentId(entity.getApplymentId(), configTenantKey);
        } else if (StrUtil.isNotBlank(entity.getBusinessCode())) {
            wxResp = payWechatSubMerchantApplymentClient.queryByBusinessCode(entity.getBusinessCode(), configTenantKey);
        } else {
            throw new IllegalStateException("缺少 business_code 或 applyment_id，无法查询");
        }
        applyWxStatus(entity, wxResp);
        entity.setLastQueryJson(wxResp.toString());
        entity.setLastSyncDate(LocalDateTime.now());
        paySubMerchantApplymentService.updatePaySubMerchantApplyment(entity);
        payProfitSharingReceiverBootstrapSupport.bootstrapIfReady(entity);
        return buildResult(entity, wxResp.toString());
    }

    /**
     * 上传进件图片到微信，返回 media_id。
     */
    public PaySubMerchantMediaUploadVO uploadMedia(MultipartFile file, String applymentTenantId) throws Exception {
        String configTenantKey = PaySubMerchantApplymentOperateSupport.resolveWechatConfigTenantKey(requireApplymentTenantId(applymentTenantId));
        String mediaId = payWechatSubMerchantApplymentClient.uploadImage(file, configTenantKey);
        PaySubMerchantMediaUploadVO vo = new PaySubMerchantMediaUploadVO();
        vo.setMediaId(mediaId);
        return vo;
    }

    private static PaySubMerchantApplymentApiTypeEnum resolveApplymentApiType(PaySubMerchantApplyment entity) {
        return PaySubMerchantApplymentApiTypeEnum.fromValue(entity.getApplymentApiType());
    }

    private static void applyWxStatus(PaySubMerchantApplyment entity, JSONObject wxResp) {
        String state = wxResp.getStr("applyment_state");
        if (StrUtil.isNotBlank(state)) {
            entity.setApplyStatus(PaySubMerchantApplyStatusSupport.normalizeWxApplymentState(state));
        }
        String stateMsg = wxResp.getStr("applyment_state_msg");
        if (StrUtil.isBlank(stateMsg)) {
            stateMsg = wxResp.getStr("applyment_state_desc");
        }
        entity.setApplyStatusDesc(stateMsg);
        entity.setSignUrl(wxResp.getStr("sign_url"));
        String legalValidationUrl = wxResp.getStr("legal_validation_url");
        if (StrUtil.isNotBlank(legalValidationUrl) && StrUtil.isBlank(entity.getSignUrl())) {
            entity.setSignUrl(legalValidationUrl);
        }
        entity.setSubMchId(wxResp.getStr("sub_mchid"));
        if (wxResp.getLong("applyment_id") != null) {
            entity.setApplymentId(wxResp.getLong("applyment_id"));
        }
    }

    private static PaySubMerchantApplymentSubmitResultVO buildResult(PaySubMerchantApplyment entity, String wxJson) {
        PaySubMerchantApplymentSubmitResultVO vo = new PaySubMerchantApplymentSubmitResultVO();
        vo.setId(entity.getId());
        vo.setApplymentId(entity.getApplymentId());
        vo.setApplyStatus(entity.getApplyStatus());
        vo.setApplyStatusDesc(entity.getApplyStatusDesc());
        vo.setSignUrl(entity.getSignUrl());
        vo.setSubMchId(entity.getSubMchId());
        vo.setWxResponseJson(wxJson);
        return vo;
    }

    private static void assertDraftJsonValid(String draftJson) {
        if (!JSONUtil.isTypeJSON(draftJson)) {
            throw new IllegalArgumentException("draftJson 必须是合法 JSON");
        }
    }

    /**
     * 生成唯一 business_code：{服务商商户号}_{雪花ID}，总长不超过 124。
     */
    private String allocateBusinessCode(String storageApplymentTenantId) {
        String tenantForConfig = PaySubMerchantApplymentOperateSupport.resolveWechatConfigTenantKey(storageApplymentTenantId);
        PayWechatConfig config = payWechatWxPayClientHolder.requireActiveConfig(tenantForConfig);
        String prefix = StrUtil.blankToDefault(config.getMchId(), "SP").trim();
        prefix = prefix.replaceAll("[^A-Za-z0-9]", "");
        if (StrUtil.isBlank(prefix)) {
            prefix = "SP";
        }
        for (int i = 0; i < 5; i++) {
            String suffix = IdUtil.getSnowflakeNextIdStr();
            String code = prefix + "_" + suffix;
            if (code.length() > 124) {
                int maxPrefix = 124 - suffix.length() - 1;
                code = (maxPrefix > 0 ? prefix.substring(0, Math.min(prefix.length(), maxPrefix)) : "SP") + "_" + suffix;
            }
            if (paySubMerchantApplymentService.findByBusinessCode(code, storageApplymentTenantId) == null) {
                return code;
            }
        }
        throw new IllegalStateException("生成业务申请编号失败，请重试");
    }

    private void ensureBusinessCodeUnique(String businessCode, String storageApplymentTenantId, String excludeId) {
        PaySubMerchantApplyment dup = paySubMerchantApplymentService.findByBusinessCode(businessCode.trim(), storageApplymentTenantId);
        if (dup != null && (excludeId == null || !excludeId.equals(dup.getId()))) {
            throw new IllegalArgumentException("业务申请编号已存在: " + businessCode);
        }
    }

    private static void extractSummaryFromDraft(PaySubMerchantApplyment entity) {
        JSONObject root = JSONUtil.parseObj(entity.getDraftJson());
        JSONObject businessInfo = root.getJSONObject("business_info");
        if (businessInfo != null) {
            entity.setMerchantShortname(businessInfo.getStr("merchant_shortname"));
        }
        JSONObject subjectInfo = root.getJSONObject("subject_info");
        if (subjectInfo != null) {
            entity.setSubjectType(subjectInfo.getStr("subject_type"));
        }
    }

    private static void validateApplymentTenantFromRequest(PaySubMerchantApplymentSaveDTO dto) {
        if (StrUtil.isBlank(dto.getApplymentTenantId())) {
            throw new BizErrorException("进件所属租户不能为空，请在前端选择租户");
        }
        if (StrUtil.isBlank(dto.getApplymentTenantCode())) {
            throw new BizErrorException("进件所属租户编号不能为空，请重新选择所属租户");
        }
    }

    private static String requireApplymentTenantId(String applymentTenantId) {
        if (PayChannelTenantSupport.isPlatformLevel(applymentTenantId)) {
            return PayChannelTenantSupport.toStorageKey(applymentTenantId);
        }
        if (StrUtil.isBlank(applymentTenantId)) {
            throw new BizErrorException("进件所属租户不能为空，请在前端选择租户");
        }
        return PayChannelTenantSupport.toStorageKey(applymentTenantId);
    }

    private static void assertApplymentTenantUnchanged(PaySubMerchantApplyment existing, PaySubMerchantApplyment incoming) {
        String existId = StrUtil.trimToEmpty(existing.getApplymentTenantId());
        String existCode = StrUtil.trimToEmpty(existing.getApplymentTenantCode());
        String inId = StrUtil.trimToEmpty(incoming.getApplymentTenantId());
        String inCode = StrUtil.trimToEmpty(incoming.getApplymentTenantCode());
        if (!Objects.equals(existId, inId)) {
            throw new BizErrorException("进件所属租户不可修改");
        }
        if (!Objects.equals(existCode, inCode)) {
            throw new BizErrorException("进件所属租户不可修改");
        }
    }

}
