package com.jeesharp.business.modules.member.application;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.google.common.collect.Maps;
import com.jeesharp.business.modules.contentsecurity.api.ContentSecurityApi;
import com.jeesharp.business.modules.contentsecurity.dto.ContentCheckRequest;
import com.jeesharp.business.modules.contentsecurity.dto.ContentCheckResult;
import com.jeesharp.business.modules.contentsecurity.dto.ContentMemberSnapshot;
import com.jeesharp.business.modules.contentsecurity.dto.ContentSecurityBizRef;
import com.jeesharp.business.modules.contentsecurity.enums.CheckDecisionEnum;
import com.jeesharp.business.modules.contentsecurity.enums.ContentSceneEnum;
import com.jeesharp.business.modules.contentsecurity.enums.ContentSecurityBizModuleEnum;
import com.jeesharp.business.modules.contentsecurity.enums.ContentTypeEnum;
import com.jeesharp.business.modules.member.api.MemberPointsApi;
import com.jeesharp.business.modules.member.application.support.MemberCodeSupport;
import com.jeesharp.business.modules.member.convert.MemberConvert;
import com.jeesharp.business.modules.member.dto.MemberSaveDTO;
import com.jeesharp.business.modules.member.dto.UpdateMemberDTO;
import com.jeesharp.business.modules.member.dto.CompleteProfileDTO;
import com.jeesharp.business.modules.member.entity.Member;
import com.jeesharp.business.modules.member.entity.MemberLevel;
import com.jeesharp.business.modules.member.entity.MemberLevelFeature;
import com.jeesharp.business.modules.member.entity.MemberPointsLog;
import com.jeesharp.business.modules.member.enums.MemberPointsBizTypeEnum;
import com.jeesharp.business.modules.member.enums.MemberPointsTypeEnum;
import com.jeesharp.business.modules.member.service.MemberLevelFeatureService;
import com.jeesharp.business.modules.member.service.MemberLevelService;
import com.jeesharp.business.modules.member.service.MemberOauthService;
import com.jeesharp.business.modules.member.service.MemberPointsLogService;
import com.jeesharp.business.modules.member.service.MemberService;
import com.jeesharp.business.modules.member.service.MemberSignInRecordService;
import com.jeesharp.business.modules.member.vo.*;
import com.jeesharp.framework.exception.BizErrorException;
import com.jeesharp.framework.pojo.Page;
import com.jeesharp.framework.pojo.query.PageRespVO;
import com.jeesharp.framework.pojo.vo.ImportRespVO;
import com.jeesharp.framework.utils.WebFrameworkUtils;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 会员应用服务
 *
 * @author jeesharp
 * @version 2024-01-24
 */
@Service
@Transactional
@AllArgsConstructor
public class MemberApplicationService {

    private final MemberService memberService;
    private final MemberPointsApplicationService memberPointsApplicationService;
    private final MemberLevelService memberLevelService;
    private final MemberLevelFeatureService memberLevelFeatureService;
    private final MemberPointsLogService memberPointsLogService;
    private final MemberOauthService memberOauthService;
    private final ContentSecurityApi contentSecurityApi;

    // ==================== 简单操作：直接委托 Domain Service ====================

    /**
     * 按会员与小程序 appId 查询微信 openid，供支付 JSAPI 预下单使用。
     */
    @Transactional(readOnly = true)
    public String getWechatMiniOpenidByMemberIdAppId(String memberId, String appId) {
        return memberOauthService.getWechatMiniOpenidByMemberIdAppId(memberId, appId);
    }

    /**
     * 获取成长等级下拉选项（消费成长档）
     *
     * @return 选项列表 [{ value: levelCode, label: levelName }]
     */
    @Transactional(readOnly = true)
    public List<Map<String, String>> getGrowthLevelOptions() {
        return buildLevelOptions(loadGrowthLevels().stream()
                .sorted(Comparator.comparing(
                        MemberLevel::getLevelSort,
                        Comparator.nullsLast(Comparator.naturalOrder())))
                .toList());
    }

    /**
     * @deprecated 使用 {@link #getGrowthLevelOptions()}，保留兼容旧前端路径 levelOptions
     */
    @Transactional(readOnly = true)
    public List<Map<String, String>> getLevelOptions() {
        return getGrowthLevelOptions();
    }

