package com.ai.da.common.utils;

import com.ai.da.model.dto.ProgressDTO;
import com.ai.da.python.vo.DesignPythonObject;
import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.netty.util.internal.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Slf4j
@Component
public class RedisUtil {

    @Resource
    private RedisTemplate<String, String> redisTemplate;

    public final static String FLUX_POLLING_URL = "Flux:";

    public Boolean hasKey(String key){
        return redisTemplate.hasKey(key);
    }

    //- - - - - - - - - - - - - - - - - - - - -  ZSet类型 - - - - - - - - - - - - - - - - - - - -

    /**
     * 向ZSet中添加元素
     */
    public void addToZSet(String key, String value, Double score) {
        redisTemplate.opsForZSet().add(key, value, score);
    }

    /**
     * 从ZSet中删除元素
     */
    public void removeFromZSet(String key, String value) {
        redisTemplate.opsForZSet().remove(key, value);
    }

    /**
     * 获取指定元素的当前排列顺序
     */
    public Long getRank(String key, String value) {
        return redisTemplate.opsForZSet().rank(key, value);
    }

    /**
     * 获取当前ZSet中的最大score
     */
    public Double getMaxScore(String key) {
        Set<ZSetOperations.TypedTuple<String>> set = redisTemplate.opsForZSet().reverseRangeWithScores(key, 0, 0);

        if (!CollectionUtils.isEmpty(set)) {
            Double score = set.iterator().next().getScore();
            return score + 1.0;
        } else {
            return 1.0;
        }
    }

    /**
     * 判断元素是否存在
     */
    public Boolean isElementExistsInZSet(String key, String value) {
        return redisTemplate.opsForZSet().score(key, value) != null;
    }

    /**
     * 获取当前ZSet中数据量的总和
     */
    public Long getZSetTotalCount(String key) {
        return redisTemplate.opsForZSet().zCard(key);
    }


    public Set<String> getZSetTotalData(String key){
        return redisTemplate.opsForZSet().range(key, 0, -1);
    }

    //- - - - - - - - - - - - - - - - - - - - -  set类型 - - - - - - - - - - - - - - - - - - - -

    public final static String VIDEO_FINISHED_TASKS = "VideoFinishedTasks";

    /**
     * 将数据放入set缓存
     */
    public void addToSet(String key, String value, Long expiresIn) {
        redisTemplate.opsForSet().add(key, value);
        // 设置过期时间
        redisTemplate.expire(key, expiresIn, TimeUnit.SECONDS);
    }

    /**
     * 弹出变量中的元素
     */
    public void removeFromSet(String key, String value) {
        redisTemplate.opsForSet().remove(key, value);
    }

    /**
     * 检查给定的元素是否在变量中。
     */
    public Boolean isElementExistsInSet(String key, String obj) {
        return redisTemplate.opsForSet().isMember(key, obj);
    }


    //- - - - - - - - - - - - - - - - - - - - -  hash类型 - - - - - - - - - - - - - - - - - - - -

    /**
     * 加入缓存
     */
    public void addToMap(String key, Map<String, String> map) {
        redisTemplate.opsForHash().putAll(key, map);
    }

    /**
     * 验证指定 key 下 有没有指定的 hashkey
     */
    public Boolean isElementExistsInMap(String key, String hashKey) {
        return redisTemplate.opsForHash().hasKey(key, hashKey);
    }

    /**
     * 获取指定key的值string
     */
    public String getMapValue(String key1, String key2) {
        return String.valueOf(redisTemplate.opsForHash().get(key1, key2));
    }

    /**
     * 删除指定 hash 的 HashKey
     *
     * @return 删除成功的 数量
     */
    public Long removeFromMap(String key, String hashKeys) {
        return redisTemplate.opsForHash().delete(key, hashKeys);
    }

    //- - - - - - - - - - - - - - - - - - - - -  String类型 - - - - - - - - - - - - - - - - - - - -
    public void addToString(String key, String value){
        redisTemplate.opsForValue().set(key,value);
    }

    public void addToString(String key, String value, Long expiresIn){
        redisTemplate.opsForValue().set(key,value,expiresIn, TimeUnit.SECONDS);
    }

    public String getFromString(String key){
        return redisTemplate.opsForValue().get(key);
    }

    public Set<String> getKeysFromString(String key){
        return redisTemplate.keys(key);
    }

    public Long getSize(String key){return redisTemplate.opsForSet().size(key);}

