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.UpdateActivityRequest;
import com.mosquito.project.dto.ActivityStatsResponse;
import com.mosquito.project.dto.ActivityGraphResponse;
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 org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import com.mosquito.project.persistence.entity.ActivityEntity;
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.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

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 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 = 30 * 1024 * 1024;
    private static final List<String> SUPPORTED_IMAGE_TYPES = List.of("image/jpeg", "image/png");

    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 ApiKeyEncryptionService encryptionService;
    private final com.mosquito.project.config.AppConfig appConfig;
    private static final int KEY_PREFIX_LEN = 12;

    public ActivityService(DelayProvider delayProvider, ActivityRepository activityRepository, ApiKeyRepository apiKeyRepository, com.mosquito.project.persistence.repository.DailyActivityStatsRepository dailyActivityStatsRepository, com.mosquito.project.persistence.repository.UserInviteRepository userInviteRepository, ApiKeyEncryptionService encryptionService, com.mosquito.project.config.AppConfig appConfig) {
        this.delayProvider = delayProvider;
        this.activityRepository = activityRepository;
        this.apiKeyRepository = apiKeyRepository;
        this.dailyActivityStatsRepository = dailyActivityStatsRepository;
        this.userInviteRepository = userInviteRepository;
        this.encryptionService = encryptionService;
        this.appConfig = appConfig;
    }

    @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) {
        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.setRewardCalculationMode("delta");
        entity.setStatus("draft");
        entity.setCreatedAt(java.time.OffsetDateTime.now(ZoneOffset.UTC));
        entity.setUpdatedAt(java.time.OffsetDateTime.now(ZoneOffset.UTC));
        entity = activityRepository.save(entity);

        Activity activity = new Activity();
        activity.setId(entity.getId());
        activity.setName(request.getName());
        activity.setStartTime(request.getStartTime());
        activity.setEndTime(request.getEndTime());
        return activity;
    }

    @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("活动结束时间不能早于开始时间。");
        }

        entity.setName(request.getName());
        entity.setStartTimeUtc(request.getStartTime().withZoneSameInstant(ZoneOffset.UTC).toOffsetDateTime());
        entity.setEndTimeUtc(request.getEndTime().withZoneSameInstant(ZoneOffset.UTC).toOffsetDateTime());
        entity.setUpdatedAt(java.time.OffsetDateTime.now(ZoneOffset.UTC));
        activityRepository.save(entity);

        Activity activity = new Activity();
        activity.setId(entity.getId());
        activity.setName(request.getName());
        activity.setStartTime(request.getStartTime());
        activity.setEndTime(request.getEndTime());
        return activity;
    }

    public Activity getActivityById(Long id) {
        ActivityEntity entity = activityRepository.findById(id)
            .orElseThrow(() -> new ActivityNotFoundException("活动不存在。"));
        Activity activity = new Activity();
        activity.setId(entity.getId());
        activity.setName(entity.getName());
        activity.setStartTime(entity.getStartTimeUtc().atZoneSameInstant(ZoneOffset.UTC));
        activity.setEndTime(entity.getEndTimeUtc().atZoneSameInstant(ZoneOffset.UTC));
        return activity;
    }

    public List<Activity> getAllActivities() {
        List<Activity> result = new ArrayList<>();
        for (ActivityEntity e : activityRepository.findAll()) {
            Activity a = new Activity();
            a.setId(e.getId());
            a.setName(e.getName());
            a.setStartTime(e.getStartTimeUtc().atZoneSameInstant(ZoneOffset.UTC));
            a.setEndTime(e.getEndTimeUtc().atZoneSameInstant(ZoneOffset.UTC));
            result.add(a);
        }
        return result;
    }

    @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密钥已吊销或无效。");
        }
        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密钥已吊销或无效。");
        }
        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不能为空。");
            }
            log.warn("Coupon validation not yet implemented. CouponBatchId: {}. " +
                    "To skip validation, call with skipValidation=true.", reward.getCouponBatchId());
            throw new UnsupportedOperationException(
                "优惠券验证功能尚未实现。请联系管理员配置优惠券批次或使用skipValidation=true参数。"
            );
        }
    }

    @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密钥已吊销，无法显示。");
        }
        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;
    }

    @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();
                return new LeaderboardEntry(userId, "用户" + userId, 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 totalParticipants = 0L;
        long totalShares = 0L;
        List<ActivityStatsResponse.DailyStats> daily = new ArrayList<>();
        for (com.mosquito.project.persistence.entity.DailyActivityStatsEntity e : rows) {
            int participants = e.getNewRegistrations() != null ? e.getNewRegistrations() : 0;
            int shares = e.getShares() != null ? e.getShares() : 0;
            totalParticipants += participants;
            totalShares += shares;
            daily.add(new ActivityStatsResponse.DailyStats(
                e.getStatDate().toString(), participants, shares
            ));
        }
        return new ActivityStatsResponse(totalParticipants, totalShares, 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<>();
        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)));
        }
        List<ActivityGraphResponse.Node> nodes = new ArrayList<>();
        for (Map.Entry<Long, String> entry : userLabels.entrySet()) {
            nodes.add(new ActivityGraphResponse.Node(String.valueOf(entry.getKey()), entry.getValue()));
        }
        return new ActivityGraphResponse(nodes, edges);
    }

    @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, java.util.List<Long>> children = new HashMap<>();
        for (com.mosquito.project.persistence.entity.UserInviteEntity inv : invites) {
            children.computeIfAbsent(inv.getInviterUserId(), k -> new ArrayList<>()).add(inv.getInviteeUserId());
        }

        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 = children.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()) {
            nodes.add(new ActivityGraphResponse.Node(String.valueOf(e.getKey()), e.getValue()));
        }
        return new ActivityGraphResponse(nodes, edges);
    }
}
