package com.mosquito.project.service;

import com.mosquito.project.domain.*;
import com.mosquito.project.dto.CreateActivityRequest;
import com.mosquito.project.dto.CreateApiKeyRequest;
import com.mosquito.project.dto.ApiKeyListItemResponse;
import com.mosquito.project.dto.UpdateActivityRequest;
import com.mosquito.project.dto.ActivityStatsResponse;
import com.mosquito.project.dto.ActivityGraphResponse;
import com.mosquito.project.exception.BusinessException;
import com.mosquito.project.exception.ActivityNotFoundException;
import com.mosquito.project.exception.ApiKeyNotFoundException;
import com.mosquito.project.exception.FileUploadException;
import com.mosquito.project.exception.InvalidActivityDataException;
import com.mosquito.project.exception.InvalidApiKeyException;
import com.mosquito.project.exception.UserNotAuthorizedForActivityException;
import com.mosquito.project.permission.ApprovalFlowService;
import com.mosquito.project.permission.PermissionCheckService;
import org.springframework.context.annotation.Lazy;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.mosquito.project.persistence.entity.ActivityEntity;
import com.mosquito.project.persistence.entity.ActivityStatus;
import com.mosquito.project.persistence.repository.ActivityRepository;
import com.mosquito.project.persistence.entity.ApiKeyEntity;
import com.mosquito.project.persistence.repository.ApiKeyRepository;
import java.time.ZoneOffset;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import jakarta.persistence.criteria.Predicate;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.security.SecureRandom;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Service
public class ActivityService {

    private static final Logger log = LoggerFactory.getLogger(ActivityService.class);

    private static final long MAX_IMAGE_SIZE_BYTES = 10 * 1024 * 1024; // 10MB
    private static final List<String> SUPPORTED_IMAGE_TYPES = List.of("image/jpeg", "image/png", "image/gif");

    private final DelayProvider delayProvider;
    private final ActivityRepository activityRepository;
    private final com.mosquito.project.persistence.repository.DailyActivityStatsRepository dailyActivityStatsRepository;
    private final ApiKeyRepository apiKeyRepository;
    private final com.mosquito.project.persistence.repository.UserInviteRepository userInviteRepository;
    private final com.mosquito.project.permission.UserRepository sysUserRepository;
    private final ApiKeyEncryptionService encryptionService;
    private final com.mosquito.project.config.AppConfig appConfig;
    private final CouponRewardService couponRewardService;
    private final ApprovalFlowService approvalFlowService;
    private final PermissionCheckService permissionCheckService;
    private final RewardService rewardService;
    private final ObjectMapper objectMapper;
    private static final int KEY_PREFIX_LEN = 12;

    // 活动业务类型，用于审批流
    public static final String BIZ_TYPE_ACTIVITY_CREATE = "ACTIVITY_CREATE";
    public static final String BIZ_TYPE_ACTIVITY_UPDATE = "ACTIVITY_UPDATE";
    public static final String BIZ_TYPE_ACTIVITY_DELETE = "ACTIVITY_DELETE";

    public ActivityService(DelayProvider delayProvider, ActivityRepository activityRepository, ApiKeyRepository apiKeyRepository, com.mosquito.project.persistence.repository.DailyActivityStatsRepository dailyActivityStatsRepository, com.mosquito.project.persistence.repository.UserInviteRepository userInviteRepository, com.mosquito.project.permission.UserRepository sysUserRepository, ApiKeyEncryptionService encryptionService, com.mosquito.project.config.AppConfig appConfig, CouponRewardService couponRewardService, @Lazy ApprovalFlowService approvalFlowService, PermissionCheckService permissionCheckService, RewardService rewardService, ObjectMapper objectMapper) {
        this.delayProvider = delayProvider;
        this.activityRepository = activityRepository;
        this.apiKeyRepository = apiKeyRepository;
        this.dailyActivityStatsRepository = dailyActivityStatsRepository;
        this.userInviteRepository = userInviteRepository;
        this.sysUserRepository = sysUserRepository;
        this.encryptionService = encryptionService;
        this.appConfig = appConfig;
        this.couponRewardService = couponRewardService;
        this.approvalFlowService = approvalFlowService;
        this.permissionCheckService = permissionCheckService;
        this.rewardService = rewardService;
        this.objectMapper = objectMapper;
    }

    @Caching(evict = {
        @CacheEvict(value = "leaderboards", allEntries = true),
        @CacheEvict(value = "activity_stats", allEntries = true),
        @CacheEvict(value = "activity_graph", allEntries = true)
    })
    @org.springframework.transaction.annotation.Transactional
    public Activity createActivity(CreateActivityRequest request, Long creatorId, Long departmentId) {
        if (request.getEndTime().isBefore(request.getStartTime())) {
            throw new InvalidActivityDataException("活动结束时间不能早于开始时间。");
        }
        ActivityEntity entity = new ActivityEntity();
        entity.setName(request.getName());
        entity.setStartTimeUtc(request.getStartTime().withZoneSameInstant(ZoneOffset.UTC).toOffsetDateTime());
        entity.setEndTimeUtc(request.getEndTime().withZoneSameInstant(ZoneOffset.UTC).toOffsetDateTime());
        // 保存目标人群配置
        entity.setTargetUsersConfig(request.getTargetUsersConfig());
        // 保存页面内容配置
        entity.setPageContentConfig(request.getPageContentConfig());
        // 保存奖励计算模式
        entity.setRewardCalculationMode(request.getRewardCalculationMode() != null ?
            request.getRewardCalculationMode() : "DIFFERENTIAL");
        // 保存阶梯奖励配置
        entity.setRewardTiersConfig(request.getRewardTiersConfig());
        // 保存多级衰减奖励配置
        entity.setMultiLevelRewardConfig(request.getMultiLevelRewardConfig());
        entity.setStatus(ActivityStatus.DRAFT);
        entity.setCreatedAt(java.time.OffsetDateTime.now(ZoneOffset.UTC));
        entity.setUpdatedAt(java.time.OffsetDateTime.now(ZoneOffset.UTC));
        // 设置创建者和部门ID（数据权限字段）
        entity.setCreatorId(creatorId);
        entity.setDepartmentId(departmentId);
        entity = activityRepository.save(entity);

        Activity activity = toActivity(entity);
        return activity;
    }

    /**
     * 兼容旧版本的无参方法
     */
    public Activity createActivity(CreateActivityRequest request) {
        return createActivity(request, null, null);
    }

    @Caching(evict = {
        @CacheEvict(value = "leaderboards", allEntries = true),
        @CacheEvict(value = "activity_stats", allEntries = true),
        @CacheEvict(value = "activity_graph", allEntries = true)
    })
    @org.springframework.transaction.annotation.Transactional
    public Activity updateActivity(Long id, UpdateActivityRequest request) {
        ActivityEntity entity = activityRepository.findById(id)
            .orElseThrow(() -> new ActivityNotFoundException("活动不存在。"));

        if (request.getEndTime().isBefore(request.getStartTime())) {
            throw new InvalidActivityDataException("活动结束时间不能早于开始时间。");
        }

        // 检查活动状态：运行中(RUNNING)或已暂停(PAUSED)的活动需要审批
        String currentStatus = entity.getStatus();
        boolean isRunningOrPaused = ActivityStatus.RUNNING.equals(currentStatus)
                || ActivityStatus.PAUSED.equals(currentStatus)
                || "RUNNING".equals(currentStatus)
                || "PAUSED".equals(currentStatus);

        if (isRunningOrPaused) {
            // 运行中/已暂停状态的活动需要审批通过后才能更新
            // 将更新请求序列化为业务数据
            try {
                String bizData = objectMapper.writeValueAsString(request);
                Map<String, Object> approvalResult = approvalFlowService.submitApprovalByEvent(
                    "ACTIVITY_UPDATE",  // 触发事件，对应审批流程模板
                    BIZ_TYPE_ACTIVITY_UPDATE,  // 业务类型
                    id,  // 业务ID
                    "活动更新审批: " + entity.getName(),  // 审批标题
                    entity.getCreatorId() != null ? entity.getCreatorId() : 1L,  // 申请人ID（暂时使用创建者ID）
                    "活动内容更新待审批",  // 申请原因
                    bizData  // 业务数据（包含变更内容）
                );
                log.info("活动更新已提交审批: activityId={}, recordId={}", id, approvalResult.get("recordId"));
                // 返回活动信息，状态保持不变，等待审批
                return toActivity(entity);
            } catch (JsonProcessingException e) {
                log.error("序列化活动更新数据失败: {}", e.getMessage());
                throw new BusinessException("提交审批失败，请稍后重试");
            } catch (Exception e) {
                log.error("提交活动更新审批失败: {}", e.getMessage());
                throw new BusinessException("提交审批失败，请稍后重试");
            }
        }

        // 草稿状态的活动可以直接更新
        entity.setName(request.getName());
        entity.setStartTimeUtc(request.getStartTime().withZoneSameInstant(ZoneOffset.UTC).toOffsetDateTime());
        entity.setEndTimeUtc(request.getEndTime().withZoneSameInstant(ZoneOffset.UTC).toOffsetDateTime());
        // 保存目标人群配置
        entity.setTargetUsersConfig(request.getTargetUsersConfig());
        // 保存页面内容配置
        entity.setPageContentConfig(request.getPageContentConfig());
        // 保存奖励计算模式
        entity.setRewardCalculationMode(request.getRewardCalculationMode());
        // 保存阶梯奖励配置
        entity.setRewardTiersConfig(request.getRewardTiersConfig());
        // 保存多级衰减奖励配置
        entity.setMultiLevelRewardConfig(request.getMultiLevelRewardConfig());
        entity.setUpdatedAt(java.time.OffsetDateTime.now(ZoneOffset.UTC));
        activityRepository.save(entity);

        return toActivity(entity);
    }