    public List<String> getMultiValue(Set<String> keys){
        return redisTemplate.opsForValue().multiGet(keys);
    }

    public Long getExpire(String key){
        return redisTemplate.getExpire(key);
    }

    public void removeFromString(String key){
        redisTemplate.delete(key);
    }

    public final static String PORTFOLIO_LIKE_KEY = "portfolio:like:";

    public void likePost(Long portfolioId, Long userId) {
        redisTemplate.opsForSet().add(PORTFOLIO_LIKE_KEY + portfolioId, String.valueOf(userId));
    }

    public Long getLikeCount(Long portfolioId) {
        String key = PORTFOLIO_LIKE_KEY + portfolioId;
        return redisTemplate.opsForSet().size(key);
    }

    public List<Long> getLikedPortfolios(Long userId) {
        // 获取所有包含PORTFOLIO_LIKE_KEY的键
        Set<String> likedPortfolios = redisTemplate.keys(PORTFOLIO_LIKE_KEY + "*");

        // 如果没有喜欢的，返回空列表
        if (likedPortfolios == null || likedPortfolios.isEmpty()) {
            return new ArrayList<>();
        }

        // 过滤出包含指定用户ID的键，并提取投资组合ID
        return likedPortfolios.stream()
                .filter(key -> redisTemplate.opsForSet().isMember(key, String.valueOf(userId)))
                .map(key -> Long.valueOf(key.replace(PORTFOLIO_LIKE_KEY, "")))
                .collect(Collectors.toList());
    }

    public void unLikePost(Long portfolioId, Long userId) {
        redisTemplate.opsForSet().remove(PORTFOLIO_LIKE_KEY + portfolioId, userId.toString());
    }

    // 检查用户是否喜欢某个作品
    public boolean isPostLikedByUser(Long portfolioId, Long userId) {
        String key = PORTFOLIO_LIKE_KEY + portfolioId;
        Boolean isMember = redisTemplate.opsForSet().isMember(key, userId.toString());
        return isMember != null && isMember;
    }

    public final static String PORTFOLIO_VIEW_KEY = "portfolio:view:";

    public void increaseViewCount(Long portfolioId) {
        String key = PORTFOLIO_VIEW_KEY + portfolioId;
        redisTemplate.opsForValue().increment(key);
    }

    public Long getViewCount(Long portfolioId) {
        String key = PORTFOLIO_VIEW_KEY + portfolioId;
        return redisTemplate.opsForValue().increment(key, 0);
    }

    public Long getViewCount(String key) {
        Object value = redisTemplate.opsForValue().get(key);
        if (value instanceof Integer) {
            return Long.valueOf((Integer) value);
        } else if (value instanceof Long) {
            return (Long) value;
        } else if (value instanceof String) {
            return Long.valueOf((String) value);
        } else {
            throw new IllegalArgumentException("Unexpected value type");
        }
    }

    public final static String PERSONAL_HOMEPAGE_VIEW_KEY = "PersonalHomepage:view:";

    public void increasePersonalHomepageViewCount(Long accountId) {
        String key = PERSONAL_HOMEPAGE_VIEW_KEY + accountId;
        redisTemplate.opsForValue().increment(key);
    }

    public Long getPersonalHomepageViewCount(Long accountId) {
        String key = PERSONAL_HOMEPAGE_VIEW_KEY + accountId;
        return redisTemplate.opsForValue().increment(key, 0);
    }

    public final static String MOODBOARD_POSITION_KEY = "moodboard:position:";

    public void saveMoodboardPosition(Long id, String moodboardPosition) {
        addToString(MOODBOARD_POSITION_KEY + id, moodboardPosition);
    }

    public String getMoodboardPosition(Long id) {
        return getFromString(MOODBOARD_POSITION_KEY + id);
    }
    public final static String NICKNAME_MODIFY_TIMES = "NicknameModifyTimes:";
    public final static String UNNAMED_PROJECT_SEQ = "Project:UnnamedProjectSeq:";
    public Long increaseCount(String key) {
        return redisTemplate.opsForValue().increment(key);
    }

    public Long getIncrementCount(String key) {
        return redisTemplate.opsForValue().increment(key, 0);
    }

    public void setKeyExpire(String key, Long expire) {
        redisTemplate.expire(key, expire, TimeUnit.DAYS);
    }

    public final static String CHANGE_MAILBOX = "ChangeMailbox:";

