package com.example.demo.service;

import java.time.LocalDateTime;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.context.annotation.Lazy;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.example.demo.config.CacheConfig;
import com.example.demo.model.PointsFreezeRecord;
import com.example.demo.model.User;
import com.example.demo.model.MembershipLevel;
import com.example.demo.model.enums.CommonTaskType;
import com.example.demo.repository.PointsFreezeRecordRepository;
import com.example.demo.repository.UserRepository;
import com.example.demo.repository.MembershipLevelRepository;
import com.example.demo.repository.UserMembershipRepository;

import java.util.Optional;
import com.example.demo.util.UserIdGenerator;
import com.example.demo.model.UserMembership;

@Service
public class UserService {

    private static final Logger logger = LoggerFactory.getLogger(UserService.class);

    private final UserRepository userRepository;
    private final PasswordEncoder passwordEncoder;
    private final PointsFreezeRecordRepository pointsFreezeRecordRepository;
    private final com.example.demo.repository.OrderRepository orderRepository;
    private final com.example.demo.repository.PaymentRepository paymentRepository;
    private final CacheManager cacheManager;
    private final MembershipLevelRepository membershipLevelRepository;
    private final UserMembershipRepository userMembershipRepository;
    private final org.springframework.jdbc.core.JdbcTemplate jdbcTemplate;

    public UserService(UserRepository userRepository, @Lazy PasswordEncoder passwordEncoder, 
                      PointsFreezeRecordRepository pointsFreezeRecordRepository,
                      com.example.demo.repository.OrderRepository orderRepository,
                      com.example.demo.repository.PaymentRepository paymentRepository,
                      CacheManager cacheManager,
                      MembershipLevelRepository membershipLevelRepository,
                      UserMembershipRepository userMembershipRepository,
                      org.springframework.jdbc.core.JdbcTemplate jdbcTemplate) {
        this.userRepository = userRepository;
        this.passwordEncoder = passwordEncoder;
        this.pointsFreezeRecordRepository = pointsFreezeRecordRepository;
        this.orderRepository = orderRepository;
        this.paymentRepository = paymentRepository;
        this.cacheManager = cacheManager;
        this.membershipLevelRepository = membershipLevelRepository;
        this.userMembershipRepository = userMembershipRepository;
        this.jdbcTemplate = jdbcTemplate;
    }

    @Transactional
    public User register(String username, String email, String rawPassword) {
        if (userRepository.existsByUsername(username)) {
            throw new IllegalArgumentException("用户名已存在");
        }
        if (userRepository.existsByEmail(email)) {
            throw new IllegalArgumentException("邮箱已被使用");
        }
        User user = new User();
        // 生成唯一用户ID，重试最多10次确保唯一性
        String userId = null;
        for (int i = 0; i < 10; i++) {
            userId = UserIdGenerator.generate();
            if (!userRepository.existsByUserId(userId)) {
                break;
            }
            logger.warn("用户ID冲突，重新生成: userId={}", userId);
        }
        user.setUserId(userId);
        user.setUsername(username);
        user.setEmail(email);
        user.setPasswordHash(passwordEncoder.encode(rawPassword));
        // 注册时默认为普通用户
        user.setRole("ROLE_USER");
        User savedUser = userRepository.save(user);
        
        // 自动创建默认会员记录（标准会员，到期时间为1年后）
        createDefaultMembership(savedUser);
        
        return savedUser;
    }
    
    /**
     * 为新用户创建默认会员记录
     */
    private void createDefaultMembership(User user) {
        try {
            // 查找免费会员等级（新用户注册默认）
            Optional<MembershipLevel> freeLevel = membershipLevelRepository.findByName("free");
            if (freeLevel.isEmpty()) {
                logger.warn("未找到免费会员等级(free)，跳过创建会员记录");
                return;
            }
            
            UserMembership membership = new UserMembership();
            membership.setUserId(user.getId());
            membership.setMembershipLevelId(freeLevel.get().getId());
            membership.setStartDate(LocalDateTime.now());
            membership.setEndDate(LocalDateTime.of(2099, 12, 31, 23, 59, 59)); // 免费会员永久有效
            membership.setStatus("ACTIVE");
            membership.setAutoRenew(false);
            membership.setCreatedAt(LocalDateTime.now());
            
            userMembershipRepository.save(membership);
            logger.info("✅ 为新用户创建默认会员记录: userId={}, level=免费会员(永久有效)", user.getId());
        } catch (Exception e) {
            logger.error("创建默认会员记录失败: userId={}", user.getId(), e);
            // 不抛出异常，允许用户注册成功
        }
    }

    @Transactional(readOnly = true)
    public java.util.List<User> findAll() {
        return userRepository.findAll();
    }

