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

import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.jeesharp.business.modules.pay.paychannelconfig.convert.PayChannelConfigConvert;
import com.jeesharp.business.modules.pay.paychannelconfig.dto.PayChannelConfigSaveDTO;
import com.jeesharp.business.modules.pay.paychannelconfig.entity.PayChannelConfig;
import com.jeesharp.business.modules.pay.paychannelconfig.enums.PayChannelCodeEnum;
import com.jeesharp.business.modules.pay.paychannelconfig.enums.PayChannelConfigStatusEnum;
import com.jeesharp.business.modules.pay.paychannelconfig.service.PayChannelConfigService;
import com.jeesharp.business.modules.pay.paychannelconfig.support.PayChannelTenantSupport;
import com.jeesharp.business.modules.pay.paychannelconfig.vo.PayChannelConfigInfoVO;
import com.jeesharp.business.modules.pay.paychannelconfig.vo.PayChannelConfigListVO;
import com.jeesharp.business.modules.pay.paychannelconfig.vo.PayChannelConfigQueryVO;
import com.jeesharp.business.modules.pay.paywechatconfig.service.PayWechatConfigRuntimeService;
import com.jeesharp.framework.exception.BizErrorException;
import com.jeesharp.framework.pojo.Page;
import com.jeesharp.framework.pojo.query.PageRespVO;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Arrays;
import java.util.List;

/**
 * 支付渠道公共配置应用服务：管理端 CRUD 与按渠道查询生效配置。
 *
 * @author jeesharp
 * @since 2026-05-25
 */
@Service
@AllArgsConstructor
@Transactional(readOnly = true)
public class PayChannelConfigApplicationService {

    private final PayChannelConfigService payChannelConfigService;
    private final PayWechatConfigRuntimeService payWechatConfigRuntimeService;

    public PayChannelConfigInfoVO get(String id) {
        PayChannelConfigInfoVO vo = PayChannelConfigConvert.INSTANCE.toInfoVO(payChannelConfigService.get(id));
        fillNames(vo);
        return vo;
    }

    public PayChannelConfigInfoVO getActiveByChannelCode(String channelCode, String tenantId) {
        PayChannelConfig active = payChannelConfigService.findActiveByChannelCode(channelCode, tenantId);
        PayChannelConfigInfoVO vo = PayChannelConfigConvert.INSTANCE.toInfoVO(active);
        fillNames(vo);
        return vo;
    }

    public PageRespVO<PayChannelConfigListVO> findPage(Page<PayChannelConfig> page, PayChannelConfigQueryVO query) {
        Page<PayChannelConfig> result = payChannelConfigService.findPage(page, query);
        PageRespVO<PayChannelConfigListVO> pageVo = PayChannelConfigConvert.INSTANCE.toPageRespVO(result);
        if (pageVo.getRecords() != null) {
            pageVo.getRecords().forEach(this::fillListNames);
        }
        return pageVo;
    }

    @Transactional(readOnly = false)
    public void create(PayChannelConfigSaveDTO dto) {
        assertChannelCode(dto);
        if (StrUtil.isNotBlank(dto.getId())) {
            throw new BizErrorException("新增请求不应携带主键 id");
        }
        PayChannelConfig entity = PayChannelConfigConvert.INSTANCE.toEntity(dto);
        applyTenantFields(entity, dto);
        entity.setConfigCode(generateUniquePayChannelConfigCode(entity.getChannelCode(), entity.getTenantId()));
        payChannelConfigService.createPayChannelConfig(entity);
        evictWechatCacheIfNeeded(entity.getChannelCode());
    }

    @Transactional(readOnly = false)
    public void update(PayChannelConfigSaveDTO dto) {
        assertChannelCode(dto);
        if (StrUtil.isBlank(dto.getId())) {
            throw new BizErrorException("更新请求必须携带主键 id");
        }
        PayChannelConfig entity = PayChannelConfigConvert.INSTANCE.toEntity(dto);
        if (StrUtil.isBlank(entity.getConfigCode())) {
            PayChannelConfig existing = payChannelConfigService.get(dto.getId());
            entity.setConfigCode(existing != null ? existing.getConfigCode() : "DEFAULT");
        }
        applyTenantFields(entity, dto);
        payChannelConfigService.updatePayChannelConfig(entity);
        evictWechatCacheIfNeeded(entity.getChannelCode());
    }

    /**
     * 新增时生成 {@code pay_channel_config.config_code}：前缀为渠道编码（大写）+ {@code _}，后缀为雪花数字串，保证同渠道同租户下唯一。
     */
    private String generateUniquePayChannelConfigCode(String channelCode, String tenantId) {
        if (StrUtil.isBlank(channelCode)) {
            throw new BizErrorException("渠道编码不能为空");
        }
        String ch = channelCode.trim().toUpperCase();
        String prefix = ch + "_";
        for (int i = 0; i < 8; i++) {
            String candidate = prefix + IdUtil.getSnowflakeNextIdStr();
            if (candidate.length() > 32) {
                candidate = candidate.substring(0, 32);
            }
            PayChannelConfig dup = payChannelConfigService.findByChannelAndConfigCode(ch, candidate, tenantId);
            if (dup == null) {
                return candidate;
            }
        }
        throw new BizErrorException("无法生成唯一配置编码，请重试");
    }

    private void assertChannelCode(PayChannelConfigSaveDTO dto) {
        if (PayChannelCodeEnum.fromCode(dto.getChannelCode()) == null) {
            throw new BizErrorException("无效的渠道编码");
        }
    }

    private static void applyTenantFields(PayChannelConfig entity, PayChannelConfigSaveDTO dto) {
        if (StrUtil.isNotBlank(dto.getTenantCode())) {
            entity.setTenantCode(dto.getTenantCode().trim());
        } else if (PayChannelTenantSupport.isPlatformLevel(dto.getTenantId())) {
            entity.setTenantCode(PayChannelTenantSupport.PLATFORM_TENANT_CODE);
        }
        entity.setTenantId(PayChannelTenantSupport.normalize(dto.getTenantId()));
    }

    private void evictWechatCacheIfNeeded(String channelCode) {
        if (PayChannelCodeEnum.WECHAT.getCode().equalsIgnoreCase(channelCode)) {
            payWechatConfigRuntimeService.evictWxPayClientCache();
        }
    }

    @Transactional(readOnly = false)
    public void delete(String ids) {
        List<String> idList = Arrays.asList(ids.split(","));
        for (String id : idList) {
            PayChannelConfig existing = payChannelConfigService.get(id);
            PayChannelTenantSupport.assertDeletableTenantCode(existing != null ? existing.getTenantCode() : null);
        }
        payChannelConfigService.batchDelete(idList);
        payWechatConfigRuntimeService.evictWxPayClientCache();
    }

    private void fillNames(PayChannelConfigInfoVO vo) {
        if (vo == null) {
            return;
        }
        vo.setChannelCodeName(PayChannelCodeEnum.getDescByCode(vo.getChannelCode()));
        vo.setStatusName(PayChannelConfigStatusEnum.getDescByValue(vo.getStatus()));
    }

    private void fillListNames(PayChannelConfigListVO vo) {
        if (vo == null) {
            return;
        }
        vo.setChannelCodeName(PayChannelCodeEnum.getDescByCode(vo.getChannelCode()));
        vo.setStatusName(PayChannelConfigStatusEnum.getDescByValue(vo.getStatus()));
    }
}