    // 每天允许通知3次
    public final static String UPLOAD_TIMEOUT_REMINDER_COUNTER = "UploadTimeoutReminderCounter";

    public void addProcessId(String processId, int progress) {
        // Redis 中的键，可以通过 processId 来唯一标识
        String redisKey = "process:progress:" + processId;

        // 将当前进度存储到 Redis
        redisTemplate.opsForValue().set(redisKey, String.valueOf(progress));

        // 设置过期时间为 5 分钟（300 秒）
        redisTemplate.expire(redisKey, 5, TimeUnit.MINUTES);
    }

    public void addPathToCache(Long collectionId, Long userId, String path) {
        // Redis 中的键，唯一标识由 collectionId 和 userId 组成
        String redisKey = "path:cache:" + collectionId + ":" + userId;

        // 增加路径的计数
        redisTemplate.opsForHash().increment(redisKey, path, 1);

        // 设置过期时间为 2 小时（7200 秒）
        redisTemplate.expire(redisKey, 8, TimeUnit.HOURS);
    }

    public int getPathUsageCount(Long collectionId, Long userId, String path) {
        String redisKey = "path:cache:" + collectionId + ":" + userId;

        // 获取路径的使用次数
        Object count = redisTemplate.opsForHash().get(redisKey, path);
        return count != null ? Integer.parseInt(count.toString()) : 0;
    }

    public void addAssembledObjects(Long collectionId, Set<DesignPythonObject> assembledObjects) {
        // Redis 中的键，使用 collectionId 来唯一标识
        String redisKey = "collection:assembledObjects:" + collectionId;

        // 将 assembledObjects 转换为 JSON 格式存储，避免直接存储对象
        String assembledObjectsJson = convertToJson(assembledObjects);

        // 使用 Redis 的 set 操作更新集合
        redisTemplate.opsForValue().set(redisKey, assembledObjectsJson);

        // 设置过期时间为 5 分钟（300 秒）
        redisTemplate.expire(redisKey, 30, TimeUnit.MINUTES);
    }

    // 将 Set<DesignPythonObject> 转换为 JSON 格式
    private String convertToJson(Set<DesignPythonObject> assembledObjects) {
        try {
            ObjectMapper objectMapper = new ObjectMapper();
            return objectMapper.writeValueAsString(assembledObjects);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
            return null;
        }
    }

    public Set<DesignPythonObject> getAssembledObjects(Long collectionId) {
        // Redis 中的键，使用 collectionId 来唯一标识
        String redisKey = "collection:assembledObjects:" + collectionId;

        // 从 Redis 获取存储的 JSON 字符串
        String assembledObjectsJson = (String) redisTemplate.opsForValue().get(redisKey);

        if (assembledObjectsJson == null) {
            return new HashSet<>(); // 如果没有找到数据，返回一个空的 Set
        }

        // 将 JSON 字符串转换为 Set<DesignPythonObject>
        return convertFromJson(assembledObjectsJson);
    }

    // 将 JSON 字符串转换为 Set<DesignPythonObject>
    private Set<DesignPythonObject> convertFromJson(String json) {
        try {
            ObjectMapper objectMapper = new ObjectMapper();
            // 使用 TypeReference 来指定目标类型是 Set<DesignPythonObject>
            return objectMapper.readValue(json, new TypeReference<Set<DesignPythonObject>>() {});
        } catch (JsonProcessingException e) {
            e.printStackTrace();
            return new HashSet<>(); // 如果转换失败，返回空的 Set
        }
    }

    public final static String PAYMENT_INFO_LAST_SCAN_TIME = "PaymentInfoLastScanTime";

    public final static String AFFILIATE_LINK_VIEW_KEY = "AffiliateLink:view:";

    public void increaseAffiliateLinkViewCount(Long accountId) {
        String key = AFFILIATE_LINK_VIEW_KEY + accountId;
        redisTemplate.opsForValue().increment(key);
    }

    public Long getAffiliateLinkViewCount(Long accountId) {
        String key = AFFILIATE_LINK_VIEW_KEY + accountId;
        return redisTemplate.opsForValue().increment(key, 0);
    }

    /**
     * 记录任务的耗时到Redis
     * @param taskKey 任务标识，如 "taskA"
     * @param elapsedTime 本次耗时，单位为毫秒
     */
    public void recordTaskElapsedTime(String taskKey, long elapsedTime) {
        String hashKey = "task:stats";

        // 累加总耗时
        redisTemplate.opsForHash().increment(hashKey, taskKey + ":totalTime", elapsedTime);

        // 增加计数器
        redisTemplate.opsForHash().increment(hashKey, taskKey + ":count", 1);
    }