    @Transactional(readOnly = true)
    public User findById(Long id) {
        return userRepository.findById(id).orElseThrow(() -> new IllegalArgumentException("用户不存在"));
    }

    @Cacheable(value = CacheConfig.USER_CACHE, key = "#username", unless = "#result == null")
    @Transactional(readOnly = true)
    public User findByUsername(String username) {
        return userRepository.findByUsername(username).orElseThrow(() -> new IllegalArgumentException("用户不存在"));
    }

    @Cacheable(value = CacheConfig.USER_CACHE, key = "#username", unless = "#result == null")
    @Transactional(readOnly = true)
    public User findByUsernameOrNull(String username) {
        return userRepository.findByUsername(username).orElse(null);
    }

    @Transactional(readOnly = true)
    public boolean existsByUserId(String userId) {
        return userRepository.existsByUserId(userId);
    }


    @Transactional
    public User create(String username, String email, String rawPassword) {
        return register(username, email, rawPassword);
    }

    @Transactional
    public User update(Long id, String username, String email, String rawPasswordNullable) {
        return update(id, username, email, rawPasswordNullable, null);
    }

    @Transactional
    public User update(Long id, String username, String email, String rawPasswordNullable, String role) {
        User user = findById(id);
        String oldUsername = user.getUsername();
        if (!oldUsername.equals(username) && userRepository.existsByUsername(username)) {
            throw new IllegalArgumentException("用户名已存在");
        }
        if (!user.getEmail().equals(email) && userRepository.existsByEmail(email)) {
            throw new IllegalArgumentException("邮箱已被使用");
        }
        user.setUsername(username);
        user.setEmail(email);
        if (role != null && !role.isBlank()) {
            user.setRole(role);
        }
        if (rawPasswordNullable != null && !rawPasswordNullable.isBlank()) {
            user.setPasswordHash(rawPasswordNullable);
        }
        User savedUser = userRepository.save(user);
        
        // 手动清除旧用户名和新用户名的缓存
        evictUserCache(oldUsername);
        if (!oldUsername.equals(username)) {
            evictUserCache(username);
            // 级联更新所有引用了 username 的关联表
            cascadeUpdateUsername(oldUsername, username);
        }
        
        return savedUser;
    }
    
    /**
     * 级联更新所有关联表中的 username 字段
     * 当用户修改用户名时，同步更新 7 张使用 username 字符串引用的表
     */
    private void cascadeUpdateUsername(String oldUsername, String newUsername) {
        String[] tables = {
            "task_queue", "task_status", "text_to_video_tasks",
            "image_to_video_tasks", "storyboard_video_tasks",
            "points_freeze_records", "user_error_log", "user_works"
        };
        for (String table : tables) {
            try {
                int rows = jdbcTemplate.update(
                    "UPDATE " + table + " SET username = ? WHERE username = ?",
                    newUsername, oldUsername);
                if (rows > 0) {
                    logger.info("级联更新 {} 表 username: {} -> {}，影响 {} 行", table, oldUsername, newUsername, rows);
                }
            } catch (Exception e) {
                logger.warn("级联更新 {} 表 username 失败: {}", table, e.getMessage());
            }
        }
    }
    
    /**
     * 手动清除用户缓存
     * 注意：使用 CacheManager 直接操作，避免同类内部调用 @CacheEvict 不生效的问题
     */
    public void evictUserCache(String username) {
        Cache cache = cacheManager.getCache(CacheConfig.USER_CACHE);
        if (cache != null && username != null) {
            cache.evict(username);
            logger.debug("清除用户缓存: {}", username);
        }
    }

    @Transactional
    public void delete(Long id) {
        // 先获取用户名用于清除缓存
        userRepository.findById(id).ifPresent(user -> {
            evictUserCache(user.getUsername());
        });
        userRepository.deleteById(id);
    }

    /**
     * 检查密码是否匹配（加密比较）
     */
    public boolean checkPassword(String rawPassword, String storedPassword) {
        return passwordEncoder.matches(rawPassword, storedPassword);
    }

    /**
     * 修改指定用户的密码
     *
     * 原密码为可选：
     * - 如果提供了原密码，则验证原密码是否正确
     * - 如果未提供原密码，则直接设置新密码
     */
    @Transactional
    public void changePassword(Long userId, String oldPassword, String newPassword) {
        User user = findById(userId);

        if (newPassword == null || newPassword.isBlank()) {
            throw new IllegalArgumentException("新密码不能为空");
        }
        if (newPassword.length() < 8) {
            throw new IllegalArgumentException("新密码长度不能少于8位");
        }
        // 验证密码必须包含英文字母和数字
        if (!newPassword.matches(".*[a-zA-Z].*")) {
            throw new IllegalArgumentException("新密码必须包含英文字母");
        }
        if (!newPassword.matches(".*[0-9].*")) {
            throw new IllegalArgumentException("新密码必须包含数字");
        }

        String currentPasswordHash = user.getPasswordHash();
        // 如果提供了原密码，则需要验证
        if (oldPassword != null && !oldPassword.isBlank()) {
            if (currentPasswordHash != null && !currentPasswordHash.isBlank()) {
                if (!checkPassword(oldPassword, currentPasswordHash)) {
                    throw new IllegalArgumentException("原密码不正确");
                }
            }
        }
        // 如果未提供原密码，直接设置新密码

        user.setPasswordHash(passwordEncoder.encode(newPassword));
        userRepository.save(user);
        evictUserCache(user.getUsername()); // 清除缓存
    }