    /**
     * 应用待审批的活动更新（审批通过后调用）
     * 此方法直接更新活动，不检查状态也不触发审批
     */
    @Caching(evict = {
        @CacheEvict(value = "leaderboards", allEntries = true),
        @CacheEvict(value = "activity_stats", allEntries = true),
        @CacheEvict(value = "activity_graph", allEntries = true)
    })
    @org.springframework.transaction.annotation.Transactional
    public Activity applyActivityUpdate(Long id, UpdateActivityRequest request) {
        ActivityEntity entity = activityRepository.findById(id)
            .orElseThrow(() -> new ActivityNotFoundException("活动不存在。"));

        if (request.getEndTime().isBefore(request.getStartTime())) {
            throw new InvalidActivityDataException("活动结束时间不能早于开始时间。");
        }

        // 直接应用更新，不检查状态
        entity.setName(request.getName());
        entity.setStartTimeUtc(request.getStartTime().withZoneSameInstant(ZoneOffset.UTC).toOffsetDateTime());
        entity.setEndTimeUtc(request.getEndTime().withZoneSameInstant(ZoneOffset.UTC).toOffsetDateTime());
        entity.setTargetUsersConfig(request.getTargetUsersConfig());
        entity.setPageContentConfig(request.getPageContentConfig());
        entity.setRewardCalculationMode(request.getRewardCalculationMode());
        entity.setRewardTiersConfig(request.getRewardTiersConfig());
        entity.setMultiLevelRewardConfig(request.getMultiLevelRewardConfig());
        entity.setUpdatedAt(java.time.OffsetDateTime.now(ZoneOffset.UTC));
        activityRepository.save(entity);

        log.info("活动更新已应用: activityId={}", id);
        return toActivity(entity);
    }

    /**
     * 更新活动配置（独立配置更新接口）
     * 只更新四大配置字段，不涉及基础信息（名称、时间等）
     * 运行中的活动配置更新需要审批
     */
    @Caching(evict = {
        @CacheEvict(value = "leaderboards", allEntries = true),
        @CacheEvict(value = "activity_stats", allEntries = true),
        @CacheEvict(value = "activity_graph", allEntries = true)
    })
    @org.springframework.transaction.annotation.Transactional
    public Activity updateActivityConfig(Long id, Map<String, Object> config) {
        ActivityEntity entity = activityRepository.findById(id)
            .orElseThrow(() -> new ActivityNotFoundException("活动不存在。"));

        // 检查活动状态：运行中(RUNNING)或已暂停(PAUSED)的活动需要审批
        String currentStatus = entity.getStatus();
        boolean isRunningOrPaused = ActivityStatus.RUNNING.equals(currentStatus)
                || ActivityStatus.PAUSED.equals(currentStatus)
                || "RUNNING".equals(currentStatus)
                || "PAUSED".equals(currentStatus);

        if (isRunningOrPaused) {
            // 运行中/已暂停状态的活动需要审批通过后才能更新配置
            try {
                String bizData = objectMapper.writeValueAsString(config);
                approvalFlowService.submitApprovalByEvent(
                    "ACTIVITY_UPDATE",  // 触发事件
                    BIZ_TYPE_ACTIVITY_UPDATE,  // 业务类型
                    id,  // 业务ID
                    "活动配置更新审批: " + entity.getName(),  // 审批标题
                    entity.getCreatorId() != null ? entity.getCreatorId() : 1L,  // 申请人ID
                    "活动配置更新待审批",  // 申请原因
                    bizData  // 业务数据（包含变更内容）
                );
                log.info("活动配置更新已提交审批: activityId={}", id);
                // 返回当前活动信息，配置变更等待审批
                return toActivity(entity);
            } catch (JsonProcessingException e) {
                log.error("序列化活动配置更新数据失败: {}", e.getMessage());
                throw new BusinessException("提交审批失败，请稍后重试");
            } catch (Exception e) {
                log.error("提交活动配置更新审批失败: {}", e.getMessage());
                throw new BusinessException("提交审批失败，请稍后重试");
            }
        }

        // 草稿状态的活动可以直接更新配置
        if (config.containsKey("targetUsersConfig")) {
            entity.setTargetUsersConfig((String) config.get("targetUsersConfig"));
        }
        if (config.containsKey("pageContentConfig")) {
            entity.setPageContentConfig((String) config.get("pageContentConfig"));
        }
        if (config.containsKey("rewardTiersConfig")) {
            entity.setRewardTiersConfig((String) config.get("rewardTiersConfig"));
        }
        if (config.containsKey("rewardCalculationMode")) {
            entity.setRewardCalculationMode((String) config.get("rewardCalculationMode"));
        }
        if (config.containsKey("multiLevelRewardConfig")) {
            entity.setMultiLevelRewardConfig((String) config.get("multiLevelRewardConfig"));
        }
        entity.setUpdatedAt(java.time.OffsetDateTime.now(ZoneOffset.UTC));
        activityRepository.save(entity);

        log.info("活动配置已更新: activityId={}", id);
        return toActivity(entity);
    }

    public Activity getActivityById(Long id) {
        ActivityEntity entity = activityRepository.findById(id)
            .orElseThrow(() -> new ActivityNotFoundException("活动不存在。"));
        return toActivity(entity);
    }

    public List<Activity> getAllActivities() {
        List<Activity> result = new ArrayList<>();
        for (ActivityEntity e : activityRepository.findAll()) {
            result.add(toActivity(e));
        }
        return result;
    }

    /**
     * 分页查询活动列表（带数据权限过滤）
     */
    public Map<String, Object> getActivitiesPaged(int page, int size, String status, String keyword,
            java.time.OffsetDateTime startDate, java.time.OffsetDateTime endDate, Long currentUserId) {
        int p = Math.max(0, page);
        int s = Math.max(1, Math.min(size, 100));
        Pageable pageable = PageRequest.of(p, s, Sort.by(Sort.Direction.DESC, "createdAt"));

        // 构建查询条件
        Specification<ActivityEntity> spec = buildActivitySpecification(status, keyword, startDate, endDate, currentUserId);

        Page<ActivityEntity> entityPage = activityRepository.findAll(spec, pageable);

        // 转换为Activity列表
        List<Activity> activities = entityPage.getContent().stream()
                .map(this::toActivity)
                .collect(Collectors.toList());

        // 构建返回结果
        Map<String, Object> result = new HashMap<>();
        result.put("content", activities);
        result.put("totalElements", entityPage.getTotalElements());
        result.put("totalPages", entityPage.getTotalPages());
        result.put("currentPage", entityPage.getNumber());
        result.put("pageSize", entityPage.getSize());

        return result;
    }

