package shop.alien.store.service;

import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Triple;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import shop.alien.config.properties.RiskControlProperties;
import shop.alien.entity.second.LifeUserLog;
import shop.alien.entity.second.SecondGoods;
import shop.alien.entity.second.SecondRiskControlRecord;
import shop.alien.entity.second.SecondTradeRecord;
import shop.alien.entity.second.enums.SecondGoodsStatusEnum;
import shop.alien.entity.store.*;
import shop.alien.entity.store.dto.LifeFansFollowOutcome;
import shop.alien.entity.store.vo.LifeMessageVo;
import shop.alien.entity.store.vo.LifeUserVo;
import shop.alien.entity.store.vo.WebSocketVo;
import shop.alien.mapper.*;
import shop.alien.mapper.second.LifeUserLogMapper;
import shop.alien.mapper.second.SecondGoodsMapper;
import shop.alien.mapper.second.SecondRiskControlRecordMapper;
import shop.alien.mapper.second.SecondTradeRecordMapper;
import shop.alien.mapper.second.SecondUserCreditMapper;
import shop.alien.util.common.constant.LawyerStatusEnum;
import shop.alien.store.config.BaseRedisService;
import shop.alien.store.config.WebSocketProcess;
import shop.alien.store.feign.SecondServiceFeign;
import shop.alien.store.service.clockin.ClockInRecommendCacheService;
import shop.alien.store.service.dynamics.DynamicsRecommendCacheService;
import shop.alien.store.util.FunctionMagic;
import shop.alien.util.type.LifeFansIdentityQuery;
import shop.alien.store.util.LifeDynamicsIdentityHelper;
import shop.alien.util.type.LifeNoticeUtil;
import shop.alien.util.type.TypeUtil;