    /**
     * 获取付费等级下拉选项（付费开通档）
     */
    @Transactional(readOnly = true)
    public List<Map<String, String>> getVipLevelOptions() {
        return buildLevelOptions(memberLevelService.listPaidLevels());
    }

    private List<Map<String, String>> buildLevelOptions(List<MemberLevel> levels) {
        List<Map<String, String>> options = new ArrayList<>();
        for (MemberLevel level : levels) {
            Map<String, String> item = new HashMap<>();
            item.put("value", level.getLevelCode());
            item.put("label", level.getLevelName());
            options.add(item);
        }
        return options;
    }

    /**
     * 解析会员所属租户 ID，供站内信等按租户隔离的 C 端能力使用。
     */
    public String getMemberTenantId(String memberId) {
        if (StrUtil.isBlank(memberId)) {
            return null;
        }
        Member member = memberService.get(memberId);
        return member == null ? null : member.getTenantId();
    }

    /**
     * 获取会员详情
     *
     * @param id 会员ID
     * @return 会员信息VO
     */
    @Transactional
    public MemberInfoVO getMember(String id) {
        String memberNo = memberService.ensureMemberNo(id);
        Member member = memberService.get(id);
        MemberInfoVO infoVO = MemberConvert.INSTANCE.toInfoVO(member);
        if (StrUtil.isBlank(infoVO.getMemberNo())) {
            infoVO.setMemberNo(memberNo);
        }
        String growthLevelName = resolveGrowthLevelName(member, loadGrowthLevels());
        infoVO.setLevelName(growthLevelName);
        return infoVO;
    }

    /**
     * 获取当前会员码卡片（含二维码内容 MKM|memberNo）。
     */
    @Transactional
    public MemberCodeCardVO getMemberCodeCard(String memberId) {
        String memberNo = memberService.ensureMemberNo(memberId);
        MemberCodeCardVO vo = new MemberCodeCardVO();
        vo.setMemberNo(memberNo);
        vo.setDisplayNo(MemberCodeSupport.formatDisplayNo(memberNo));
        vo.setQrcodeContent(MemberCodeSupport.buildQrcodeContent(memberNo));
        return vo;
    }

    /**
     * 分页查询会员列表
     *
     * @param page  分页对象
     * @param query 查询条件
     * @return 分页结果
     */
    @Transactional(readOnly = true)
    public PageRespVO<MemberListVO> findPage(Page<Member> page, MemberQueryVO query) {
        Page<Member> result = memberService.findPage(page, query);
        PageRespVO<MemberListVO> pageResp = MemberConvert.INSTANCE.toPageRespVO(result);
        enrichLevelNames(result.getRecords(), pageResp.getRecords());
        return pageResp;
    }

    /**
     * 保存会员
     *
     * @param dto 保存请求
     */
    public void saveData(MemberSaveDTO dto) {
        memberService.saveData(dto);
    }

    /**
     * 删除会员
     *
     * @param ids 会员ID，多个用逗号分隔
     */
    public void delete(String ids) {
        List<String> idList = StrUtil.split(ids, ",");
        memberService.deleteData(idList);
    }

    // ==================== 导入导出 ====================

    /**
     * 获取导出数据
     *
     * @param query 查询条件
     * @return 导出数据列表
     */
    @Transactional(readOnly = true)
    public List<MemberListVO> findExportList(MemberQueryVO query) {
        List<Member> list = memberService.findList(query);
        List<MemberListVO> exportList = MemberConvert.INSTANCE.toListVO(list);
        enrichLevelNames(list, exportList);
        return exportList;
    }

    /**
     * 导入会员数据
     *
     * @param list 导入数据列表
     * @return 导入结果
     */
    public ImportRespVO importData(List<MemberImportVO> list) {
        return memberService.importData(list);
    }

    // ==================== 复杂业务操作 ====================

    /**
     * 根据手机号查询会员
     *
     * @param mobile 手机号
     * @return 会员信息VO
     */
    @Transactional(readOnly = true)
    public MemberInfoVO getByMobile(String mobile) {
        Member member = memberService.getByMobile(mobile);
        if (member == null) {
            return null;
        }
        MemberInfoVO infoVO = MemberConvert.INSTANCE.toInfoVO(member);
        infoVO.setLevelName(resolveGrowthLevelName(member, loadGrowthLevels()));
        return infoVO;
    }