    /**
     * 构建活动查询条件（带数据权限过滤）
     */
    private Specification<ActivityEntity> buildActivitySpecification(String status, String keyword,
            java.time.OffsetDateTime startDate, java.time.OffsetDateTime endDate, Long currentUserId) {
        return (root, query, cb) -> {
            List<Predicate> predicates = new ArrayList<>();

            // 状态筛选
            if (status != null && !status.isBlank()) {
                predicates.add(cb.equal(root.get("status"), status));
            }

            // 关键词搜索（活动名称）
            if (keyword != null && !keyword.isBlank()) {
                predicates.add(cb.like(root.get("name"), "%" + keyword + "%"));
            }

            // 时间范围筛选
            if (startDate != null) {
                predicates.add(cb.greaterThanOrEqualTo(root.get("startTimeUtc"), startDate));
            }
            if (endDate != null) {
                predicates.add(cb.lessThanOrEqualTo(root.get("endTimeUtc"), endDate));
            }

            // 数据权限过滤
            if (currentUserId != null) {
                String dataScope = permissionCheckService.getUserDataScope(currentUserId);
                if ("ALL".equals(dataScope)) {
                    // 全部数据权限不过滤
                } else if ("DEPARTMENT".equals(dataScope)) {
                    // 部门数据权限：只看本部门及下级部门的活动
                    Long userDeptId = permissionCheckService.getUserDepartmentId(currentUserId);
                    if (userDeptId != null) {
                        List<Long> deptIds = permissionCheckService.getUserDepartmentAndChildIds(currentUserId);
                        if (!deptIds.isEmpty()) {
                            predicates.add(root.get("departmentId").in(deptIds));
                        }
                    }
                } else {
                    // 个人数据权限：只看自己创建的活动
                    predicates.add(cb.equal(root.get("creatorId"), currentUserId));
                }
            }

            return cb.and(predicates.toArray(new Predicate[0]));
        };
    }

    /**
     * 保存待审批的API Key（创建禁用状态）
     */
    public Long savePendingApiKey(CreateApiKeyRequest request) {
        if (!activityRepository.existsById(request.getActivityId())) {
            throw new ActivityNotFoundException("关联的活动不存在。");
        }

        // 待审批API Key也必须满足表非空约束（key_hash/salt），
        // 但保持禁用状态，只有审批通过后才可被使用。
        String rawApiKey = UUID.randomUUID().toString();
        byte[] salt = generateSalt();
        String keyHash = hashApiKey(rawApiKey, salt);
        String encryptedKey = encryptionService.encrypt(rawApiKey);

        ApiKeyEntity entity = new ApiKeyEntity();
        entity.setActivityId(request.getActivityId());
        entity.setName(request.getName());
        entity.setSalt(Base64.getEncoder().encodeToString(salt));
        entity.setKeyHash(keyHash);
        entity.setKeyPrefix(rawApiKey.substring(0, Math.min(KEY_PREFIX_LEN, rawApiKey.length())));
        entity.setEncryptedKey(encryptedKey);
        entity.setEnabled(false); // 禁用状态，等待审批
        entity.setCreatedAt(java.time.OffsetDateTime.now(java.time.ZoneOffset.UTC));
        entity = apiKeyRepository.save(entity);
        log.info("待审批API Key已保存: id={}, activityId={}", entity.getId(), request.getActivityId());
        return entity.getId();
    }

    /**
     * 审批通过后激活API Key
     */
    public String activatePendingApiKey(Long id) {
        Optional<ApiKeyEntity> optKey = apiKeyRepository.findById(id);
        if (optKey.isPresent()) {
            ApiKeyEntity entity = optKey.get();
            if (!entity.getEnabled()) {
                // 生成API Key
                String rawApiKey = UUID.randomUUID().toString();
                byte[] salt = generateSalt();
                String keyHash = hashApiKey(rawApiKey, salt);
                String encryptedKey = encryptionService.encrypt(rawApiKey);

                entity.setSalt(Base64.getEncoder().encodeToString(salt));
                entity.setKeyHash(keyHash);
                entity.setKeyPrefix(rawApiKey.substring(0, Math.min(KEY_PREFIX_LEN, rawApiKey.length())));
                entity.setEncryptedKey(encryptedKey);
                entity.setEnabled(true);
                entity.setCreatedAt(java.time.OffsetDateTime.now(java.time.ZoneOffset.UTC));
                apiKeyRepository.save(entity);
                log.info("审批通过，API Key已激活: id={}", id);
                return rawApiKey;
            }
        }
        return null;
    }

    @Caching(evict = {
        @CacheEvict(value = "leaderboards", allEntries = true),
        @CacheEvict(value = "activity_stats", allEntries = true)
    })
    @org.springframework.transaction.annotation.Transactional
    public String generateApiKey(CreateApiKeyRequest request) {
        if (!activityRepository.existsById(request.getActivityId())) {
            throw new ActivityNotFoundException("关联的活动不存在。");
        }

        String rawApiKey = UUID.randomUUID().toString();
        byte[] salt = generateSalt();
        String keyHash = hashApiKey(rawApiKey, salt);

        String encryptedKey = encryptionService.encrypt(rawApiKey);

        ApiKeyEntity entity = new ApiKeyEntity();
        entity.setActivityId(request.getActivityId());
        entity.setName(request.getName());
        entity.setSalt(Base64.getEncoder().encodeToString(salt));
        entity.setKeyHash(keyHash);
        entity.setKeyPrefix(rawApiKey.substring(0, Math.min(KEY_PREFIX_LEN, rawApiKey.length())));
        entity.setEncryptedKey(encryptedKey);
        entity.setCreatedAt(java.time.OffsetDateTime.now(java.time.ZoneOffset.UTC));
        apiKeyRepository.save(entity);

        return rawApiKey;
    }

    private byte[] generateSalt() {
        SecureRandom random = new SecureRandom();
        byte[] salt = new byte[16];
        random.nextBytes(salt);
        return salt;
    }

    private String hashApiKey(String apiKey, byte[] salt) {
        // Strengthen hashing using PBKDF2WithHmacSHA256
        try {
            javax.crypto.SecretKeyFactory skf = javax.crypto.SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
            javax.crypto.spec.PBEKeySpec spec = new javax.crypto.spec.PBEKeySpec(
                apiKey.toCharArray(), salt, 185000, 256
            );
            byte[] derived = skf.generateSecret(spec).getEncoded();
            return Base64.getEncoder().encodeToString(derived);
        } catch (Exception e) {
            throw new RuntimeException("无法创建API密钥哈希", e);
        }
    }

    @org.springframework.transaction.annotation.Transactional
    public void validateAndMarkApiKeyUsed(Long id, String rawApiKey) {
        ApiKeyEntity entity = apiKeyRepository.findById(id)
            .orElseThrow(() -> new com.mosquito.project.exception.ApiKeyNotFoundException("API密钥不存在。"));
        if (entity.getRevokedAt() != null) {
            throw new com.mosquito.project.exception.InvalidApiKeyException("API密钥已吊销或无效。");
        }
        if (entity.getEnabled() == null || !entity.getEnabled()) {
            throw new com.mosquito.project.exception.InvalidApiKeyException("API密钥已禁用。");
        }
        byte[] salt = Base64.getDecoder().decode(entity.getSalt());
        String computed = hashApiKey(rawApiKey, salt);
        if (!computed.equals(entity.getKeyHash())) {
            throw new com.mosquito.project.exception.InvalidApiKeyException("API密钥已吊销或无效。");
        }
        entity.setLastUsedAt(java.time.OffsetDateTime.now(java.time.ZoneOffset.UTC));
        apiKeyRepository.save(entity);
    }