    /**
     * 获取任务的平均耗时
     * @param taskKey 任务标识，如 "taskA"
     * @return 平均耗时（毫秒）
     */
    public double getTaskAverageTime(String taskKey) {
        String hashKey = "task:stats";

        // 获取总耗时和计数
        Object totalTime = redisTemplate.opsForHash().get(hashKey, taskKey + ":totalTime");
        Object count = redisTemplate.opsForHash().get(hashKey, taskKey + ":count");

        // 计算平均值
        if (totalTime == null || count == null) {
            return 0;
        }
        return Double.parseDouble(totalTime.toString()) / Long.parseLong(count.toString());
    }

    /**
     * 清除指定任务的统计数据
     * @param taskKey 任务标识，如 "taskA"
     */
    public void clearTaskStats(String taskKey) {
        String hashKey = "task:stats";

        // 删除总耗时和计数器
        redisTemplate.opsForHash().delete(hashKey, taskKey + ":totalTime", taskKey + ":count");
    }

    public void recordTaskElapsedTime(String taskKey, double elapsedTimeInSeconds) {
        // 将耗时转换为 BigDecimal，并四舍五入保留四位小数
        BigDecimal elapsedTime = new BigDecimal(elapsedTimeInSeconds).setScale(4, RoundingMode.HALF_UP);

        // 累加总耗时（以毫秒为单位）
        redisTemplate.opsForHash().increment("task:stats", taskKey + ":totalTime", elapsedTime.doubleValue());

        // 增加计数器
        redisTemplate.opsForHash().increment("task:stats", taskKey + ":count", 1);
    }

    // 获取第一部分（Sketch）耗时
    public double getFirstSketchTime() {
        // 获取 "firstSketchTime:totalTime" 对应的值，并返回（单位为秒）
        Object time = redisTemplate.opsForHash().get("task:stats", "firstSketchTime:totalTime");
        return time != null ? (double) time : 0.0;
    }

    // 获取第二部分（获取特征值）耗时
    public double getGetAttributeRecognitionTime() {
        // 获取 "getAttributeRecognitionTime:totalTime" 对应的值，并返回（单位为秒）
        Object time = redisTemplate.opsForHash().get("task:stats", "getAttributeRecognitionTime:totalTime");
        return time != null ? (double) time : 0.0;
    }

    // 获取第三部分（搭配 Sketch）耗时
    public double getOtherSketchTime() {
        // 获取 "otherSketchTime:totalTime" 对应的值，并返回（单位为秒）
        Object time = redisTemplate.opsForHash().get("task:stats", "otherSketchTime:totalTime");
        return time != null ? (double) time : 0.0;
    }

    // 清理三部分的缓存
    public void clearTaskElapsedTimeCache() {
        // 删除第一部分的缓存
        redisTemplate.opsForHash().delete("task:stats", "firstSketchTime:totalTime");
        redisTemplate.opsForHash().delete("task:stats", "firstSketchTime:count");

        // 删除第二部分的缓存
        redisTemplate.opsForHash().delete("task:stats", "getAttributeRecognitionTime:totalTime");
        redisTemplate.opsForHash().delete("task:stats", "getAttributeRecognitionTime:count");

        // 删除第三部分的缓存
        redisTemplate.opsForHash().delete("task:stats", "otherSketchTime:totalTime");
        redisTemplate.opsForHash().delete("task:stats", "otherSketchTime:count");
    }

    public boolean incrementLikeCount(Long userId, String sketchPath) {
        String redisKey = "user_like_count:" + userId;
        try {
            redisTemplate.opsForHash().increment(redisKey, sketchPath, 1);
            return true;
        } catch (Exception e) {
            log.error("Error incrementing like count for userId {} and sketchPath {}: {}", userId, sketchPath, e.getMessage());
            return false;
        }
    }

    public int getLikeCount(Long userId, String sketchPath) {
        String redisKey = "user_like_count:" + userId;
        Object count = redisTemplate.opsForHash().get(redisKey, sketchPath);
        return count != null ? Integer.parseInt(count.toString()) : 0;
    }