    /**
     * 更新会员状态
     *
     * @param id     会员ID
     * @param status 状态
     */
    public void updateStatus(String id, Integer status) {
        memberService.updateStatus(id,status);
    }

    /**
     * 禁用会员
     *
     * @param id 会员ID
     */
    public void disable(String id) {
        memberService.updateStatus(id, 0);
    }

    /**
     * 启用会员
     *
     * @param id 会员ID
     */
    public void enable(String id) {
        memberService.updateStatus(id, 1);
    }

    /**
     * 增加积分
     *
     * @param memberId 会员ID
     * @param points   积分数量
     * @param bizType  业务类型
     * @param bizId    业务ID
     * @param bizDesc  业务描述
     */
    public void addPoints(String memberId, Integer points, String bizType, String bizId, String bizDesc) {
        if (points == null || points <= 0) {
            return;
        }
        memberPointsApplicationService.grantManualAdjust(memberId, points, bizDesc);
    }

    /**
     * 扣减积分
     *
     * @param memberId 会员ID
     * @param points   积分数量
     * @param bizType  业务类型（保留参数兼容，实际走 MANUAL 场景）
     * @param bizId    业务ID（保留参数兼容）
     * @param bizDesc  业务描述
     */
    public void deductPoints(String memberId, Integer points, String bizType, String bizId, String bizDesc) {
        if (points == null || points <= 0) {
            return;
        }
        memberPointsApplicationService.deductManualAdjust(memberId, points, bizDesc);
    }

    /**
     * 增加成长值
     *
     * @param memberId    会员ID
     * @param growthValue 成长值
     * @param bizType     业务类型
     * @param bizDesc     业务描述
     */
    public void addGrowthValue(String memberId, Integer growthValue, String bizType, String bizDesc) {
        memberService.addGrowthValue(memberId, growthValue, bizType, bizDesc);
    }

    // ==================== 会员功能权限（等级相关） ====================

    /**
     * 获取会员可用功能列表（供移动端展示）
     *
     * @param memberId 会员ID
     * @return 功能列表（含 featureCode、featureName、featureDesc）
     */
    @Transactional(readOnly = true)
    public List<MemberFeatureVO> getMemberFeatures(String memberId) {
        List<String> codes = memberService.getMemberFeatureCodes(memberId);
        if (codes.isEmpty()) {
            return new ArrayList<>();
        }
        List<MemberLevelFeature> features = memberLevelFeatureService.listByFeatureCodes(codes);
        List<MemberFeatureVO> ordered = new ArrayList<>();
        for (String code : codes) {
            MemberLevelFeature f = features.stream().filter(x -> code.equals(x.getFeatureCode())).findFirst().orElse(null);
            MemberFeatureVO vo = new MemberFeatureVO();
            vo.setFeatureCode(code);
            vo.setFeatureName(f != null ? f.getFeatureName() : code);
            vo.setFeatureDesc(f != null ? f.getFeatureDesc() : null);
            ordered.add(vo);
        }
        return ordered;
    }

    /**
     * 校验会员是否拥有某功能
     *
     * @param memberId    会员ID
     * @param featureCode 功能权限码
     * @return 是否拥有
     */
    @Transactional(readOnly = true)
    public boolean hasFeature(String memberId, String featureCode) {
        if (StrUtil.isBlank(memberId) || StrUtil.isBlank(featureCode)) {
            return false;
        }
        List<String> codes = memberService.getMemberFeatureCodes(memberId);
        return codes.contains(featureCode);
    }

    @Transactional(readOnly = false)
    public void updateAvatar(String memberId, String avatar) {
        String url = StrUtil.trimToNull(avatar);
        if (url == null) {
            throw new BizErrorException("头像地址不能为空");
        }
        Member exist = memberService.get(memberId);
        if (exist == null) {
            throw new BizErrorException("会员不存在");
        }
        assertAvatarImageSecurity(exist, url, exist.getAvatar());
        LambdaUpdateWrapper<Member> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.set(Member::getAvatar, url);
        updateWrapper.eq(Member::getId, memberId);
        memberService.update(updateWrapper);
    }