    @org.springframework.transaction.annotation.Transactional
    public void validateApiKeyByPrefixAndMarkUsed(String rawApiKey) {
        String prefix = rawApiKey.substring(0, Math.min(KEY_PREFIX_LEN, rawApiKey.length())).trim();
        ApiKeyEntity entity = apiKeyRepository.findByKeyPrefix(prefix)
            .orElseThrow(() -> new com.mosquito.project.exception.InvalidApiKeyException("API密钥已吊销或无效。"));
        if (entity.getRevokedAt() != null) {
            throw new com.mosquito.project.exception.InvalidApiKeyException("API密钥已吊销或无效。");
        }
        if (entity.getEnabled() == null || !entity.getEnabled()) {
            throw new com.mosquito.project.exception.InvalidApiKeyException("API密钥已禁用。");
        }
        byte[] salt = Base64.getDecoder().decode(entity.getSalt());
        String computed = hashApiKey(rawApiKey, salt);
        if (!computed.equals(entity.getKeyHash())) {
            throw new com.mosquito.project.exception.InvalidApiKeyException("API密钥已吊销或无效。");
        }
        entity.setLastUsedAt(java.time.OffsetDateTime.now(java.time.ZoneOffset.UTC));
        apiKeyRepository.save(entity);
    }

    public void accessActivity(Activity activity, User user) {
        Set<Long> targetUserIds = activity.getTargetUserIds();
        if (targetUserIds != null && !targetUserIds.isEmpty() && !targetUserIds.contains(user.getId())) {
            throw new UserNotAuthorizedForActivityException("该活动仅对部分用户开放");
        }
    }

    public void uploadCustomizationImage(Long activityId, MultipartFile imageFile) {
        if (imageFile.getSize() > MAX_IMAGE_SIZE_BYTES) {
            throw new FileUploadException("暂不支持，请重新上传");
        }

        String contentType = imageFile.getContentType();
        if (contentType == null || !SUPPORTED_IMAGE_TYPES.contains(contentType)) {
            throw new FileUploadException("暂不支持，请重新上传");
        }
    }

    public Reward calculateReward(Activity activity, int userInviteCount) {
        if (activity.getRewardTiers() == null || activity.getRewardTiers().isEmpty()) {
            return new Reward(0);
        }

        List<RewardTier> achievedTiers = activity.getRewardTiers().stream()
            .filter(tier -> userInviteCount >= tier.getThreshold())
            .sorted(Comparator.comparingInt(RewardTier::getThreshold))
            .toList();

        if (achievedTiers.isEmpty()) {
            return new Reward(0);
        }

        RewardTier highestAchievedTier = achievedTiers.get(achievedTiers.size() - 1);

        if (activity.getRewardMode() == RewardMode.CUMULATIVE) {
            return highestAchievedTier.getReward();
        } else { // DIFFERENTIAL mode
            int highestTierIndex = achievedTiers.size() - 1;
            int previousTierPoints = (highestTierIndex > 0)
                ? achievedTiers.get(highestTierIndex - 1).getReward().getPoints()
                : 0;
            int currentTierPoints = highestAchievedTier.getReward().getPoints();
            return new Reward(currentTierPoints - previousTierPoints);
        }
    }

    public Reward calculateMultiLevelReward(Activity activity, Reward originalReward, int level) {
        if (activity.getMultiLevelRewardRules() == null) {
            return new Reward(0);
        }

        return activity.getMultiLevelRewardRules().stream()
            .filter(rule -> rule.getLevel() == level)
            .findFirst()
            .map(rule -> {
                BigDecimal originalPoints = new BigDecimal(originalReward.getPoints());
                BigDecimal calculatedPoints = originalPoints.multiply(rule.getDecayCoefficient());
                return new Reward(calculatedPoints.setScale(0, RoundingMode.HALF_UP).intValue());
            })
            .orElse(new Reward(0));
    }

    @Caching(evict = {
        @CacheEvict(value = "leaderboards", allEntries = true),
        @CacheEvict(value = "activity_stats", allEntries = true)
    })
    public void createReward(Reward reward, boolean skipValidation) {
        if (reward.getRewardType() == RewardType.COUPON && !skipValidation) {
            if (reward.getCouponBatchId() == null || reward.getCouponBatchId().isBlank()) {
                throw new InvalidActivityDataException("优惠券批次ID不能为空。");
            }
            // 使用CouponRewardService验证优惠券批次
            CouponRewardService.CouponValidationResult validationResult =
                couponRewardService.validateCouponBatch(reward.getCouponBatchId());
            if (!validationResult.isValid()) {
                throw new InvalidActivityDataException("优惠券批次验证失败: " + validationResult.getMessage());
            }
            log.info("优惠券批次验证通过: {}", reward.getCouponBatchId());
        }
    }

    @Caching(evict = {
        @CacheEvict(value = "activities", key = "#id"),
        @CacheEvict(value = "leaderboards", allEntries = true),
        @CacheEvict(value = "activity_stats", allEntries = true),
        @CacheEvict(value = "activity_graph", allEntries = true)
    })
    public void evictActivityCache(Long id) {
        log.info("Evicted cache for activity: {}", id);
    }

    @Caching(evict = {
        @CacheEvict(value = "leaderboards", allEntries = true),
        @CacheEvict(value = "activity_stats", allEntries = true)
    })
    @org.springframework.transaction.annotation.Transactional
    public void revokeApiKey(Long id) {
        ApiKeyEntity entity = apiKeyRepository.findById(id)
            .orElseThrow(() -> new ApiKeyNotFoundException("API密钥不存在。"));
        entity.setRevokedAt(java.time.OffsetDateTime.now(java.time.ZoneOffset.UTC));
        apiKeyRepository.save(entity);
    }

    @Caching(evict = {
        @CacheEvict(value = "leaderboards", allEntries = true),
        @CacheEvict(value = "activity_stats", allEntries = true)
    })
    public void markApiKeyUsed(Long id) {
        ApiKeyEntity entity = apiKeyRepository.findById(id)
            .orElseThrow(() -> new ApiKeyNotFoundException("API密钥不存在。"));
        entity.setLastUsedAt(java.time.OffsetDateTime.now(java.time.ZoneOffset.UTC));
        apiKeyRepository.save(entity);
    }

    @Caching(evict = {
        @CacheEvict(value = "leaderboards", allEntries = true),
        @CacheEvict(value = "activity_stats", allEntries = true)
    })
    @org.springframework.transaction.annotation.Transactional
    public String revealApiKey(Long id) {
        ApiKeyEntity entity = apiKeyRepository.findById(id)
            .orElseThrow(() -> new ApiKeyNotFoundException("API密钥不存在。"));
        if (entity.getRevokedAt() != null) {
            throw new InvalidApiKeyException("API密钥已吊销，无法显示。");
        }
        // PRD安全条款：API Key仅首次明文返回
        if (entity.getRevealedAt() != null) {
            throw new InvalidApiKeyException("API密钥已显示过一次，请妥善保管。如需重新获取，请吊销后重新创建。");
        }
        String rawApiKey = encryptionService.decrypt(entity.getEncryptedKey());
        entity.setRevealedAt(java.time.OffsetDateTime.now(java.time.ZoneOffset.UTC));
        apiKeyRepository.save(entity);
        log.info("API key revealed for id: {}", id);
        return rawApiKey;
    }

    /**
     * 获取API Key列表
     */
    public List<ApiKeyListItemResponse> listApiKeys() {
        List<ApiKeyEntity> entities = apiKeyRepository.findAll();
        return entities.stream()
            .map(this::toApiKeyListItem)
            .toList();
    }

    /**
     * 启用API Key
     */
    @org.springframework.transaction.annotation.Transactional
    public void enableApiKey(Long id) {
        ApiKeyEntity entity = apiKeyRepository.findById(id)
            .orElseThrow(() -> new ApiKeyNotFoundException("API密钥不存在。"));
        entity.setEnabled(true);
        apiKeyRepository.save(entity);
        log.info("API key enabled for id: {}", id);
    }

    /**
     * 禁用API Key
     */
    @org.springframework.transaction.annotation.Transactional
    public void disableApiKey(Long id) {
        ApiKeyEntity entity = apiKeyRepository.findById(id)
            .orElseThrow(() -> new ApiKeyNotFoundException("API密钥不存在。"));
        entity.setEnabled(false);
        apiKeyRepository.save(entity);
        log.info("API key disabled for id: {}", id);
    }