    /**
     * 根据邮箱查找用户
     */
    @Transactional(readOnly = true)
    public User findByEmail(String email) {
        return userRepository.findByEmail(email).orElse(null);
    }

    @Transactional(readOnly = true)
    public User findByEmailOrNull(String email) {
        return userRepository.findByEmail(email).orElse(null);
    }

    /**
     * 根据手机号查找用户
     */
    @Transactional(readOnly = true)
    public User findByPhone(String phone) {
        return userRepository.findByPhone(phone).orElse(null);
    }

    /**
     * 保存用户
     */
    @Transactional
    public User save(User user) {
        User savedUser = userRepository.save(user);
        evictUserCache(user.getUsername()); // 清除缓存
        return savedUser;
    }

    /**
     * 更新用户活跃时间（不清除缓存，避免频繁清除影响性能）
     * 此方法专门用于 UserActivityInterceptor，仅更新 lastActiveTime 字段
     */
    @Transactional
    public void updateLastActiveTime(String username) {
        userRepository.findByUsername(username).ifPresent(user -> {
            user.setLastActiveTime(java.time.LocalDateTime.now());
            userRepository.save(user);
            // 注意：不清除缓存，因为 lastActiveTime 不是缓存的关键数据
        });
    }

    /**
     * 增加用户积分
     */
    @Transactional
    public User addPoints(Long userId, Integer points) {
        java.util.Objects.requireNonNull(userId, "用户ID不能为空");
        User user = userRepository.findById(userId)
            .orElseThrow(() -> new RuntimeException("用户不存在"));
        
        int newPoints = user.getPoints() + points;
        if (newPoints < 0) {
            throw new RuntimeException("积分不能为负数");
        }
        
        user.setPoints(newPoints);
        User savedUser = userRepository.save(user);
        evictUserCache(user.getUsername()); // 清除缓存
        return savedUser;
    }

    /**
     * 减少用户积分
     */
    @Transactional
    public User deductPoints(Long userId, Integer points) {
        User user = userRepository.findById(userId)
            .orElseThrow(() -> new RuntimeException("用户不存在"));
        
        int newPoints = user.getPoints() - points;
        if (newPoints < 0) {
            throw new RuntimeException("积分不足");
        }
        
        user.setPoints(newPoints);
        User savedUser = userRepository.save(user);
        evictUserCache(user.getUsername()); // 清除缓存
        return savedUser;
    }

    /**
     * 冻结用户积分
     * 修复：重试失败任务时需要重新冻结积分
     */
    @Transactional
    public PointsFreezeRecord freezePoints(String username, String taskId, CommonTaskType taskType, Integer points, String reason) {
        // 检查是否已经存在相同 taskId 的冻结记录
        Optional<PointsFreezeRecord> existingRecord = pointsFreezeRecordRepository.findByTaskId(taskId);
        if (existingRecord.isPresent()) {
            PointsFreezeRecord record = existingRecord.get();
            
            // 只有状态为 FROZEN 时才跳过（真正的重复冻结）
            if (record.getStatus() == PointsFreezeRecord.FreezeStatus.FROZEN) {
                logger.info("积分已冻结，跳过重复冻结: taskId={}, status={}", taskId, record.getStatus());
                return record;
            }
            
            // 如果状态是 DEDUCTED，说明任务已完成，不应该重试
            if (record.getStatus() == PointsFreezeRecord.FreezeStatus.DEDUCTED) {
                logger.warn("任务已完成并扣除积分，不允许重复冻结: taskId={}", taskId);
                throw new RuntimeException("任务已完成，不允许重复提交: " + taskId);
            }
            
            // 如果状态是 RETURNED/EXPIRED，说明是重试任务，需要重新冻结
            logger.info("检测到重试任务，旧记录状态: {}, 将创建新的冻结记录: taskId={}", 
                record.getStatus(), taskId);
            // 继续执行下面的冻结逻辑
        }
        
        User user = userRepository.findByUsername(username)
            .orElseThrow(() -> new RuntimeException("用户不存在"));
        
        // 检查可用积分是否足够
        if (user.getAvailablePoints() < points) {
            throw new RuntimeException("可用积分不足，当前可用积分: " + user.getAvailablePoints() + "，需要积分: " + points);
        }
        
        // 检查总积分是否足够（防止冻结积分过多导致总积分为负）
        if (user.getPoints() < points) {
            throw new RuntimeException("总积分不足，当前总积分: " + user.getPoints() + "，需要积分: " + points);
        }
        
        // 增加冻结积分
        user.setFrozenPoints(user.getFrozenPoints() + points);
        userRepository.save(user);
        evictUserCache(username); // 清除缓存
        
        // 创建新的冻结记录
        PointsFreezeRecord record = new PointsFreezeRecord(username, taskId, taskType, points, reason);
        record = pointsFreezeRecordRepository.save(record);
        
        logger.info("积分冻结成功: taskId={}, username={}, points={}, reason={}", 
            taskId, username, points, reason);
        
        return record;
    }