    public void storeMaxLikeCount(Long userId, int maxLikeCount) {
        String redisKey = "user_max_like_count:" + userId;
        redisTemplate.opsForValue().set(redisKey, String.valueOf(maxLikeCount));
    }

    public int getMaxLikeCount(Long userId) {
        String redisKey = "user_max_like_count:" + userId;
        String maxLikeCount = redisTemplate.opsForValue().get(redisKey);
        return maxLikeCount != null ? Integer.parseInt(maxLikeCount) : 0;
    }

    public final static String IMAGE_SEGMENTATION = "ImageSegmentation:";

    public final static String STRIPE_EXCEPTION_LOG = "StripeException:";
    public final static String SUBSCRIPTION_SENT_EMAIL_TYPE = "SubscriptionEmailSentType:";

    public void batchDeleteKeysWithSamePrefix(String prefix){
        Set<String> keys = redisTemplate.keys(prefix + "*");
        assert keys != null;
        if (!keys.isEmpty()){
            redisTemplate.delete(keys);
        }
    }

    public void setTaskProgressDTO(String taskId, ProgressDTO dto) {
        String key = "task:progress:" + taskId;
        redisTemplate.opsForValue().set(key, JSON.toJSONString(dto), Duration.ofDays(1));
    }

    public ProgressDTO getTaskProgressDTO(String taskId) {
        String key = "task:progress:" + taskId;
        String json = redisTemplate.opsForValue().get(key);
        if (StringUtils.isBlank(json)) {
//            return new ProgressDTO(0, 0, false);
            return null;
        }
        try {
            return JSON.parseObject(json, ProgressDTO.class);
        } catch (Exception e) {
            log.warn("任务进度解析失败 key={}, json={}", key, json);
            return new ProgressDTO(0, 0, false, null);
        }
    }

    // Lua脚本（原子化操作）
    /*private static final String RATE_LIMIT_SCRIPT =
            "local current = redis.call('INCR', KEYS[1])\n" +
                    "if tonumber(current) == 1 then\n" +
                    "    redis.call('EXPIRE', KEYS[1], ARGV[1])\n" +
                    "end\n" +
                    "return tonumber(current) <= tonumber(ARGV[2])";*/
    private static final String RATE_LIMIT_SCRIPT =
            "local current = redis.call('INCR', KEYS[1])\n" +
                    "local ttl = redis.call('TTL', KEYS[1])\n" +
                    "if tonumber(current) == 1 or tonumber(ttl) == -1 then\n" +
                    "    redis.call('EXPIRE', KEYS[1], ARGV[1])\n" +
                    "end\n" +
                    "return tonumber(current) <= tonumber(ARGV[2])";

    /**
     * 检查是否允许发送
     * @param userId 用户ID
     * @return true-允许发送，false-已超限
     */
    public boolean allowSend(Long userId) {
        String hourKey = getCurrentHourKey(userId);

        // 执行Lua脚本
        List<String> keys = Collections.singletonList(hourKey);
        List<Long> args = Arrays.asList(
                3600L,     // 1小时过期
                10L // 限制数量 一小时只能向普通用户发10封
        );

        Boolean result = redisTemplate.execute(
                new DefaultRedisScript<>(RATE_LIMIT_SCRIPT, Boolean.class),
                keys,
                args.toArray()
        );

        return Boolean.TRUE.equals(result);
    }

    /**
     * 获取当前小时的Key
     * 格式：email_limit:{userId}:{yyyyMMddHH}
     */
    private String getCurrentHourKey(Long userId) {
        String hour = LocalDateTime.now()
                .format(DateTimeFormatter.ofPattern("yyyyMMddHH"));
        return String.format("email_limit:%s:%s", userId, hour);
    }

    /**
     * 获取当前已发送数量
     */
    public int getCurrentCount(Long userId) {
        String key = getCurrentHourKey(userId);
        String val = redisTemplate.opsForValue().get(key);
        int count;
        if (StringUtils.isBlank(val)){
            count = 0;
        }else {
            count = Integer.parseInt(val);
        }
        return count;
    }

    public boolean allowRequest(String apiKey) {
        String key = "rate_limit:" + apiKey;
        ValueOperations<String, String> ops = redisTemplate.opsForValue();

        // 使用Redis的INCR命令
        Long count = ops.increment(key, 1);

        if (count == 1) {
            // 第一次调用，设置过期时间
            redisTemplate.expire(key, 1, TimeUnit.MINUTES);
        }

        return count <= 3;
    }

}