    /**
     * 重置API Key（吊销旧Key并创建新Key）
     */
    @org.springframework.transaction.annotation.Transactional
    public String resetApiKey(Long id) {
        ApiKeyEntity entity = apiKeyRepository.findById(id)
            .orElseThrow(() -> new ApiKeyNotFoundException("API密钥不存在。"));

        // 吊销旧Key
        entity.setRevokedAt(java.time.OffsetDateTime.now(java.time.ZoneOffset.UTC));
        entity.setEnabled(false);
        apiKeyRepository.save(entity);

        // 创建新Key
        CreateApiKeyRequest request = new CreateApiKeyRequest();
        request.setName(entity.getName());
        request.setActivityId(entity.getActivityId());
        String newRawApiKey = generateApiKey(request);

        log.info("API key reset for id: {}, new key generated", id);
        return newRawApiKey;
    }

    private ApiKeyListItemResponse toApiKeyListItem(ApiKeyEntity entity) {
        return ApiKeyListItemResponse.builder()
            .id(entity.getId())
            .name(entity.getName())
            .keyPrefix(entity.getKeyPrefix())
            .activityId(entity.getActivityId())
            .enabled(entity.getEnabled())
            .createdAt(entity.getCreatedAt())
            .lastUsedAt(entity.getLastUsedAt())
            .revealedAt(entity.getRevealedAt())
            .revokedAt(entity.getRevokedAt())
            .build();
    }

    @Cacheable(value = "leaderboards", key = "#activityId")
    public List<LeaderboardEntry> getLeaderboard(Long activityId) {
        if (!activityRepository.existsById(activityId)) {
            throw new ActivityNotFoundException("活动不存在。");
        }

        List<Object[]> results = userInviteRepository.countInvitesByActivityIdGroupByInviter(activityId);
        if (results.isEmpty()) {
            return java.util.Collections.emptyList();
        }

        return results.stream()
            .map(row -> {
                Long userId = ((Number) row[0]).longValue();
                Long inviteCount = ((Number) row[1]).longValue();
                // 尝试从用户服务获取真实昵称，否则使用默认值
                String nickname;
                String defaultAvatar = "https://api.dicebear.com/7.x/avataaars/svg?seed=" + userId;
                try {
                    var userOpt = sysUserRepository.findById(userId);
                    if (userOpt.isPresent()) {
                        var user = userOpt.get();
                        nickname = user.getRealName() != null && !user.getRealName().isEmpty()
                            ? user.getRealName()
                            : "用户" + userId;
                    } else {
                        nickname = "用户" + userId;
                    }
                } catch (Exception e) {
                    // 如果查询失败，使用默认值
                    nickname = "用户" + userId;
                }
                return new LeaderboardEntry(userId, nickname, defaultAvatar, inviteCount.intValue(), inviteCount.intValue());
            })
            .sorted((a, b) -> Integer.compare(b.getScore(), a.getScore()))
            .toList();
    }

    @org.springframework.cache.annotation.Cacheable(value = "activity_stats", key = "#activityId")
    public ActivityStatsResponse getActivityStats(Long activityId) {
        if (!activityRepository.existsById(activityId)) {
            throw new ActivityNotFoundException("活动不存在。");
        }

        List<com.mosquito.project.persistence.entity.DailyActivityStatsEntity> rows =
            dailyActivityStatsRepository.findByActivityIdOrderByStatDateAsc(activityId);

        long totalPv = 0L;
        long totalUv = 0L;
        long totalParticipants = 0L;
        long totalNewUsers = 0L;
        long totalShares = 0L;
        long totalConversions = 0L;

        List<ActivityStatsResponse.DailyStats> daily = new ArrayList<>();
        for (com.mosquito.project.persistence.entity.DailyActivityStatsEntity e : rows) {
            int pv = e.getViews() != null ? e.getViews() : 0;
            // UV: 从独立访客数字段获取真实UV（从link_clicks表按IP去重统计）
            int uv = e.getUniqueVisitors() != null ? e.getUniqueVisitors() : 0;
            int participants = e.getNewRegistrations() != null ? e.getNewRegistrations() : 0;
            // newUsers与participants使用同一数据源（新增注册用户）
            int newUsers = participants;
            int shares = e.getShares() != null ? e.getShares() : 0;
            int conversions = e.getConversions() != null ? e.getConversions() : 0;

            totalPv += pv;
            totalUv += uv;
            totalParticipants += participants;
            totalNewUsers += newUsers;
            totalShares += shares;
            totalConversions += conversions;

            // 按天的统计，使用完整参数构造函数
            daily.add(new ActivityStatsResponse.DailyStats(
                e.getStatDate().toString(), pv, uv, participants, newUsers, shares
            ));
        }

        // K因子 = 总分享数 / 新用户数 (病毒系数)
        double kFactor = totalNewUsers > 0 ? (double) totalShares / totalNewUsers : 0;

        // CAC = 总花费(奖励积分) / 新用户数 (客户获取成本)
        // 从奖励表获取真实成本
        double totalRewardCost = rewardService.getTotalRewardCostByActivity(activityId);
        double cac = totalNewUsers > 0 ? totalRewardCost / totalNewUsers : 0;

        return new ActivityStatsResponse(totalPv, totalUv, totalParticipants, totalNewUsers, totalShares, kFactor, cac, daily);
    }

    public String generateLeaderboardCsv(Long activityId) {
        return generateLeaderboardCsv(activityId, null);
    }

    public String generateLeaderboardCsv(Long activityId, Integer topN) {
        List<LeaderboardEntry> entries = getLeaderboard(activityId);
        int n = (topN == null || topN < 1) ? entries.size() : Math.min(topN, entries.size());

        try (java.io.StringWriter writer = new java.io.StringWriter();
             org.apache.commons.csv.CSVPrinter csvPrinter = new org.apache.commons.csv.CSVPrinter(writer,
                 org.apache.commons.csv.CSVFormat.DEFAULT.builder()
                     .setHeader("userId", "userName", "score")
                     .build())) {

            for (int i = 0; i < n; i++) {
                LeaderboardEntry e = entries.get(i);
                csvPrinter.printRecord(e.getUserId(), e.getUserName(), e.getScore());
            }
            csvPrinter.flush();
            return writer.toString();
        } catch (java.io.IOException e) {
            log.error("Failed to generate CSV", e);
            throw new RuntimeException("CSV生成失败", e);
        }
    }

    @org.springframework.cache.annotation.Cacheable(value = "activity_graph", key = "#activityId")
    public ActivityGraphResponse getActivityGraph(Long activityId) {
        if (!activityRepository.existsById(activityId)) {
            throw new ActivityNotFoundException("活动不存在。");
        }
        List<com.mosquito.project.persistence.entity.UserInviteEntity> invites = userInviteRepository.findByActivityId(activityId);
        Map<Long, String> userLabels = new HashMap<>();
        Map<Long, Integer> directInvitesCount = new HashMap<>();
        Map<Long, Integer> indirectInvitesCount = new HashMap<>();
        List<ActivityGraphResponse.Edge> edges = new ArrayList<>();

        // 构建邀请关系，计算直接邀请数
        for (com.mosquito.project.persistence.entity.UserInviteEntity inv : invites) {
            Long from = inv.getInviterUserId();
            Long to = inv.getInviteeUserId();
            userLabels.putIfAbsent(from, "用户" + from);
            userLabels.putIfAbsent(to, "用户" + to);
            edges.add(new ActivityGraphResponse.Edge(String.valueOf(from), String.valueOf(to)));

            // 直接邀请数 +1
            directInvitesCount.merge(from, 1, Integer::sum);
        }

        // 构建父子关系，用于计算间接邀请数
        Map<Long, java.util.List<Long>> childrenMap = new HashMap<>();
        for (com.mosquito.project.persistence.entity.UserInviteEntity inv : invites) {
            childrenMap.computeIfAbsent(inv.getInviterUserId(), k -> new ArrayList<>()).add(inv.getInviteeUserId());
        }

        // 递归计算间接邀请数（深度2及以上）
        for (Long userId : userLabels.keySet()) {
            int indirectCount = countIndirectInvites(userId, childrenMap, 2, new java.util.HashSet<>());
            indirectInvitesCount.put(userId, indirectCount);
        }

        List<ActivityGraphResponse.Node> nodes = new ArrayList<>();
        for (Map.Entry<Long, String> entry : userLabels.entrySet()) {
            Long userId = entry.getKey();
            int direct = directInvitesCount.getOrDefault(userId, 0);
            int indirect = indirectInvitesCount.getOrDefault(userId, 0);
            nodes.add(new ActivityGraphResponse.Node(String.valueOf(entry.getKey()), entry.getValue(), direct, indirect));
        }
        return new ActivityGraphResponse(nodes, edges);
    }