    /**
     * 扣除冻结的积分（任务完成）
     * 使用悲观锁防止并发重复扣除
     * 使用 REQUIRES_NEW 传播行为，确保在一个独立的事务中执行，
     * 这样即使抛出异常回滚，也不会影响调用者的事务（只要调用者捕获了异常）
     */
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void deductFrozenPoints(String taskId) {
        // 使用悲观写锁查询，防止并发重复扣除
        PointsFreezeRecord record = pointsFreezeRecordRepository.findByTaskIdWithLock(taskId)
            .orElseThrow(() -> new RuntimeException("找不到冻结记录: " + taskId));
        
        // 如果已经扣除过，直接返回，避免重复扣除（双重检查，悲观锁已经保证了并发安全）
        if (record.getStatus() == PointsFreezeRecord.FreezeStatus.DEDUCTED) {
            return;
        }
        
        if (record.getStatus() != PointsFreezeRecord.FreezeStatus.FROZEN) {
            throw new RuntimeException("冻结记录状态不正确: " + record.getStatus() + "，期望状态: FROZEN");
        }
        
        User user = userRepository.findByUsername(record.getUsername())
            .orElseThrow(() -> new RuntimeException("用户不存在"));
        
        // 减少总积分和冻结积分
        user.setPoints(user.getPoints() - record.getFreezePoints());
        user.setFrozenPoints(user.getFrozenPoints() - record.getFreezePoints());
        userRepository.save(user);
        evictUserCache(record.getUsername()); // 清除缓存
        
        // 更新冻结记录状态
        record.updateStatus(PointsFreezeRecord.FreezeStatus.DEDUCTED);
        pointsFreezeRecordRepository.save(record);
    }

    /**
     * 返还冻结的积分（任务失败）
     * 使用悲观锁防止并发重复返还
     * 使用 REQUIRES_NEW 传播行为，防止异常导致外部事务回滚
     * 如果积分已经返还或扣除，则静默返回（幂等操作）
     */
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void returnFrozenPoints(String taskId) {
        // 使用悲观写锁查询，防止并发重复返还
        // 注意：只查询 FROZEN 状态的记录，如果找不到说明已经处理过
        Optional<PointsFreezeRecord> recordOpt = pointsFreezeRecordRepository.findByTaskIdWithLock(taskId);
        
        if (recordOpt.isEmpty()) {
            // 找不到 FROZEN 状态的记录，说明已经返还或扣除过，静默返回（幂等操作）
            logger.debug("找不到 FROZEN 状态的冻结记录，可能已处理过: taskId={}", taskId);
            return;
        }
        
        PointsFreezeRecord record = recordOpt.get();
        
        // 双重检查：如果状态不是 FROZEN，说明已经处理过，静默返回（幂等操作）
        if (record.getStatus() != PointsFreezeRecord.FreezeStatus.FROZEN) {
            logger.debug("积分记录状态为 {}，跳过返还: taskId={}", record.getStatus(), taskId);
            return;
        }
        
        User user = userRepository.findByUsername(record.getUsername())
            .orElseThrow(() -> new RuntimeException("用户不存在"));
        
        // 减少冻结积分（总积分不变）
        user.setFrozenPoints(user.getFrozenPoints() - record.getFreezePoints());
        userRepository.save(user);
        evictUserCache(record.getUsername()); // 清除缓存
        
        // 更新冻结记录状态
        record.updateStatus(PointsFreezeRecord.FreezeStatus.RETURNED);
        pointsFreezeRecordRepository.save(record);
    }

    /**
     * 给用户增加积分
     */
    @Transactional
    public void addPoints(String username, Integer points) {
        User user = userRepository.findByUsername(username)
            .orElseThrow(() -> new RuntimeException("用户不存在: " + username));
        
        user.setPoints(user.getPoints() + points);
        userRepository.save(user);
        evictUserCache(username); // 清除缓存
    }