import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * 用户
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class LifeUserService extends ServiceImpl<LifeUserMapper, LifeUser> {

    /**
     * App 点击推送后跳转页面（与动态点赞通知一致）
     */
    private static final String FOLLOW_APP_PUSH_OPEN_PATH =
            "pages/secondHandTransactions/pages/message/noticesAndMessage";

    /**
     * 关注双方标识：`store_xxx` / `user_xxx`（整体无空格，后缀最长 120 字符）
     */
    private static final Pattern LIFE_FANS_PAIR_ID_PATTERN = Pattern.compile("^(store|user)_[^\\s]{1,120}$");
    private static final int FAN_IDS_MAX_CHARS = 160;

    /** 商家预订：待支付 / 待使用 / 商家预订 */
    private static final List<Integer> PENDING_RESERVATION_ORDER_STATUSES =
            Arrays.asList(0, 1, 8);

    /** 二手交易：待确认 / 待交易 */
    private static final List<Integer> PENDING_SECOND_TRADE_STATUSES = Arrays.asList(1, 3);

    private static final String CANCEL_MSG_PENDING_RESERVATION =
            "您有待消费的商家预订，请完成消费或取消预订后再申请注销。";
    private static final String CANCEL_MSG_LISTED_SECOND_GOODS =
            "您有上架的二手商品，请确认下架后再申请注销。";
    private static final String CANCEL_MSG_PENDING_SECOND_TRADE =
            "您有进行中的二手订单，请确认收货或与对方协商取消后再申请注销。";
    private static final String CANCEL_MSG_CONSULT_IN_PROGRESS =
            "您有进行中的法律咨询，请与律师确认服务完结后再申请注销。";
    private static final String CANCEL_MSG_UNPAID_CONSULT_FEE =
            "您有未结清的咨询费用，请完成支付后再申请注销。";
    private static final String CANCEL_MSG_MULTIPLE_BLOCKERS =
            "您有多项待办事项未处理，处理完成后即可申请注销。";
    private static final String CANCEL_REVOKE_NOT_IN_COOLING_MSG =
            "当前账号不在注销冷静期内，无法撤销";

    /** C 端用户注销冷静期：logout_flag=2 */
    private static final int LIFE_USER_LOGOUT_FLAG_COOLING = 2;

    private static final int LOGOUT_COOLING_DAYS = 7;

    private final LifeUserMapper lifeUserMapper;

    private final LifeFansMapper lifeFansMapper;

    private final LifeUserDynamicsMapper lifeUserDynamicsMapper;

    private final LifeDynamicsIdentityHelper dynamicsIdentityHelper;

    private final BaseRedisService baseRedisService;

    private final LifeNoticeMapper lifeNoticeMapper;

    private final LifeNoticeUtil lifeNoticeUtil;

    private final LifeMessageMapper messageMapper;

    private final WebSocketProcess webSocketProcess;

    private final ActivityInviteConfigService activityInviteConfigService;

    private final LifeUserLogMapper lifeUserLogMapper;

    private final SecondServiceFeign alienSecondFeign;

    private final SecondUserCreditMapper secondUserCreditMapper;

    private final SecondRiskControlRecordMapper secondRiskControlRecordMapper;

    private final LifeUserPersonalizationSettingService lifeUserPersonalizationSettingService;

    private final LifeUserPushDeviceService lifeUserPushDeviceService;

    private final UniPushCloudInvokeService uniPushCloudInvokeService;

    private final StoreUserMapper storeUserMapper;

    private final DynamicsRecommendCacheService dynamicsRecommendCacheService;

    private final ClockInRecommendCacheService clockInRecommendCacheService;

    private final JdbcTemplate jdbcTemplate;

    private final TypeUtil typeUtil;

    private final UserReservationOrderMapper userReservationOrderMapper;

    private final SecondGoodsMapper secondGoodsMapper;

    private final SecondTradeRecordMapper secondTradeRecordMapper;

    private final LawyerConsultationOrderMapper lawyerConsultationOrderMapper;

    @Autowired
    private RiskControlProperties riskControlProperties;

    @Value("${jwt.expiration-time}")
    private String effectiveTime;

    /**
     * 系统app通知开关
     */
    @Value("${alien.unipush.enabled:false}")
    private boolean uniPushOn;

    public IPage<LifeUser> getStoresPage(int page, int size, String realName, String userPhone) {
        IPage<LifeUser> storePage = new Page<>(page, size);
        QueryWrapper<LifeUser> queryWrapper = new QueryWrapper<>();
        queryWrapper.like(!realName.isEmpty(), "real_name", realName);
        queryWrapper.like(!userPhone.isEmpty(), "user_phone", userPhone);
        queryWrapper.orderByDesc("created_time");
        return lifeUserMapper.selectPage(storePage, queryWrapper);
    }

    public int modifyUser(LifeUser user) {
        return lifeUserMapper.updateById(user);
    }

    public int addOrUpdateUser(LifeUser user) {
        user.setCreatedTime(new Date());
        return lifeUserMapper.insert(user);
    }

    public LifeUser getUserById(String id) {
        return lifeUserMapper.selectById(id);
    }

    @Transactional(rollbackFor = Exception.class)
    public LifeFansFollowOutcome addFans(LifeFans fans) {
        Optional<String> paramError = validateAndNormalizeLifeFansPair(fans);
        if (paramError.isPresent()) {
            return LifeFansFollowOutcome.failure(paramError.get());
        }

        try {
            fans.setCreatedTime(new Date());
            LifeFansIdentityQuery.fillIdentityOnFans(fans, typeUtil);
            Optional<String> identityError = LifeFansIdentityQuery.validatePairIdentity(fans);
            if (identityError.isPresent()) {
                return LifeFansFollowOutcome.failure(identityError.get());
            }

            Integer followedType = fans.getFollowedUserType();
            Integer followedId = fans.getFollowedRefId();
            Integer fansType = fans.getFansUserType();
            Integer fansId = fans.getFansRefId();
            LambdaQueryWrapper<LifeFans> activeQ = new LambdaQueryWrapper<>();
            activeQ.eq(LifeFans::getFollowedUserType, followedType)
                    .eq(LifeFans::getFollowedRefId, followedId)
                    .eq(LifeFans::getFansUserType, fansType)
                    .eq(LifeFans::getFansRefId, fansId)
                    .last("LIMIT 1");
            LifeFans active = lifeFansMapper.selectOne(activeQ);
            if (active != null) {
                return LifeFansFollowOutcome.success("您已关注，无需重复操作");
            }

            int revived = reviveDeletedFollowRow(followedType, followedId, fansType, fansId);
            if (revived > 0) {
                sendFollowNoticeIfAllowed(fans);
                notifyRecommendFeedCachesFollow(fans, true);
                return LifeFansFollowOutcome.success("关注成功");
            }

            int num = lifeFansMapper.insert(fans);
            if (num == 1) {
                sendFollowNoticeIfAllowed(fans);
                notifyRecommendFeedCachesFollow(fans, true);
                return LifeFansFollowOutcome.success("关注成功");
            }
            return LifeFansFollowOutcome.failure("关注失败：数据未保存成功，请稍后重试");
        } catch (DuplicateKeyException ex) {
            log.debug("addFans duplicate key (concurrent), followedId={} fansId={}", fans.getFollowedId(), fans.getFansId());
            return LifeFansFollowOutcome.success("关注成功");
        } catch (DataIntegrityViolationException ex) {
            if (isDuplicateKeyMessage(ex)) {
                return LifeFansFollowOutcome.success("关注成功");
            }
            log.warn("addFans 数据约束异常 followedId={} fansId={} err={}",
                    fans.getFollowedId(), fans.getFansId(), ex.getMessage());
            return LifeFansFollowOutcome.failure("关注失败：数据校验未通过，请检查参数或稍后再试");
        } catch (Exception ex) {
            log.error("addFans 未预期异常 followedId={} fansId={}", fans.getFollowedId(), fans.getFansId(), ex);
            return LifeFansFollowOutcome.failure("关注失败：系统繁忙，请稍后重试");
        }
    }

    private static boolean isDuplicateKeyMessage(DataIntegrityViolationException ex) {
        Throwable t = ex.getMostSpecificCause();
        String a = StringUtils.trimToEmpty(t != null ? t.getMessage() : "");
        String b = StringUtils.trimToEmpty(ex.getMessage());
        String m = (a + " | " + b).toLowerCase(Locale.ROOT);
        return m.contains("duplicate") || m.contains("unique") || m.contains("uk_life_fans_follow_pair")
                || m.contains("uk_life_fans_type_ref_pair");
    }

    /**
     * 校验并规范化一组关注标识；有错返回错误文案。
     */
    private Optional<String> validateAndNormalizeLifeFansPair(LifeFans fans) {
        if (fans == null) {
            return Optional.of("请求参数不能为空：请传入被关注方与粉丝标识");
        }
        String fid = StringUtils.trimToEmpty(fans.getFollowedId());
        String sid = StringUtils.trimToEmpty(fans.getFansId());

        fans.setFollowedId(normalizeFansPhoneId(fid));
        fans.setFansId(normalizeFansPhoneId(sid));
        fid = fans.getFollowedId();
        sid = fans.getFansId();

        if (StringUtils.isBlank(fid)) {
            return Optional.of("被关注方标识（followedId）不能为空");
        }
        if (StringUtils.isBlank(sid)) {
            return Optional.of("粉丝方标识（fansId）不能为空");
        }
        if (fid.length() > FAN_IDS_MAX_CHARS || sid.length() > FAN_IDS_MAX_CHARS) {
            return Optional.of("关注标识过长（单字段不超过 " + FAN_IDS_MAX_CHARS + " 字符）");
        }
        Optional<String> fErr = assertValidFanPartyId("被关注方标识（followedId）", fid);
        if (fErr.isPresent()) {
            return fErr;
        }
        Optional<String> sErr = assertValidFanPartyId("粉丝方标识（fansId）", sid);
        if (sErr.isPresent()) {
            return sErr;
        }
        if (fid.equals(sid)) {
            return Optional.of("无效操作：粉丝方与被关注方不能相同");
        }
        return Optional.empty();
    }

    private static String normalizeFansPhoneId(String raw) {
        int u = raw.indexOf('_');
        if (u <= 0) {
            return raw;
        }
        String prefix = raw.substring(0, u).toLowerCase(Locale.ROOT);
        return prefix + raw.substring(u);
    }

    private static Optional<String> assertValidFanPartyId(String label, String value) {
        if (!LIFE_FANS_PAIR_ID_PATTERN.matcher(value).matches()) {
            return Optional.of(label + "格式错误：须为 store_ 或 user_ 开头，且整体不能包含空格");
        }
        return Optional.empty();
    }

    /**
     * 绕过 MyBatis-Plus 逻辑删除插件对 Mapper 手写 UPDATE 的条件注入风险，直接恢复一条软删的关注记录。
     */
    private int reviveDeletedFollowRow(Integer followedType, Integer followedId, Integer fansType, Integer fansId) {
        return jdbcTemplate.update(
                "UPDATE life_fans SET delete_flag = 0 WHERE followed_user_type = ? AND followed_ref_id = ? AND fans_user_type = ? AND fans_ref_id = ? AND delete_flag = 1 LIMIT 1",
                followedType,
                followedId,
                fansType,
                fansId);
    }

    /**
     * 仅在数据库关注关系已成功写入/删除之后调用：增量同步推荐 Redis（动态 + 打卡）。
     * 与业务返回值、事务无关；任一缓存同步失败仅打日志，不反向影响 DB。
     */
    private void notifyRecommendFeedCachesFollow(LifeFans fans, boolean followed) {
        try {
            dynamicsRecommendCacheService.onFollowRelationChanged(fans, followed);
        } catch (Exception e) {
            log.error("推荐动态缓存同步失败(关注)，不影响主流程 fansId={} followedId={}",
                    fans != null ? fans.getFansId() : null,
                    fans != null ? fans.getFollowedId() : null,
                    e);
        }
        try {
            clockInRecommendCacheService.onFollowRelationChanged(fans, followed);
        } catch (Exception e) {
            log.error("推荐打卡缓存同步失败(关注)，不影响主流程 fansId={} followedId={}",
                    fans != null ? fans.getFansId() : null,
                    fans != null ? fans.getFollowedId() : null,
                    e);
        }
    }

    private void sendFollowNoticeIfAllowed(LifeFans fans) {
        LifeNotice notice = new LifeNotice();
        notice.setTitle("关注通知");
        notice.setContext("关注了你");
        notice.setNoticeType(0);
        notice.setReceiverId(fans.getFollowedId());
        notice.setSenderId(fans.getFansId());
        notice.setSenderUserType(fans.getFansUserType());
        notice.setSenderRefId(fans.getFansRefId());
        notice.setReceiverUserType(fans.getFollowedUserType());
        notice.setReceiverRefId(fans.getFollowedRefId());

        String storePhones = "''";
        String userPhones = "''";
        String sid = fans.getFansId();
        String[] fp = sid.split("_", 2);
        if (fp.length >= 2 && StringUtils.isNotBlank(fp[1])) {
            if ("store".equalsIgnoreCase(fp[0])) {
                storePhones = "'" + fp[1] + "'";
            } else if ("user".equalsIgnoreCase(fp[0])) {
                userPhones = "'" + fp[1] + "'";
            }
        } else {
            log.warn("fansId 无法解析手机号段，跳过业务 id 回填，fansId={}", sid);
        }
        List<LifeMessageVo> userList = messageMapper.getLifeUserAndStoreUserByPhone(storePhones, userPhones);
        if (!CollectionUtils.isEmpty(userList)) {
            notice.setBusinessId(userList.get(0).getId());
        }
        Integer followedLifeUserId = resolveLifeUserIdFromFollowedId(fans.getFollowedId());
        boolean suppressNotice = followedLifeUserId != null
                && lifeUserPersonalizationSettingService.shouldSuppressFollowRelatedNotice(followedLifeUserId);
        if (!suppressNotice) {
            lifeNoticeUtil.fillUserTypeAndRefId(notice);
            lifeNoticeMapper.insert(notice);

            if (uniPushOn) {
                try {
                    sendFollowRelationAppPush(fans.getFollowedId());
                } catch (Exception e) {
                    log.warn("关注 App 推送失败，followedId={}，err={}", fans.getFollowedId(), e.getMessage());
                }
            }
        }
    }

    /**
     * 关注站内通知写入成功后，向被关注方已绑定 cid 的设备发 App 系统消息；与站内信条件一致，失败不影响关注与通知入库。
     */
    private void sendFollowRelationAppPush(String followedId) {
        if (StringUtils.isBlank(followedId)) {
            return;
        }
        List<String> cids = resolvePushClientIdsForFollowReceiver(followedId.trim());
        if (CollectionUtils.isEmpty(cids)) {
            return;
        }
        Map<String, Object> payload = new HashMap<>(8);
        payload.put("scene", "follow");
        payload.put("noticeType", 0);
        payload.put("path", FOLLOW_APP_PUSH_OPEN_PATH);
        uniPushCloudInvokeService.sendToClientIds(cids, "关注通知", "关注了你", payload);
    }

    private List<String> resolvePushClientIdsForFollowReceiver(String phoneId) {
        if (phoneId.startsWith("user_")) {
            String phone = phoneId.substring("user_".length());
            if (StringUtils.isBlank(phone)) {
                return Collections.emptyList();
            }
            LifeUser u = lifeUserMapper.selectOne(new LambdaQueryWrapper<LifeUser>()
                    .eq(LifeUser::getUserPhone, phone)
                    .eq(LifeUser::getDeleteFlag, 0)
                    .last("LIMIT 1"));
            if (u == null) {
                return Collections.emptyList();
            }
            return lifeUserPushDeviceService.listPushClientIdsByUserId(u.getId(), PushDeviceOwnerType.USER.getCode());
        }
        if (phoneId.startsWith("store_")) {
            String phone = phoneId.substring("store_".length());
            if (StringUtils.isBlank(phone)) {
                return Collections.emptyList();
            }
            StoreUser su = storeUserMapper.selectOne(new LambdaQueryWrapper<StoreUser>()
                    .eq(StoreUser::getPhone, phone)
                    .eq(StoreUser::getDeleteFlag, 0)
                    .last("LIMIT 1"));
            if (su == null) {
                return Collections.emptyList();
            }
            return lifeUserPushDeviceService.listPushClientIdsByUserId(su.getId(), PushDeviceOwnerType.STORE.getCode());
        }
        return Collections.emptyList();
    }

    /**
     * followedId 为 user_手机号 时解析为 life_user.id；店铺等非用户被关注方返回 null（不读个性化表，照常发通知）
     */
    private Integer resolveLifeUserIdFromFollowedId(String followedId) {
        if (StringUtils.isBlank(followedId) || !followedId.startsWith("user_")) {
            return null;
        }
        String phone = followedId.substring("user_".length());
        if (StringUtils.isBlank(phone)) {
            return null;
        }
        LifeUser u = lifeUserMapper.selectOne(new LambdaQueryWrapper<LifeUser>()
                .eq(LifeUser::getUserPhone, phone)
                .eq(LifeUser::getDeleteFlag, 0)
                .last("LIMIT 1"));
        return u != null ? u.getId() : null;
    }

    @Transactional(rollbackFor = Exception.class)
    public LifeFansFollowOutcome cancelFans(LifeFans fans) {
        Optional<String> paramError = validateAndNormalizeLifeFansPair(fans);
        if (paramError.isPresent()) {
            return LifeFansFollowOutcome.failure(paramError.get());
        }
        try {
            LifeFansIdentityQuery.fillIdentityOnFans(fans, typeUtil);
            Optional<String> identityError = LifeFansIdentityQuery.validatePairIdentity(fans);
            if (identityError.isPresent()) {
                return LifeFansFollowOutcome.failure(identityError.get());
            }
            LambdaUpdateWrapper<LifeFans> wrapper = new LambdaUpdateWrapper<>();
            LifeFansIdentityQuery.applyFansSide(wrapper, LifeFansIdentityQuery.resolve(fans.getFansId(), typeUtil));
            LifeFansIdentityQuery.applyFollowedSide(wrapper, LifeFansIdentityQuery.resolve(fans.getFollowedId(), typeUtil));
            int n = lifeFansMapper.delete(wrapper);
            if (n <= 0) {
                return LifeFansFollowOutcome.success("当前未关注或已取消，无需重复操作");
            }
            notifyRecommendFeedCachesFollow(fans, false);
            return LifeFansFollowOutcome.success("取消关注成功");
        } catch (Exception ex) {
            log.error("cancelFans 失败 followedId={} fansId={}", fans.getFollowedId(), fans.getFansId(), ex);
            return LifeFansFollowOutcome.failure("取消关注失败：系统繁忙，请稍后重试");
        }
    }

    public IPage<LifeUserVo> getStoreAndUserByName(LifeUserVo vo) {
        QueryWrapper<LifeUserVo> queryWrapper = new QueryWrapper<>();
        if (StringUtils.isNotEmpty(vo.getSearchName())) {
            queryWrapper.and(wrapper -> wrapper
                    .like("a.storeUserName", vo.getSearchName())
                    .or()
                    .like("a.phoneId", vo.getSearchName())
                    .or()
                    .like("a.accountName", vo.getSearchName())
            );
        }
        IPage<LifeUserVo> voList = lifeUserMapper.getStoreAndUserByName(new Page<>(vo.getPage(), vo.getSize()), queryWrapper);

        LambdaQueryWrapper<LifeFans> lifeFansWrapper = new LambdaQueryWrapper<>();
        LifeFansIdentityQuery.applyFansSide(lifeFansWrapper, LifeFansIdentityQuery.resolve(vo.getPhoneId(), typeUtil));
        List<LifeFans> lifeFansList = lifeFansMapper.selectList(lifeFansWrapper);
        List<String> followList = lifeFansList.stream().map(LifeFans::getFollowedId).collect(Collectors.toList());

        voList.getRecords().forEach(item -> {
            if (followList.contains(item.getPhoneId())) {
                item.setIsFollowThis("1");
            } else {
                item.setIsFollowThis("0");
            }
        });
        return voList;
    }

    public LifeUser getUserByPhone(String phoneNum) {
        LambdaQueryWrapper<LifeUser> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(LifeUser::getUserPhone, phoneNum);
        lambdaQueryWrapper.in(LifeUser::getLogoutFlag,0, 2);
        lambdaQueryWrapper.orderByDesc(LifeUser::getCreatedTime).orderByDesc(LifeUser::getId);
        lambdaQueryWrapper.last("LIMIT 1");
        List<LifeUser> users = this.list(lambdaQueryWrapper);
        LifeUser user = users.isEmpty() ? null : users.get(0);
        if (user != null) {
            String userId = "user_" + phoneNum;
            LifeFansIdentityQuery.Scope userScope = new LifeFansIdentityQuery.Scope(userId, 1, user.getId());
            LambdaQueryWrapper<LifeFans> followWrapper = new LambdaQueryWrapper<>();
            followWrapper.eq(LifeFans::getDeleteFlag, 0);
            LifeFansIdentityQuery.applyFansSide(followWrapper, userScope);
            long followCount = lifeFansMapper.selectCount(followWrapper);
            user.setFollowCount((int) followCount);

            LambdaQueryWrapper<LifeFans> fansWrapper = new LambdaQueryWrapper<>();
            fansWrapper.eq(LifeFans::getDeleteFlag, 0);
            LifeFansIdentityQuery.applyFollowedSide(fansWrapper, userScope);
            long fansCount = lifeFansMapper.selectCount(fansWrapper);
            user.setFansCount((int) fansCount);

            LambdaQueryWrapper<LifeUserDynamics> dynamicsWrapper = new LambdaQueryWrapper<>();
            dynamicsIdentityHelper.applyPublisherFilter(dynamicsWrapper,
                    dynamicsIdentityHelper.resolveFromPhoneId(userId));
            dynamicsWrapper.eq(LifeUserDynamics::getDeleteFlag, 0);
            List<LifeUserDynamics> dynamicsList = lifeUserDynamicsMapper.selectList(dynamicsWrapper);
            long likeCount = 0L;
            if (dynamicsList != null && !dynamicsList.isEmpty()) {
                likeCount = dynamicsList.stream()
                        .mapToLong(d -> d.getDianzanCount() != null ? d.getDianzanCount() : 0)
                        .sum();
            }
            user.setLikeCount((int) likeCount);
        }
        return user;
    }

    public void liftCancelAccount(LifeUser user) {
        if (user == null || user.getId() == null) {
            throw new IllegalArgumentException("用户id不能为空");
        }
        LifeUser lifeUser = lifeUserMapper.selectById(user.getId());
        if (lifeUser == null) {
            throw new IllegalArgumentException("用户不存在");
        }
        if (lifeUser.getLogoutFlag() != null && lifeUser.getLogoutFlag() == 2) {
            throw new IllegalArgumentException("账号已在注销中");
        }
        validateCancelAccountBlockers(user.getId());
        // 设置成2，注销冷静期
        lifeUser.setLogoutFlag(2);
        lifeUser.setLogoutReason(user.getLogoutReason());
        lifeUser.setLogoutContext(user.getLogoutContext());
        lifeUser.setLogoutTime(new Date());
        lifeUserMapper.updateById(lifeUser);
    }

    /**
     * 注销前业务校验：商家预订、二手上架、二手待确认/待交易、咨询进行中、咨询费用未结清。
     * 单项不满足返回对应提示；多项不满足返回汇总提示。
     */
    private void validateCancelAccountBlockers(Integer userId) {
        List<String> blockers = new ArrayList<>();

        long pendingReservationCount = userReservationOrderMapper.selectCount(
                new LambdaQueryWrapper<UserReservationOrder>()
                        .eq(UserReservationOrder::getUserId, userId)
                        .in(UserReservationOrder::getOrderStatus, PENDING_RESERVATION_ORDER_STATUSES));
        if (pendingReservationCount > 0) {
            blockers.add(CANCEL_MSG_PENDING_RESERVATION);
        }

        long listedGoodsCount = secondGoodsMapper.selectCount(
                new LambdaQueryWrapper<SecondGoods>()
                        .eq(SecondGoods::getUserId, userId)
                        .eq(SecondGoods::getGoodsStatus, SecondGoodsStatusEnum.LISTED.getCode()));
        if (listedGoodsCount > 0) {
            blockers.add(CANCEL_MSG_LISTED_SECOND_GOODS);
        }

        long pendingSecondTradeCount = secondTradeRecordMapper.selectCount(
                new LambdaQueryWrapper<SecondTradeRecord>()
                        .in(SecondTradeRecord::getTradeStatus, PENDING_SECOND_TRADE_STATUSES)
                        .and(w -> w.eq(SecondTradeRecord::getBuyerId, userId)
                                .or()
                                .eq(SecondTradeRecord::getSellerId, userId)));
        if (pendingSecondTradeCount > 0) {
            blockers.add(CANCEL_MSG_PENDING_SECOND_TRADE);
        }

        long inProgressConsultCount = lawyerConsultationOrderMapper.selectCount(
                new LambdaQueryWrapper<LawyerConsultationOrder>()
                        .eq(LawyerConsultationOrder::getClientUserId, userId)
                        .in(LawyerConsultationOrder::getOrderStatus,
                                LawyerStatusEnum.WAIT_ACCEPT.getStatus(),
                                LawyerStatusEnum.INPROGRESS.getStatus()));
        if (inProgressConsultCount > 0) {
            blockers.add(CANCEL_MSG_CONSULT_IN_PROGRESS);
        }

        long unpaidConsultCount = lawyerConsultationOrderMapper.selectCount(
                new LambdaQueryWrapper<LawyerConsultationOrder>()
                        .eq(LawyerConsultationOrder::getClientUserId, userId)
                        .eq(LawyerConsultationOrder::getPaymentStatus, 0)
                        .notIn(LawyerConsultationOrder::getOrderStatus,
                                LawyerStatusEnum.CANCEL.getStatus(),
                                LawyerStatusEnum.REFUNDED.getStatus(),LawyerStatusEnum.COMPLETE.getStatus()));
        if (unpaidConsultCount > 0) {
            blockers.add(CANCEL_MSG_UNPAID_CONSULT_FEE);
        }

        if (blockers.isEmpty()) {
            return;
        }
        if (blockers.size() == 1) {
            throw new IllegalArgumentException(blockers.get(0));
        }
        throw new IllegalArgumentException(CANCEL_MSG_MULTIPLE_BLOCKERS);
    }

    public void liftCancelAccountUn(Integer id) {
        if (id == null) {
            throw new IllegalArgumentException("用户id不能为空");
        }
        LifeUser lifeUser = lifeUserMapper.selectById(id);
        if (lifeUser == null) {
            throw new IllegalArgumentException("用户不存在");
        }
        boolean logoutFlagCooling = lifeUser.getLogoutFlag() != null
                && lifeUser.getLogoutFlag() == LIFE_USER_LOGOUT_FLAG_COOLING;
        if (!logoutFlagCooling && !isLifeUserInLogoutCoolingPeriod(lifeUser)) {
            throw new IllegalArgumentException(CANCEL_REVOKE_NOT_IN_COOLING_MSG);
        }
        lifeUserMapper.update(null, new LambdaUpdateWrapper<LifeUser>()
                .eq(LifeUser::getId, id)
                .set(LifeUser::getLogoutFlag, LifeUser.LOGOUT_FLAG_NORMAL)
                .set(LifeUser::getLogoutReason, null)
                .set(LifeUser::getLogoutContext, null)
                .set(LifeUser::getLogoutTime, null));
    }

    /**
     * 是否在注销冷静期内（logoutTime 起 7 天内，含第 7 天当日）。
     */
    private static boolean isLifeUserInLogoutCoolingPeriod(LifeUser lifeUser) {
        if (lifeUser == null || lifeUser.getLogoutTime() == null) {
            return false;
        }
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(lifeUser.getLogoutTime());
        calendar.add(Calendar.DAY_OF_YEAR, LOGOUT_COOLING_DAYS);
        return !new Date().after(calendar.getTime());
    }

    public List<String> getIds(String name, String phone) {
        if (Objects.isNull(name) && Objects.isNull(phone)) return Arrays.asList("");
        LambdaQueryWrapper<LifeUser> wrapper = new LambdaQueryWrapper<>();
        List<Triple<Boolean, SFunction<LifeUser, ?>, Object>> conditions = new ArrayList<>();
        conditions.add(Triple.of(StringUtils.isNotEmpty(name), LifeUser::getUserName, name));
        conditions.add(Triple.of(StringUtils.isNotEmpty(phone), LifeUser::getUserPhone, phone));
        FunctionMagic.buildLikeConditions(wrapper, conditions);
        List<String> res = FunctionMagic.convertToIds(lifeUserMapper.selectList(wrapper), LifeUser::getId);
        return CollectionUtils.isEmpty(res) ? Arrays.asList("") : res;
    }

    public boolean checkRealInfo(LifeUser user) {
        LifeUser lifeUser = lifeUserMapper.selectById(user.getId());
        if (lifeUser != null) {
            String realName = user.getRealName();
            String idCard = user.getIdCard();
            if ((realName == null && lifeUser.getRealName() == null) || (idCard == null && lifeUser.getIdCard() == null)) {
                return false;
            }
            return (StringUtils.isNotEmpty(realName) && !realName.equals(lifeUser.getRealName())) || (StringUtils.isNotEmpty(idCard) && !idCard.equals(lifeUser.getIdCard()));
        }
        return false;
    }

    public void sendNotice(LifeUser user) {
        String receiverId = "user_" + user.getUserPhone();
        LifeNotice lifeNotice = new LifeNotice();
        lifeNotice.setReceiverId(receiverId);

        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String storeDate = simpleDateFormat.format(new Date());
        String text = "您在" + storeDate + "实名认证成功。";
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("message", text);
        lifeNotice.setContext(jsonObject.toJSONString());
        lifeNotice.setTitle("实名认证通知");
        lifeNotice.setSenderId("system");
        lifeNotice.setIsRead(0);
        lifeNotice.setNoticeType(1);
        lifeNoticeUtil.fillUserTypeAndRefId(lifeNotice);
        lifeNoticeMapper.insert(lifeNotice);

        WebSocketVo websocketVo = new WebSocketVo();
        websocketVo.setSenderId("system");
        websocketVo.setReceiverId(receiverId);
        websocketVo.setCategory("notice");
        websocketVo.setNoticeType("1");
        websocketVo.setIsRead(0);
        websocketVo.setText(JSONObject.from(lifeNotice).toJSONString());
        try {
            webSocketProcess.sendMessage(receiverId, JSONObject.from(websocketVo).toJSONString());
        } catch (Exception e) {
            log.error("LifeUserService webSocketProcess Stack={}", e);
        }
    }

    @Transactional
    public void addLifeUserLogInfo(LifeUser user, String macIp) {
        try {
            LifeUserLog lifeUserLog = new LifeUserLog();
            lifeUserLog.setUserId(user.getId());
            lifeUserLog.setUserName(user.getUserName());
            lifeUserLog.setMacIp(macIp);
            lifeUserLog.setCreatedTime(new Date());
            int count = lifeUserLogMapper.insert(lifeUserLog);
            if (count > 0) {
                String startDate = LocalDateTime.now().minusHours(24L).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
                String endDate = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
                // 使用一个新的事务来查询，确保能看到刚才插入的数据
                List<LifeUserLog> lsit = getLifeUserLogByDateInNewTransaction(startDate, endDate, macIp);

                if (lsit.size() > riskControlProperties.getAccountAbnormal().getRegCount24h() && !isViolation(startDate, endDate, macIp, user.getId())) {
                    String detailInfo = lsit.stream()
                            .map(row -> row.getUserId().toString())
                            .collect(Collectors.joining(","));
                    alienSecondFeign.recordRiskControlData(user.getId(), 2, "账号异常", macIp, detailInfo);
                }
            }
            // 第一次登录，添加用户基础积分
            alienSecondFeign.createPointsRecord(user.getId(), 300, 1);
        } catch (Exception e) {
            log.error("用户登录log存放异常：{}", e);
        }
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public List<LifeUserLog> getLifeUserLogByDateInNewTransaction(String startDate, String endDate, String macIp) {
        return lifeUserLogMapper.getLifeUserLogByDate(startDate, endDate, macIp);
    }

    public boolean isViolation(String startDate, String endDate, String macIp, Integer userId) {
        List<SecondRiskControlRecord> list = secondRiskControlRecordMapper.selectByBusinessId(startDate, endDate, macIp);
        for (SecondRiskControlRecord record : list) {
            // 将数组转换为包含整数的列表
            List<Integer> userIdList = Arrays.stream(record.getDetailInfo().split(","))
                    .map(Integer::parseInt)
                    .collect(Collectors.toList());
            if (userIdList.contains(userId)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 统计用户设备总数（life_user.device_id 逗号拼接，按段计数求和）
     */
    public Long countUserDeviceNum() {
        Long count = lifeUserMapper.countTotalDeviceNum();
        return count == null ? 0L : count;
    }
}