    @Transactional(readOnly = false)
    public void updateNickname(String memberId,String  nickname){
        LambdaUpdateWrapper<Member> updateWrapper =    new LambdaUpdateWrapper<Member>();
        updateWrapper.set(Member::getNickname,nickname);
        updateWrapper.eq(Member::getId,memberId);
        memberService.update(updateWrapper);
    }

    /**
     * C 端按传入字段局部更新会员资料（昵称、头像、性别、生日、邮箱及省市区），未传的字段不改库。
     *
     * @param memberId 当前登录会员主键
     * @param dto      待更新字段
     * @author jeesharp
     * @since 2026-05-13
     */
    @Transactional(readOnly = false)
    public void updateMemberByMobile(String memberId, UpdateMemberDTO dto) {
        if (StrUtil.isBlank(memberId) || dto == null) {
            return;
        }
        Member exist = memberService.get(memberId);
        if (exist == null) {
            throw new BizErrorException("会员不存在");
        }
        LambdaUpdateWrapper<Member> uw = new LambdaUpdateWrapper<>();
        uw.eq(Member::getId, memberId);
        boolean any = false;
        if (dto.getNickname() != null) {
            String n = StrUtil.trim(dto.getNickname());
            if (StrUtil.isBlank(n)) {
                throw new BizErrorException("昵称不能为空");
            }
            uw.set(Member::getNickname, n);
            any = true;
        }
        if (dto.getAvatar() != null) {
            String url = StrUtil.trimToNull(dto.getAvatar());
            if (url != null) {
                assertAvatarImageSecurity(exist, url, exist.getAvatar());
            }
            uw.set(Member::getAvatar, url);
            any = true;
        }
        if (dto.getGender() != null) {
            uw.set(Member::getGender, dto.getGender());
            any = true;
        }
        if (dto.getBirthday() != null) {
            if (StrUtil.isBlank(dto.getBirthday())) {
                uw.set(Member::getBirthday, null);
            } else {
                try {
                    uw.set(Member::getBirthday, LocalDate.parse(dto.getBirthday().trim()));
                } catch (DateTimeParseException ex) {
                    throw new BizErrorException("生日格式须为 yyyy-MM-dd");
                }
            }
            any = true;
        }
        if (dto.getEmail() != null) {
            uw.set(Member::getEmail, StrUtil.trimToNull(dto.getEmail()));
            any = true;
        }
        if (dto.getProvince() != null) {
            uw.set(Member::getProvince, StrUtil.trimToNull(dto.getProvince()));
            any = true;
        }
        if (dto.getCity() != null) {
            uw.set(Member::getCity, StrUtil.trimToNull(dto.getCity()));
            any = true;
        }
        if (dto.getDistrict() != null) {
            uw.set(Member::getDistrict, StrUtil.trimToNull(dto.getDistrict()));
            any = true;
        }
        if (any) {
            memberService.update(uw);
        }
    }

    @Transactional(readOnly = false)
    public void realNameAuth(String memberId,String   realName,String idCard){

    }

    /**
     * 完善个人信息（真实姓名 + 手机号）。
     * <p>用于登录后强制补全资料：手机号变更需校验唯一性。</p>
     *
     * @param memberId 当前登录会员主键
     * @param dto      姓名 + 手机号
     */
    @Transactional(readOnly = false)
    public void completeProfile(String memberId, CompleteProfileDTO dto) {
        if (StrUtil.isBlank(memberId) || dto == null) {
            throw new BizErrorException("参数不能为空");
        }
        String realName = StrUtil.trim(dto.getRealName());
        String mobile = StrUtil.trim(dto.getMobile());
        if (StrUtil.isBlank(realName)) {
            throw new BizErrorException("姓名不能为空");
        }
        if (StrUtil.isBlank(mobile)) {
            throw new BizErrorException("手机号不能为空");
        }
        Member exist = memberService.get(memberId);
        if (exist == null) {
            throw new BizErrorException("会员不存在");
        }
        // 手机号与当前不一致时校验唯一性
        if (!mobile.equals(exist.getMobile()) && !memberService.checkMobileUnique(mobile, memberId)) {
            throw new BizErrorException("该手机号已被其他账号使用");
        }
        LambdaUpdateWrapper<Member> uw = new LambdaUpdateWrapper<>();
        uw.eq(Member::getId, memberId)
                .set(Member::getRealName, realName)
                .set(Member::getMobile, mobile)
                .set(Member::getIsCompleteProfile, 1);
        memberService.update(uw);
    }