    /**
     * 设置用户积分
     */
    @Transactional
    public void setPoints(String username, Integer points) {
        User user = userRepository.findByUsername(username)
            .orElseThrow(() -> new RuntimeException("用户不存在: " + username));
        
        user.setPoints(points);
        userRepository.save(user);
        evictUserCache(username); // 清除缓存
    }

    /**
     * 获取用户可用积分
     */
    @Transactional(readOnly = true)
    public Integer getAvailablePoints(String username) {
        User user = userRepository.findByUsername(username)
            .orElseThrow(() -> new RuntimeException("用户不存在"));
        return user.getAvailablePoints();
    }

    /**
     * 获取用户冻结积分
     */
    @Transactional(readOnly = true)
    public Integer getFrozenPoints(String username) {
        User user = userRepository.findByUsername(username)
            .orElseThrow(() -> new RuntimeException("用户不存在"));
        return user.getFrozenPoints();
    }

    /**
     * 获取用户积分冻结记录
     */
    @Transactional(readOnly = true)
    public java.util.List<PointsFreezeRecord> getPointsFreezeRecords(String username) {
        return pointsFreezeRecordRepository.findByUsernameOrderByCreatedAtDesc(username);
    }

    /**
     * 处理过期的冻结记录
     */
    @Transactional
    public int processExpiredFrozenRecords() {
        java.time.LocalDateTime expiredTime = java.time.LocalDateTime.now().minusHours(24);
        java.util.List<PointsFreezeRecord> expiredRecords = pointsFreezeRecordRepository.findExpiredFrozenRecords(expiredTime);
        
        int processedCount = 0;
        for (PointsFreezeRecord record : expiredRecords) {
            try {
                // 返还过期冻结的积分
                returnFrozenPoints(record.getTaskId());
                processedCount++;
            } catch (Exception e) {
                logger.error("处理过期冻结记录失败: {}", record.getTaskId(), e);
            }
        }
        
        return processedCount;
    }

    /**
     * 获取用户积分
     */
    @Transactional(readOnly = true)
    public Integer getUserPoints(Long userId) {
        User user = userRepository.findById(userId)
            .orElseThrow(() -> new RuntimeException("用户不存在"));
        return user.getPoints();
    }