    /**
     * 递归计算间接邀请数（深度 >= 2）
     * depth >= 2 时继续递归，确保统计所有层级的间接邀请
     */
    private int countIndirectInvites(Long userId, Map<Long, java.util.List<Long>> childrenMap, int depth, java.util.Set<Long> visited) {
        if (depth < 2 || visited.contains(userId)) {
            return 0;
        }
        visited.add(userId);
        java.util.List<Long> children = childrenMap.getOrDefault(userId, java.util.Collections.emptyList());
        int count = 0;
        for (Long child : children) {
            // depth >= 2 时，当前子节点算作间接邀请
            count += 1;
            // 继续递归更深层次
            count += countIndirectInvites(child, childrenMap, depth - 1, visited);
        }
        return count;
    }

    @org.springframework.cache.annotation.Cacheable(value = "activity_graph", key = "#activityId + ':' + #rootUserId + ':' + #maxDepth + ':' + #limit")
    public ActivityGraphResponse getActivityGraph(Long activityId, Long rootUserId, Integer maxDepth, Integer limit) {
        if (!activityRepository.existsById(activityId)) {
            throw new ActivityNotFoundException("活动不存在。");
        }
        List<com.mosquito.project.persistence.entity.UserInviteEntity> invites = userInviteRepository.findByActivityId(activityId);

        // 构建直接邀请数统计
        Map<Long, Integer> directInvitesCount = new HashMap<>();
        // 构建父子关系，用于计算间接邀请数
        Map<Long, java.util.List<Long>> childrenMap = new HashMap<>();
        for (com.mosquito.project.persistence.entity.UserInviteEntity inv : invites) {
            Long from = inv.getInviterUserId();
            Long to = inv.getInviteeUserId();
            // 直接邀请数 +1
            directInvitesCount.merge(from, 1, Integer::sum);
            // 构建父子关系
            childrenMap.computeIfAbsent(from, k -> new ArrayList<>()).add(to);
        }

        // 递归计算间接邀请数（深度2及以上）
        Map<Long, Integer> indirectInvitesCount = new HashMap<>();
        Set<Long> allUserIds = new HashSet<>();
        for (com.mosquito.project.persistence.entity.UserInviteEntity inv : invites) {
            allUserIds.add(inv.getInviterUserId());
            allUserIds.add(inv.getInviteeUserId());
        }
        for (Long userId : allUserIds) {
            int indirectCount = countIndirectInvites(userId, childrenMap, 2, new java.util.HashSet<>());
            indirectInvitesCount.put(userId, indirectCount);
        }

        int maxDepthVal = (maxDepth == null || maxDepth < 1) ? 1 : maxDepth;
        int limitVal = (limit == null || limit < 1) ? 1000 : limit;

        List<ActivityGraphResponse.Edge> edges = new ArrayList<>();
        Map<Long, String> labels = new HashMap<>();
        java.util.Set<Long> seen = new java.util.HashSet<>();

        java.util.function.Consumer<Long> ensureLabel = (uid) -> labels.putIfAbsent(uid, "用户" + uid);

        if (rootUserId != null) {
            java.util.ArrayDeque<long[]> q = new java.util.ArrayDeque<>();
            q.add(new long[]{rootUserId, 0});
            seen.add(rootUserId);
            ensureLabel.accept(rootUserId);
            while (!q.isEmpty() && edges.size() < limitVal) {
                long[] cur = q.poll();
                long uid = cur[0];
                int depth = (int) cur[1];
                if (depth >= maxDepthVal) continue;
                List<Long> childs = childrenMap.getOrDefault(uid, java.util.Collections.emptyList());
                for (Long v : childs) {
                    edges.add(new ActivityGraphResponse.Edge(String.valueOf(uid), String.valueOf(v)));
                    ensureLabel.accept(v);
                    if (edges.size() >= limitVal) break;
                    if (seen.add(v)) {
                        q.add(new long[]{v, depth + 1});
                    }
                }
            }
        } else {
            for (com.mosquito.project.persistence.entity.UserInviteEntity inv : invites) {
                long from = inv.getInviterUserId();
                long to = inv.getInviteeUserId();
                edges.add(new ActivityGraphResponse.Edge(String.valueOf(from), String.valueOf(to)));
                ensureLabel.accept(from);
                ensureLabel.accept(to);
                if (edges.size() >= limitVal) break;
            }
        }

        List<ActivityGraphResponse.Node> nodes = new ArrayList<>();
        for (Map.Entry<Long, String> e : labels.entrySet()) {
            Long userId = e.getKey();
            int direct = directInvitesCount.getOrDefault(userId, 0);
            int indirect = indirectInvitesCount.getOrDefault(userId, 0);
            nodes.add(new ActivityGraphResponse.Node(String.valueOf(userId), e.getValue(), direct, indirect));
        }
        return new ActivityGraphResponse(nodes, edges);
    }

    /**
     * 发布活动 - 需要审批通过才能发布
     * PRD要求: "提交审批后发布"联动
     */
    @Caching(evict = {
        @CacheEvict(value = "leaderboards", allEntries = true),
        @CacheEvict(value = "activity_stats", allEntries = true)
    })
    @org.springframework.transaction.annotation.Transactional
    public Activity publishActivity(Long id) {
        ActivityEntity entity = activityRepository.findById(id)
            .orElseThrow(() -> new ActivityNotFoundException("活动不存在"));

        // 只有APPROVED(审批通过)或WAITING_PUBLISH(待发布)状态可以发布
        // 不再兼容旧的draft/paused状态，严格按PRD执行
        String currentStatus = entity.getStatus();
        boolean canPublish = ActivityStatus.canPublish(currentStatus);

        if (!canPublish) {
            throw new IllegalStateException("只有审批通过或待发布状态的活动可以发布");
        }

        entity.setStatus(ActivityStatus.RUNNING);
        entity.setUpdatedAt(java.time.OffsetDateTime.now(ZoneOffset.UTC));
        entity = activityRepository.save(entity);

        log.info("活动已发布: id={}, status={}", id, ActivityStatus.RUNNING);
        return toActivity(entity);
    }

    /**
     * 提交活动审批
     * PRD要求: 活动"提交审批后发布"联动
     * 接入统一审批流服务
     */
    @org.springframework.transaction.annotation.Transactional
    public Activity submitForApproval(Long id, Long applicantId) {
        ActivityEntity entity = activityRepository.findById(id)
            .orElseThrow(() -> new ActivityNotFoundException("活动不存在"));

        String currentStatus = entity.getStatus();
        if (!ActivityStatus.canSubmitForApproval(currentStatus)
            && !ActivityStatus.DRAFT_LEGACY.equals(currentStatus)) {
            throw new IllegalStateException("只有草稿或被拒绝状态的活动可以提交审批");
        }

        // 1. 设置活动状态为审批中
        entity.setStatus(ActivityStatus.IN_APPROVAL);
        entity.setUpdatedAt(java.time.OffsetDateTime.now(ZoneOffset.UTC));
        entity = activityRepository.save(entity);

        // 2. 根据活动当前状态确定业务类型
        // DRAFT/PAUSED -> 首次提交 -> ACTIVITY_CREATE
        // REJECTED/WAITING_PUBLISH/APPROVED -> 重新提交/修改 -> ACTIVITY_UPDATE
        String bizType;
        if (ActivityStatus.DRAFT_LEGACY.equals(currentStatus) || ActivityStatus.DRAFT.equals(currentStatus)) {
            bizType = BIZ_TYPE_ACTIVITY_CREATE;
        } else {
            bizType = BIZ_TYPE_ACTIVITY_UPDATE;
        }

        // 3. 调用统一审批流服务创建审批记录
        try {
            Map<String, Object> approvalResult = approvalFlowService.submitApprovalByEvent(
                "ACTIVITY_SUBMIT",  // 触发事件
                bizType,           // 业务类型
                id,                // 业务ID
                "活动审批: " + entity.getName(), // 审批标题
                applicantId,       // 申请人ID
                "提交活动审批"      // 申请原因
            );
            log.info("活动已提交审批并创建审批记录: activityId={}, recordId={}, status={}",
                id, approvalResult.get("recordId"), entity.getStatus());
        } catch (Exception e) {
            log.error("创建审批记录失败，拒绝执行敏感操作: activityId={}, error={}", id, e.getMessage());
            // 审批失败，拒绝执行敏感操作（Fail-Closed），回滚状态
            entity.setStatus(ActivityStatus.DRAFT);
            entity = activityRepository.save(entity);
            throw new RuntimeException("审批服务不可用，无法执行敏感操作，请稍后重试");
        }

        log.info("活动已提交审批: id={}, status={}", id, entity.getStatus());
        return toActivity(entity);
    }