    @Transactional(readOnly = false)
    public void updateMemberInfo(MemberSaveDTO memberSaveDTO){
        Member entity = MemberConvert.INSTANCE.toEntity(memberSaveDTO);
        memberService.update(entity);
    }

    /**
     * 头像图片内容安全检测：未变更 URL 跳过；违规或未出结果前禁止落库。
     */
    private void assertAvatarImageSecurity(Member member, String avatarUrl, String currentAvatar) {
        if (member == null || StrUtil.isBlank(avatarUrl) || StrUtil.equals(avatarUrl, currentAvatar)) {
            return;
        }
        ContentCheckResult result = contentSecurityApi.checkMedia(ContentCheckRequest.builder()
                .contentType(ContentTypeEnum.IMAGE)
                .scene(ContentSceneEnum.PROFILE)
                .memberId(member.getId())
                .tenantId(member.getTenantId())
                .mediaUrl(avatarUrl)
                .memberSnapshot(buildMemberSnapshot(member))
                .bizRef(ContentSecurityBizRef.builder()
                        .bizModule(ContentSecurityBizModuleEnum.MEMBER.getCode())
                        .bizType("AVATAR")
                        .bizId(member.getId())
                        .build())
                .build());
        contentSecurityApi.assertPass(result);
        if (result.getDecision() == CheckDecisionEnum.PENDING) {
            throw new BizErrorException("头像图片正在检测，请稍后再试");
        }
    }

    private static ContentMemberSnapshot buildMemberSnapshot(Member member) {
        if (member == null) {
            return null;
        }
        String displayName = StrUtil.isNotBlank(member.getRealName()) ? member.getRealName() : member.getNickname();
        return ContentMemberSnapshot.builder()
                .memberNo(member.getMemberNo())
                .memberNickname(displayName)
                .memberMobile(member.getMobile())
                .memberIdCard(member.getIdCard())
                .build();
    }

    // ==================== 后台管理：等级功能配置 ====================

    /**
     * 分页查询等级功能配置
     */
    @Transactional(readOnly = true)
    public Page<MemberLevelFeature> findLevelFeaturePage(Page<MemberLevelFeature> page, MemberLevelFeatureQueryVO query) {
        return memberLevelFeatureService.findPage(page, query);
    }

    /**
     * 保存等级功能配置
     */
    public void saveLevelFeature(MemberLevelFeature entity) {
        memberLevelFeatureService.save(entity);
    }

    /**
     * 删除等级功能配置
     */
    public void deleteLevelFeature(String ids) {
        List<String> idList = StrUtil.split(ids, ",");
        for (String id : idList) {
            memberLevelFeatureService.removeById(id);
        }
    }

    /**
     * 获取功能码下拉选项（去重）
     */
    @Transactional(readOnly = true)
    public List<MemberLevelFeature> getLevelFeatureOptions() {
        return memberLevelFeatureService.list();
    }

    /**
     * 当前会员可用积分（移动端「我的积分」等），与 {@code app_member.available_points} 一致。
     *
     * @param memberId 会员主键
     * @return 可用积分数，空按 0
     */
    @Transactional(readOnly = true)
    public Integer getAvailablePoints(String memberId) {
        Member member = memberService.get(memberId);
        if (member == null) {
            throw new BizErrorException("会员不存在");
        }
        Integer n = member.getAvailablePoints();
        return n != null ? n : 0;
    }

    /**
     * 小程序积分记录列表：仅读取 {@code app_member_points_log} 流水，与后台「积分流水」一致。
     */
    @Transactional(readOnly = true)
    public List<Map<String, Object>> listMiniProgramPointsRecords(String memberId) {
        if (memberService.get(memberId) == null) {
            throw new BizErrorException("会员不存在");
        }
        List<Map<String, Object>> list = new ArrayList<>();
        DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

        for (MemberPointsLog log : memberPointsLogService.listRecentByMember(memberId, 200)) {
            list.add(toMiniProgramPointsRecord(log, fmt));
        }
        return list;
    }