    /**
     * 获取积分使用历史（充值和使用记录）
     * 包括：订单充值记录和积分消耗记录
     */
    @Transactional(readOnly = true)
    public java.util.List<java.util.Map<String, Object>> getPointsHistory(String username, int page, int size) {
        User user = userRepository.findByUsername(username)
            .orElseThrow(() -> new RuntimeException("用户不存在"));
        
        java.util.List<java.util.Map<String, Object>> history = new java.util.ArrayList<>();
        
        // 1. 获取成功支付的记录（充值记录）
        // 注意：积分是在支付成功时通过 PaymentService.addPointsForPayment 添加的
        // 所以应该从支付记录中获取充值记录，而不是从订单中获取
        java.util.List<com.example.demo.model.Payment> allPayments = paymentRepository
            .findByUserIdOrderByCreatedAtDesc(user.getId());
        
        java.util.List<com.example.demo.model.Payment> successfulPayments = allPayments
            .stream()
            .filter(payment -> payment.getStatus() == com.example.demo.model.PaymentStatus.SUCCESS)
            .collect(java.util.stream.Collectors.toList());
        
        for (com.example.demo.model.Payment payment : successfulPayments) {
            // 从支付记录中提取积分数量（使用与 PaymentService.addPointsForPayment 相同的逻辑）
            Integer points = extractPointsFromPayment(payment);
            
            // 即使没有提取到积分，也显示充值记录（可能金额不在套餐范围内，但用户确实支付了）
            // 如果提取到积分，使用提取的积分；否则显示0积分（表示支付成功但未获得积分）
            Integer displayPoints = (points != null && points > 0) ? points : 0;
            
            java.util.Map<String, Object> record = new java.util.HashMap<>();
            record.put("type", "充值");
            String description = payment.getDescription() != null ? payment.getDescription() : "支付充值";
            if (displayPoints == 0 && points == null) {
                // 如果未提取到积分，在描述中说明
                description = description + "（金额不在套餐范围内，未获得积分）";
            }
            record.put("description", description);
            record.put("points", displayPoints);
            record.put("time", payment.getPaidAt() != null ? payment.getPaidAt() : payment.getCreatedAt());
            record.put("orderId", payment.getOrderId());
            record.put("paymentId", payment.getId());
            history.add(record);
        }
        
        // 2. 也检查已支付和已完成订单（作为补充，以防有订单但没有支付记录的情况）
        java.util.List<com.example.demo.model.Order> paidOrders = new java.util.ArrayList<>();
        // 包含PAID状态的订单
        paidOrders.addAll(orderRepository.findByUserIdAndStatus(
            user.getId(), 
            com.example.demo.model.OrderStatus.PAID
        ));
        // 包含COMPLETED状态的订单
        paidOrders.addAll(orderRepository.findByUserIdAndStatus(
            user.getId(), 
            com.example.demo.model.OrderStatus.COMPLETED
        ));
        
        for (com.example.demo.model.Order order : paidOrders) {
            // 检查是否已经在支付记录中处理过（避免重复）
            // 通过Payment关联的Order ID来匹配，而不是通过orderId字符串
            boolean alreadyProcessed = successfulPayments.stream()
                .anyMatch(p -> p.getOrder() != null && p.getOrder().getId().equals(order.getId()));
            
            if (!alreadyProcessed) {
                // 从订单描述或订单项中提取积分数量
                Integer points = extractPointsFromOrder(order);
                
                if (points != null && points > 0) {
                    java.util.Map<String, Object> record = new java.util.HashMap<>();
                    record.put("type", "充值");
                    record.put("description", "订单充值 - " + (order.getDescription() != null ? order.getDescription() : ""));
                    record.put("points", points);
                    record.put("time", order.getPaidAt() != null ? order.getPaidAt() : order.getCreatedAt());
                    record.put("orderNumber", order.getOrderNumber());
                    record.put("orderType", order.getOrderType() != null ? order.getOrderType().name() : "");
                    history.add(record);
                }
            }
        }
        
        // 3. 获取积分冻结记录（使用记录）- 只获取已扣除的记录
        // 直接在数据库层面过滤DEDUCTED状态，避免加载所有记录到内存
        java.util.List<PointsFreezeRecord> deductedRecords = pointsFreezeRecordRepository
            .findByUsernameAndStatusOrderByCompletedAtDesc(username, PointsFreezeRecord.FreezeStatus.DEDUCTED);
        
        for (PointsFreezeRecord record : deductedRecords) {
            java.util.Map<String, Object> historyRecord = new java.util.HashMap<>();
            historyRecord.put("type", "消耗");
            historyRecord.put("description", record.getTaskType().getDescription() + " - " + 
                (record.getFreezeReason() != null ? record.getFreezeReason() : "任务消耗"));
            historyRecord.put("points", -record.getFreezePoints()); // 负数表示消耗
            historyRecord.put("time", record.getCompletedAt() != null ? record.getCompletedAt() : record.getCreatedAt());
            historyRecord.put("taskId", record.getTaskId());
            historyRecord.put("taskType", record.getTaskType().name());
            history.add(historyRecord);
        }
        
        // 4. 按时间倒序排序
        history.sort((a, b) -> {
            java.time.LocalDateTime timeA = (java.time.LocalDateTime) a.get("time");
            java.time.LocalDateTime timeB = (java.time.LocalDateTime) b.get("time");
            return timeB.compareTo(timeA); // 倒序
        });
        
        // 5. 分页处理
        int start = page * size;
        int end = Math.min(start + size, history.size());
        if (start >= history.size()) {
            return new java.util.ArrayList<>();
        }
        return history.subList(start, end);
    }
    