    /**
     * 审批通过活动
     * 审批通过后可选择立即发布或待发布
     */
    @org.springframework.transaction.annotation.Transactional
    public Activity approveActivity(Long id, boolean autoPublish) {
        ActivityEntity entity = activityRepository.findById(id)
            .orElseThrow(() -> new ActivityNotFoundException("活动不存在"));

        if (!ActivityStatus.PENDING.equals(entity.getStatus())
            && !ActivityStatus.IN_APPROVAL.equals(entity.getStatus())) {
            throw new IllegalStateException("只有待审批或审批中的活动可以审批");
        }

        if (autoPublish) {
            entity.setStatus(ActivityStatus.RUNNING);
        } else {
            entity.setStatus(ActivityStatus.WAITING_PUBLISH);
        }
        entity.setUpdatedAt(java.time.OffsetDateTime.now(ZoneOffset.UTC));
        entity = activityRepository.save(entity);

        log.info("活动审批通过: id={}, status={}, autoPublish={}", id, entity.getStatus(), autoPublish);
        return toActivity(entity);
    }

    /**
     * 拒绝活动
     */
    @org.springframework.transaction.annotation.Transactional
    public Activity rejectActivity(Long id, String reason) {
        ActivityEntity entity = activityRepository.findById(id)
            .orElseThrow(() -> new ActivityNotFoundException("活动不存在"));

        if (!ActivityStatus.PENDING.equals(entity.getStatus())
            && !ActivityStatus.IN_APPROVAL.equals(entity.getStatus())) {
            throw new IllegalStateException("只有待审批或审批中的活动可以拒绝");
        }

        entity.setStatus(ActivityStatus.REJECTED);
        entity.setUpdatedAt(java.time.OffsetDateTime.now(ZoneOffset.UTC));
        entity = activityRepository.save(entity);

        log.info("活动审批被拒绝: id={}, reason={}", id, reason);
        return toActivity(entity);
    }

    /**
     * 暂停活动
     */
    @Caching(evict = {
        @CacheEvict(value = "leaderboards", allEntries = true),
        @CacheEvict(value = "activity_stats", allEntries = true)
    })
    @org.springframework.transaction.annotation.Transactional
    public Activity pauseActivity(Long id) {
        ActivityEntity entity = activityRepository.findById(id)
            .orElseThrow(() -> new ActivityNotFoundException("活动不存在"));

        if (!ActivityStatus.RUNNING.equals(entity.getStatus())) {
            throw new IllegalStateException("只有运行中状态的活动可以暂停");
        }

        entity.setStatus(ActivityStatus.PAUSED);
        entity.setUpdatedAt(java.time.OffsetDateTime.now(ZoneOffset.UTC));
        entity = activityRepository.save(entity);

        return toActivity(entity);
    }

    /**
     * 恢复活动
     */
    @Caching(evict = {
        @CacheEvict(value = "leaderboards", allEntries = true),
        @CacheEvict(value = "activity_stats", allEntries = true)
    })
    @org.springframework.transaction.annotation.Transactional
    public Activity resumeActivity(Long id) {
        ActivityEntity entity = activityRepository.findById(id)
            .orElseThrow(() -> new ActivityNotFoundException("活动不存在"));

        if (!ActivityStatus.PAUSED.equals(entity.getStatus())) {
            throw new IllegalStateException("只有暂停状态的活动可以恢复");
        }

        entity.setStatus(ActivityStatus.RUNNING);
        entity.setUpdatedAt(java.time.OffsetDateTime.now(ZoneOffset.UTC));
        entity = activityRepository.save(entity);

        return toActivity(entity);
    }

    /**
     * 结束活动
     */
    @Caching(evict = {
        @CacheEvict(value = "leaderboards", allEntries = true),
        @CacheEvict(value = "activity_stats", allEntries = true)
    })
    @org.springframework.transaction.annotation.Transactional
    public Activity endActivity(Long id) {
        ActivityEntity entity = activityRepository.findById(id)
            .orElseThrow(() -> new ActivityNotFoundException("活动不存在"));

        if (!ActivityStatus.RUNNING.equals(entity.getStatus()) && !ActivityStatus.PAUSED.equals(entity.getStatus())) {
            throw new IllegalStateException("只有运行中或暂停状态的活动可以结束");
        }

        entity.setStatus(ActivityStatus.ENDED);
        entity.setUpdatedAt(java.time.OffsetDateTime.now(ZoneOffset.UTC));
        entity = activityRepository.save(entity);

        return toActivity(entity);
    }

    /**
     * 删除活动（软删除）
     * - 草稿状态（DRAFT）：直接删除（PRD 1.3规则）
     * - 运行中状态（RUNNING）：需要审批通过后删除
     * - 已结束状态（ENDED）：需要审批通过后删除
     * - 已归档/已删除（ARCHIVED/DELETED）：禁止重复删除
     * @param id 活动ID
     * @param currentUserId 当前操作人ID（申请人）
     */
    @Caching(evict = {
        @CacheEvict(value = "leaderboards", allEntries = true),
        @CacheEvict(value = "activity_stats", allEntries = true)
    })
    @org.springframework.transaction.annotation.Transactional
    public void deleteActivity(Long id, Long currentUserId) {
        ActivityEntity entity = activityRepository.findById(id)
            .orElseThrow(() -> new ActivityNotFoundException("活动不存在"));

        // 已删除/已归档状态不允许重复删除
        if (ActivityStatus.DELETED.equals(entity.getStatus()) || ActivityStatus.ARCHIVED.equals(entity.getStatus())) {
            throw new BusinessException("活动已删除，不能重复操作",
                org.springframework.http.HttpStatus.BAD_REQUEST, "ACTIVITY_DELETE_NOT_ALLOWED");
        }

        // PRD 1.3规则：草稿状态可直接删除；运行中/已结束需审批
        if (ActivityStatus.DRAFT.equals(entity.getStatus())) {
            // 草稿直接删除
            entity.setStatus(ActivityStatus.DELETED);
            entity.setUpdatedAt(java.time.OffsetDateTime.now(ZoneOffset.UTC));
            activityRepository.save(entity);
            log.info("草稿活动直接删除: activityId={}, operator={}", id, currentUserId);
            return;
        }

        // 运行中、已结束状态需要审批
        try {
            String reason = switch (entity.getStatus()) {
                case ActivityStatus.RUNNING -> "活动进行中，申请删除";
                case ActivityStatus.ENDED -> "活动已结束，申请删除";
                default -> "申请删除活动";
            };

            Map<String, Object> approvalResult = approvalFlowService.submitApprovalByEvent(
                "ACTIVITY_DELETE",  // 触发事件
                BIZ_TYPE_ACTIVITY_DELETE,  // 业务类型
                id,  // 业务ID
                "活动删除审批: " + entity.getName(),  // 审批标题
                currentUserId,  // 申请人ID（当前操作人，而非活动创建人）
                reason  // 申请原因
            );
            log.info("活动删除已提交审批: activityId={}, recordId={}, applicant={}, status={}",
                id, approvalResult.get("recordId"), currentUserId, entity.getStatus());
            // 返回活动信息，审批通过后会自动删除
            return;
        } catch (Exception e) {
            log.error("提交活动删除审批失败: {}", e.getMessage());
            throw new BusinessException("提交删除审批失败，请稍后重试");
        }
    }