    private Map<String, Object> toMiniProgramPointsRecord(MemberPointsLog log, DateTimeFormatter fmt) {
        Map<String, Object> item = Maps.newHashMap();
        item.put("id", log.getId());
        item.put("title", resolvePointsRecordTitle(log));
        item.put("subtitle", resolvePointsRecordSubtitle(log));
        item.put("changeType", MemberPointsTypeEnum.getChangeTypeKey(log.getPointsType()));
        item.put("points", log.getPoints() != null ? log.getPoints() : 0);
        LocalDateTime when = log.getCreateDate() != null ? log.getCreateDate() : LocalDateTime.now();
        item.put("createTime", when.format(fmt));
        return item;
    }

    private String resolvePointsRecordTitle(MemberPointsLog log) {
        if (StrUtil.isNotBlank(log.getBizDesc())) {
            return log.getBizDesc();
        }
        String bizDesc = MemberPointsBizTypeEnum.getDescByCode(log.getBizType());
        if (StrUtil.isNotBlank(bizDesc)) {
            return bizDesc;
        }
        return MemberPointsTypeEnum.getDescByValue(log.getPointsType());
    }

    private String resolvePointsRecordSubtitle(MemberPointsLog log) {
        if (StrUtil.isNotBlank(log.getBizDesc())) {
            String title = resolvePointsRecordTitle(log);
            if (!log.getBizDesc().equals(title)) {
                return log.getBizDesc();
            }
        }
        return null;
    }

    // ==================== 私有方法 ====================

    /**
     * 批量填充列表行成长等级、付费等级名称。
     */
    private void enrichLevelNames(List<Member> members, List<MemberListVO> rows) {
        if (members == null || rows == null || members.isEmpty()) {
            return;
        }
        List<MemberLevel> growthLevels = loadGrowthLevels();
        Map<String, String> paidLevelNameMap = loadPaidLevelNameMap();
        int size = Math.min(members.size(), rows.size());
        for (int i = 0; i < size; i++) {
            Member member = members.get(i);
            MemberListVO row = rows.get(i);
            String growthLevelName = resolveGrowthLevelName(member, growthLevels);
            row.setGrowthLevelName(growthLevelName);
            row.setLevelName(growthLevelName);
            if (StrUtil.isNotBlank(member.getVipLevelCode())) {
                row.setVipLevelName(paidLevelNameMap.get(member.getVipLevelCode()));
            }
        }
    }

    private Map<String, String> loadPaidLevelNameMap() {
        Map<String, String> map = new HashMap<>();
        for (MemberLevel level : memberLevelService.listPaidLevels()) {
            if (StrUtil.isNotBlank(level.getLevelCode())) {
                map.put(level.getLevelCode(), level.getLevelName());
            }
        }
        return map;
    }

    /**
     * 按成长值解析成长等级名称；无匹配时回退 levelCode 对应成长档。
     */
    private String resolveGrowthLevelName(Member member, List<MemberLevel> growthLevels) {
        if (member == null) {
            return null;
        }
        int growthValue = member.getGrowthValue() != null ? member.getGrowthValue() : 0;
        for (MemberLevel level : growthLevels) {
            Integer min = level.getMinGrowthValue();
            if (min == null || min > growthValue) {
                continue;
            }
            Integer max = level.getMaxGrowthValue();
            if (max == null || growthValue <= max) {
                return level.getLevelName();
            }
        }
        if (StrUtil.isNotBlank(member.getLevelCode())) {
            for (MemberLevel level : growthLevels) {
                if (member.getLevelCode().equals(level.getLevelCode())) {
                    return level.getLevelName();
                }
            }
        }
        return null;
    }

    /**
     * 加载启用中的消费成长等级（is_paid_level=0），按 level_sort 降序便于区间匹配。
     */
    private List<MemberLevel> loadGrowthLevels() {
        MemberLevelQueryVO query = new MemberLevelQueryVO();
        query.setStatus(1);
        List<MemberLevel> levels = memberLevelService.findList(query);
        return levels.stream()
                .filter(level -> level.getIsPaidLevel() == null || level.getIsPaidLevel() == 0)
                .sorted(Comparator.comparing(
                        MemberLevel::getLevelSort,
                        Comparator.nullsLast(Comparator.reverseOrder())))
                .toList();
    }

}