    /**
     * 从订单中提取积分数量
     * 这里需要根据实际业务逻辑调整
     * 假设订单描述或订单项中包含积分信息
     */
    private Integer extractPointsFromOrder(com.example.demo.model.Order order) {
        // 方法1：从订单描述中提取（如果描述包含积分信息）
        if (order.getDescription() != null) {
                    java.util.regex.Pattern pattern = java.util.regex.Pattern.compile("(\\d+)积分");
                    java.util.regex.Matcher matcher = pattern.matcher(order.getDescription());
                    if (matcher.find()) {
                        return Integer.valueOf(matcher.group(1));
                    }
        }
        
        // 方法2：从订单项中提取（如果订单项名称包含积分信息）
        if (order.getOrderItems() != null && !order.getOrderItems().isEmpty()) {
            for (com.example.demo.model.OrderItem item : order.getOrderItems()) {
                if (item.getProductName() != null) {
                    java.util.regex.Pattern pattern = java.util.regex.Pattern.compile("(\\d+)积分");
                    java.util.regex.Matcher matcher = pattern.matcher(item.getProductName());
                    if (matcher.find()) {
                        return Integer.valueOf(matcher.group(1));
                    }
                    // 如果是会员订阅，从数据库读取积分配置
                    if (item.getProductName().contains("入门") || item.getProductName().contains("标准版") || item.getProductName().contains("专业版")) {
                        // 从membership_levels表读取积分配置（禁止硬编码）
                        MembershipLevel starterLevel = membershipLevelRepository.findByName("starter")
                            .orElse(null);
                        MembershipLevel standardLevel = membershipLevelRepository.findByName("standard")
                            .orElse(null);
                        MembershipLevel proLevel = membershipLevelRepository.findByName("professional")
                            .orElse(null);
                        
                        if (item.getProductName().contains("专业版") && proLevel != null) {
                            return proLevel.getPointsBonus();
                        } else if (item.getProductName().contains("标准版") && standardLevel != null) {
                            return standardLevel.getPointsBonus();
                        } else if (item.getProductName().contains("入门") && starterLevel != null) {
                            return starterLevel.getPointsBonus();
                        }
                    }
                }
            }
        }
        
        // 方法3：根据订单类型和金额计算积分（从数据库读取配置）
        if (order.getOrderType() != null) {
            if (order.getOrderType() == com.example.demo.model.OrderType.SUBSCRIPTION) {
                // 订阅订单：根据金额计算积分（从数据库读取，禁止硬编码）
                if (order.getTotalAmount() != null) {
                    // 使用OrderService的逻辑从数据库读取积分配置
                    try {
                        MembershipLevel starterLevel = membershipLevelRepository.findByName("starter")
                            .orElseThrow(() -> new IllegalStateException("数据库中缺少starter会员等级配置"));
                        MembershipLevel standardLevel = membershipLevelRepository.findByName("standard")
                            .orElseThrow(() -> new IllegalStateException("数据库中缺少standard会员等级配置"));
                        MembershipLevel proLevel = membershipLevelRepository.findByName("professional")
                            .orElseThrow(() -> new IllegalStateException("数据库中缺少professional会员等级配置"));
                        
                        int starterPrice = starterLevel.getPrice().intValue();
                        int starterPoints = starterLevel.getPointsBonus();
                        int standardPrice = standardLevel.getPrice().intValue();
                        int standardPoints = standardLevel.getPointsBonus();
                        int proPrice = proLevel.getPrice().intValue();
                        int proPoints = proLevel.getPointsBonus();
                        
                        int amountInt = order.getTotalAmount().intValue();
                        
                        // 判断套餐类型（允许10%的价格浮动范围，先高后低）
                        if (amountInt >= proPrice * 0.9 && amountInt <= proPrice * 1.1) {
                            return proPoints;  // 专业版积分
                        } else if (amountInt >= standardPrice * 0.9 && amountInt <= standardPrice * 1.1) {
                            return standardPoints;   // 标准版积分
                        } else if (amountInt >= starterPrice * 0.9 && amountInt <= starterPrice * 1.1) {
                            return starterPoints;    // 入门版积分
                        } else if (amountInt >= proPrice) {
                            return proPoints;
                        } else if (amountInt >= standardPrice) {
                            return standardPoints;
                        } else if (amountInt >= starterPrice) {
                            return starterPoints;
                        }
                    } catch (Exception e) {
                        logger.error("从数据库读取会员等级配置失败: {}", e.getMessage(), e);
                    }
                }
            }
        }
        
        return null;
    }
    
    /**
     * 从支付记录中提取积分数量
     * 使用与 PaymentService.addPointsForPayment 相同的逻辑
     */
    private Integer extractPointsFromPayment(com.example.demo.model.Payment payment) {
        if (payment == null) {
            return null;
        }
        
        java.math.BigDecimal amount = payment.getAmount();
        if (amount == null) {
            logger.warn("支付记录 ID: {} 的金额为空", payment.getId());
            return null;
        }
        
        String description = payment.getDescription() != null ? payment.getDescription() : "";
        Integer pointsToAdd = 0;
        
        // 从membership_levels表读取价格和积分配置（必须从数据库获取，禁止硬编码）
        MembershipLevel starterLevel = membershipLevelRepository.findByName("starter")
            .orElseThrow(() -> new IllegalStateException("数据库中缺少starter会员等级配置"));
        MembershipLevel standardLevel = membershipLevelRepository.findByName("standard")
            .orElseThrow(() -> new IllegalStateException("数据库中缺少standard会员等级配置"));
        MembershipLevel proLevel = membershipLevelRepository.findByName("professional")
            .orElseThrow(() -> new IllegalStateException("数据库中缺少professional会员等级配置"));
        
        int starterPrice = starterLevel.getPrice().intValue();
        int starterPoints = starterLevel.getPointsBonus();
        int standardPrice = standardLevel.getPrice().intValue();
        int standardPoints = standardLevel.getPointsBonus();
        int proPrice = proLevel.getPrice().intValue();
        int proPoints = proLevel.getPointsBonus();
        
        // 优先从描述中识别套餐类型
        if (description.contains("专业版") || description.contains("premium") || 
            description.contains("Premium") || description.contains("PREMIUM") ||
            description.contains("professional") || description.contains("Professional")) {
            pointsToAdd = proPoints;
        } else if (description.contains("标准版") || description.contains("standard") || 
            description.contains("Standard") || description.contains("STANDARD")) {
            pointsToAdd = standardPoints;
        } else if (description.contains("入门") || description.contains("starter") || 
            description.contains("Starter") || description.contains("STARTER")) {
            pointsToAdd = starterPoints;
        } else {
            // 如果描述中没有套餐信息，根据金额判断（先高后低）
            int amountInt = amount.intValue();
            if (amountInt >= proPrice * 0.9 && amountInt <= proPrice * 1.1) {
                pointsToAdd = proPoints;
            } else if (amountInt >= standardPrice * 0.9 && amountInt <= standardPrice * 1.1) {
                pointsToAdd = standardPoints;
            } else if (amountInt >= starterPrice * 0.9 && amountInt <= starterPrice * 1.1) {
                pointsToAdd = starterPoints;
            } else if (amountInt >= proPrice) {
                pointsToAdd = proPoints;
            } else if (amountInt >= standardPrice) {
                pointsToAdd = standardPoints;
            } else if (amountInt >= starterPrice) {
                pointsToAdd = starterPoints;
            }
        }
        
        return pointsToAdd > 0 ? pointsToAdd : null;
    }