    /**
     * 应用待审批的活动删除（审批通过后调用）
     */
    @Caching(evict = {
        @CacheEvict(value = "leaderboards", allEntries = true),
        @CacheEvict(value = "activity_stats", allEntries = true)
    })
    @org.springframework.transaction.annotation.Transactional
    public void applyActivityDelete(Long id) {
        ActivityEntity entity = activityRepository.findById(id)
            .orElseThrow(() -> new ActivityNotFoundException("活动不存在"));

        entity.setStatus(ActivityStatus.DELETED);
        entity.setUpdatedAt(java.time.OffsetDateTime.now(ZoneOffset.UTC));
        activityRepository.save(entity);
        log.info("活动删除已执行: activityId={}", id);
    }

    /**
     * 处理过期活动 - 将已过期活动自动结束
     * 由定时任务调用
     */
    public int processExpiredActivities() {
        java.time.OffsetDateTime now = java.time.OffsetDateTime.now(ZoneOffset.UTC);
        List<ActivityEntity> expiredActivities = activityRepository.findExpiredActivities(now);

        for (ActivityEntity entity : expiredActivities) {
            entity.setStatus(ActivityStatus.ENDED);
            entity.setUpdatedAt(now);
            activityRepository.save(entity);
        }

        return expiredActivities.size();
    }

    /**
     * 归档已结束的活动
     */
    public void archiveActivity(Long id) {
        ActivityEntity entity = activityRepository.findById(id)
            .orElseThrow(() -> new ActivityNotFoundException("活动不存在"));

        if (!ActivityStatus.ENDED.equals(entity.getStatus())) {
            throw new BusinessException("只有已结束的活动可以归档",
                org.springframework.http.HttpStatus.BAD_REQUEST, "ACTIVITY_ARCHIVE_NOT_ALLOWED");
        }

        entity.setStatus(ActivityStatus.ARCHIVED);
        entity.setUpdatedAt(java.time.OffsetDateTime.now(ZoneOffset.UTC));
        activityRepository.save(entity);
    }

    /**
     * 复制活动
     * 将现有活动复制为新草稿，继承关键配置但重置运行态字段
     */
    public Activity cloneActivity(Long sourceId, String newName, Long currentUserId, Long currentUserDeptId) {
        ActivityEntity source = activityRepository.findById(sourceId)
            .orElseThrow(() -> new ActivityNotFoundException("活动不存在"));

        // 创建新活动实体
        ActivityEntity cloned = new ActivityEntity();
        cloned.setName(newName != null && !newName.isEmpty() ? newName : source.getName() + "（复制）");

        // 复制时间配置
        cloned.setStartTimeUtc(source.getStartTimeUtc());
        cloned.setEndTimeUtc(source.getEndTimeUtc());

        // 设置为草稿状态
        cloned.setStatus(ActivityStatus.DRAFT);
        cloned.setCreatorId(currentUserId);
        cloned.setDepartmentId(currentUserDeptId);

        // 继承配置字段
        cloned.setTargetUsersConfig(source.getTargetUsersConfig());
        cloned.setPageContentConfig(source.getPageContentConfig());
        cloned.setRewardTiersConfig(source.getRewardTiersConfig());
        cloned.setRewardCalculationMode(source.getRewardCalculationMode());
        cloned.setMultiLevelRewardConfig(source.getMultiLevelRewardConfig());

        // 设置创建时间
        java.time.OffsetDateTime now = java.time.OffsetDateTime.now(java.time.ZoneOffset.UTC);
        cloned.setCreatedAt(now);
        cloned.setUpdatedAt(now);

        ActivityEntity saved = activityRepository.save(cloned);
        return toActivity(saved);
    }

    /**
     * 转换为Activity领域对象
     * 包含四大配置字段的映射
     */
    private Activity toActivity(ActivityEntity entity) {
        Activity activity = new Activity();
        activity.setId(entity.getId());
        activity.setName(entity.getName());
        if (entity.getStartTimeUtc() != null) {
            activity.setStartTime(entity.getStartTimeUtc().toZonedDateTime());
        }
        if (entity.getEndTimeUtc() != null) {
            activity.setEndTime(entity.getEndTimeUtc().toZonedDateTime());
        }
        // 状态字段
        if (entity.getStatus() != null) {
            activity.setStatus(entity.getStatus());
        }
        // 四大配置字段
        activity.setTargetUsersConfig(entity.getTargetUsersConfig());
        activity.setPageContentConfig(entity.getPageContentConfig());
        activity.setRewardTiersConfig(entity.getRewardTiersConfig());
        activity.setRewardCalculationMode(entity.getRewardCalculationMode());
        activity.setMultiLevelRewardConfig(entity.getMultiLevelRewardConfig());
        // 解析目标人群配置到targetUserIds（支持userIds数组格式）
        activity.setTargetUserIds(parseTargetUsersConfig(entity.getTargetUsersConfig()));
        // 数据权限字段
        activity.setCreatorId(entity.getCreatorId());
        activity.setDepartmentId(entity.getDepartmentId());
        return activity;
    }

    /**
     * 解析目标人群配置JSON到用户ID集合
     * 支持格式: {"userIds":[1001,1002]}
     * @param targetUsersConfig JSON配置字符串
     * @return 用户ID集合，如果解析失败或为空则返回null
     */
    private Set<Long> parseTargetUsersConfig(String targetUsersConfig) {
        if (targetUsersConfig == null || targetUsersConfig.isBlank()) {
            return null;
        }
        try {
            Map<String, Object> config = objectMapper.readValue(targetUsersConfig, new com.fasterxml.jackson.core.type.TypeReference<Map<String, Object>>() {});
            Object userIdsObj = config.get("userIds");
            if (userIdsObj instanceof List) {
                @SuppressWarnings("unchecked")
                List<Number> userIdsList = (List<Number>) userIdsObj;
                Set<Long> targetUserIds = new java.util.HashSet<>();
                for (Number id : userIdsList) {
                    targetUserIds.add(id.longValue());
                }
                return targetUserIds.isEmpty() ? null : targetUserIds;
            }
            return null;
        } catch (Exception e) {
            log.warn("解析目标人群配置失败: {}, error: {}", targetUsersConfig, e.getMessage());
            return null;
        }
    }

    /**
     * 批量发布活动
     */
    public Map<String, Object> batchPublish(List<Long> activityIds) {
        return batchOperation(activityIds, "publish");
    }

    /**
     * 批量暂停活动
     */
    public Map<String, Object> batchPause(List<Long> activityIds) {
        return batchOperation(activityIds, "pause");
    }

    /**
     * 批量结束活动
     */
    public Map<String, Object> batchEnd(List<Long> activityIds) {
        return batchOperation(activityIds, "end");
    }

    /**
     * 批量操作通用方法
     */
    private Map<String, Object> batchOperation(List<Long> activityIds, String operation) {
        List<Long> successIds = new java.util.ArrayList<>();
        List<Map<String, Object>> failures = new java.util.ArrayList<>();

        for (Long id : activityIds) {
            try {
                switch (operation) {
                    case "publish":
                        publishActivity(id);
                        break;
                    case "pause":
                        pauseActivity(id);
                        break;
                    case "end":
                        endActivity(id);
                        break;
                    default:
                        throw new BusinessException("未知操作: " + operation,
                            org.springframework.http.HttpStatus.BAD_REQUEST, "UNKNOWN_OPERATION");
                }
                successIds.add(id);
            } catch (Exception e) {
                Map<String, Object> failure = new HashMap<>();
                failure.put("id", id);
                failure.put("reason", e.getMessage());
                failures.add(failure);
            }
        }

        Map<String, Object> result = new HashMap<>();
        result.put("successCount", successIds.size());
        result.put("failureCount", failures.size());
        result.put("successIds", successIds);
        result.put("failures", failures);
        return result;
    }
}
