package com.pharmacopoeia.service;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.time.Duration;
import java.util.List;
import java.util.Map;
import java.util.Optional;

/**
 * 全局 QA 缓存（不区分用户，7 天过期）。
 * 同一个问题在 24 小时内只调用一次 LLM，后续直接返回缓存结果。
 * 并发控制：同一问题同时只有第一个请求调 LLM，其余等待缓存写入后直接返回。
 */
@Service
public class QACacheService {

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

    private static final String CACHE_PREFIX = "aiyaodian:qa:";
    private static final String PENDING_PREFIX = "aiyaodian:qa:pending:";
    private static final Duration TTL = Duration.ofHours(168); // 7 天
    /** 处理中标记超时：防止死锁，30 秒后自动释放 */
    private static final Duration PENDING_TTL = Duration.ofSeconds(30);
    /** 等待缓存写入的最大重试次数 */
    private static final int MAX_RETRIES = 30;
    /** 每次重试间隔 */
    private static final long RETRY_INTERVAL_MS = 500;

    private final StringRedisTemplate redis;
    private final boolean available;
    private final ObjectMapper objectMapper;

    public QACacheService(StringRedisTemplate redis, ObjectMapper objectMapper) {
        this.redis = redis;
        this.objectMapper = objectMapper;
        this.available = pingRedis();
    }

    private boolean pingRedis() {
        try {
            return "PONG".equals(redis.getConnectionFactory().getConnection().ping());
        } catch (Exception e) {
            log.warn("Redis 不可用，QA 缓存降级为无缓存模式");
            return false;
        }
    }

    /** 规范化问题文本，用于生成缓存 key */
    public String normalize(String query) {
        if (query == null) {
            return "";
        }
        return query.trim().replaceAll("\\s+", " ");
    }

    /** 根据问题文本生成缓存 key */
    public String cacheKey(String normalizedQuery) {
        return CACHE_PREFIX + sha256(normalizedQuery);
    }

    private String pendingKey(String normalizedQuery) {
        return PENDING_PREFIX + sha256(normalizedQuery);
    }

    /**
     * 查询缓存，命中返回 {answer, sources, intent}，未命中返回 null。
     */
    public Map<String, Object> get(String normalizedQuery) {
        if (!available || normalizedQuery == null || normalizedQuery.isBlank()) {
            return null;
        }
        try {
            String json = redis.opsForValue().get(cacheKey(normalizedQuery));
            if (json == null || json.isEmpty()) {
                return null;
            }
            return objectMapper.readValue(json, new TypeReference<>() {
            });
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * 写入缓存，同时移除处理中标记。
     */
    public void put(String normalizedQuery, String answer, String intent,
                    List<Map<String, Object>> sources) {
        if (!available || normalizedQuery == null || normalizedQuery.isBlank()) {
            return;
        }
        try {
            Map<String, Object> entry = Map.of(
                    "answer", answer,
                    "intent", intent != null ? intent : "",
                    "sources", sources != null ? sources : List.of()
            );
            String json = objectMapper.writeValueAsString(entry);
            String key = cacheKey(normalizedQuery);
            redis.opsForValue().set(key, json, TTL);
            // 移除处理中标记，唤醒等待者
            redis.delete(pendingKey(normalizedQuery));
        } catch (Exception e) {
            log.warn("QA 缓存写入失败: {}", e.getMessage());
        }
    }

    /**
     * 尝试标记该问题为"处理中"。
     * @return true 表示成功抢占（可以调 LLM），false 表示已有其他请求在处理
     */
    public boolean tryMarkPending(String normalizedQuery) {
        if (!available || normalizedQuery == null || normalizedQuery.isBlank()) {
            return false; // Redis 不可用时放行，避免阻塞
        }
        try {
            Boolean ok = redis.opsForValue()
                    .setIfAbsent(pendingKey(normalizedQuery), "1", PENDING_TTL);
            return !Boolean.TRUE.equals(ok);
        } catch (Exception e) {
            return false; // 异常时放行
        }
    }

    /**
     * 等待缓存写入（另一个请求正在调 LLM），轮询直到缓存命中或超时。
     * @return 缓存结果，超时返回 null（调用方应自行调 LLM）
     */
    public Map<String, Object> waitForCache(String normalizedQuery) {
        for (int i = 0; i < MAX_RETRIES; i++) {
            try {
                Thread.sleep(RETRY_INTERVAL_MS);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                return null;
            }
            Map<String, Object> cached = get(normalizedQuery);
            if (cached != null) {
                return cached;
            }
        }
        return null; // 超时，调用方自行处理
    }

    /**
     * 移除"处理中"标记（限流/异常时调用，避免后续请求被 PENDING 锁死）。
     */
    public void removePending(String normalizedQuery) {
        if (!available || normalizedQuery == null || normalizedQuery.isBlank()) {
            return;
        }
        try {
            redis.delete(pendingKey(normalizedQuery));
        } catch (Exception e) {
            log.warn("移除 PENDING 标记失败: {}", e.getMessage());
        }
    }

    /**
     * 清除 QA 缓存。不传 keyword 则全清；传 keyword 则只清除 answer/content
     * 中包含该关键字的条目。
     * @param keyword 可选，不为空时按关键字过滤
     * @return 删除的 key 数量
     */
    public int clearAll(String keyword) {
        if (!available) {
            log.warn("Redis 不可用，无法清除缓存");
            return 0;
        }
        boolean filterByKeyword = keyword != null && !keyword.isBlank();
        try {
            var keys = redis.keys(CACHE_PREFIX + "*");
            int count = 0;
            if (!keys.isEmpty()) {
                for (String key : keys) {
                    if (filterByKeyword) {
                        String json = redis.opsForValue().get(key);
                        if (json == null || !json.contains(keyword)) {
                            continue;
                        }
                    }
                    count += Optional.of(redis.delete(key)).orElse(false) ? 1 : 0;
                }
            }
            var pendingKeys = redis.keys(PENDING_PREFIX + "*");
            if (!pendingKeys.isEmpty()) {
                count += redis.delete(pendingKeys).intValue();
            }
            log.info("QA 缓存已清除: keyword={}, deleted={}", keyword, count);
            return count;
        } catch (Exception e) {
            log.warn("清除 QA 缓存失败: {}", e.getMessage());
            return 0;
        }
    }

    private static String sha256(String input) {
        try {
            MessageDigest md = MessageDigest.getInstance("SHA-256");
            byte[] digest = md.digest(input.getBytes(StandardCharsets.UTF_8));
            StringBuilder sb = new StringBuilder();
            for (byte b : digest) {
                sb.append(String.format("%02x", b));
            }
            return sb.toString();
        } catch (NoSuchAlgorithmException e) {
            return Integer.toHexString(input.hashCode());
        }
    }
}