    /**
     * 统计在线用户数（最近N分钟内有活动的用户）
     * 
     * @param minutes 活跃时间范围（分钟），默认10分钟
     * @return 在线用户数量
     */
    @Transactional(readOnly = true)
    public long countOnlineUsers(int minutes) {
        LocalDateTime activeAfter = LocalDateTime.now().minusMinutes(minutes);
        return userRepository.countByLastActiveTimeAfter(activeAfter);
    }

    /**
     * 统计在线用户数（默认10分钟内活跃）
     * 
     * @return 在线用户数量
     */
    @Transactional(readOnly = true)
    public long countOnlineUsers() {
        return countOnlineUsers(10);
    }
    
    /**
     * 处理过期会员：付费会员过期同时清零积分，但保留会员等级
     * 免费会员不会过期（到期时间设为2099年）
     * @return 处理的过期会员数量
     */
    @Transactional
    public int processExpiredMemberships(com.example.demo.repository.UserMembershipRepository userMembershipRepository) {
        LocalDateTime now = LocalDateTime.now();
        java.util.List<com.example.demo.model.UserMembership> expiredMemberships = 
            userMembershipRepository.findByStatusAndEndDateBefore("ACTIVE", now);
        
        // 查找免费会员等级
        Optional<MembershipLevel> freeLevelOpt = membershipLevelRepository.findByName("free");
        if (freeLevelOpt.isEmpty()) {
            logger.error("未找到免费会员等级(free)，无法处理过期会员");
            return 0;
        }
        Long freeLevelId = freeLevelOpt.get().getId();
        
        // 永久有效的到期时间（针对免费会员）
        LocalDateTime permanentEndDate = LocalDateTime.of(2099, 12, 31, 23, 59, 59);
        
        int processedCount = 0;
        for (com.example.demo.model.UserMembership membership : expiredMemberships) {
            try {
                // 跳过免费会员（他们不应该过期，但以防万一）
                if (membership.getMembershipLevelId().equals(freeLevelId)) {
                    // 免费会员如果意外过期，直接延长到2099年
                    membership.setEndDate(permanentEndDate);
                    membership.setUpdatedAt(now);
                    userMembershipRepository.save(membership);
                    logger.info("✅ 免费会员到期时间已延长: userId={}", membership.getUserId());
                    continue;
                }
                
                // 🔥 付费会员过期：保留等级，只清零积分，不降级
                // 不修改 membershipLevelId（保留原等级）
                // 不修改 endDate（保留过期时间）
                // 不修改 status（保持ACTIVE，前端根据endDate判断是否过期）
                membership.setUpdatedAt(now);
                userMembershipRepository.save(membership);
                
                // 清零用户积分
                java.util.Optional<User> userOpt = userRepository.findById(membership.getUserId());
                if (userOpt.isPresent()) {
                    User user = userOpt.get();
                    int oldPoints = user.getPoints();
                    if (oldPoints > 0) {
                        user.setPoints(0);
                        user.setFrozenPoints(0); // 同时清零冻结积分
                        userRepository.save(user);
                        logger.info("✅ 付费会员过期积分清零: userId={}, 原积分={}", membership.getUserId(), oldPoints);
                    }
                }
                
                processedCount++;
                logger.info("✅ 付费会员过期已处理（保留等级，积分清零）: userId={}, 过期时间={}", 
                    membership.getUserId(), membership.getEndDate());
            } catch (Exception e) {
                logger.error("处理过期会员失败: userId={}", membership.getUserId(), e);
            }
        }
        
        if (processedCount > 0) {
            logger.info("✅ 共处理 {} 个过期付费会员，已清零积分但保留会员等级", processedCount);
        }
        
        return processedCount;
    }
}
