package cn.novalon.manage.gateway.cache;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;

import java.time.Duration;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 请求缓存服务
 * 
 * 文件定义：实现网关请求的缓存机制
 * 涉及业务：响应缓存、缓存失效、缓存统计
 * 
 * 核心功能：
 * 1. 请求响应缓存
 * 2. 缓存键生成
 * 3. 缓存失效管理
 * 4. 缓存统计
 * 
 * @author 张翔
 * @date 2026-03-26
 */
@Service
public class RequestCacheService {

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

    private final Map<String, CacheEntry> cache = new ConcurrentHashMap<>();
    private final Map<String, CacheStats> stats = new ConcurrentHashMap<>();

    private boolean cacheEnabled = true;
    private Duration defaultTtl = Duration.ofMinutes(5);
    private int maxCacheSize = 10000;

    public Mono<String> get(ServerHttpRequest request) {
        if (!cacheEnabled) {
            return Mono.empty();
        }

        String cacheKey = generateCacheKey(request);
        CacheEntry entry = cache.get(cacheKey);

        if (entry == null) {
            recordMiss(cacheKey);
            return Mono.empty();
        }

        if (isExpired(entry)) {
            cache.remove(cacheKey);
            recordMiss(cacheKey);
            return Mono.empty();
        }

        recordHit(cacheKey);
        logger.debug("Cache hit for key: {}", cacheKey);
        return Mono.just(entry.getValue());
    }

    public void put(ServerHttpRequest request, String response) {
        if (!cacheEnabled || response == null) {
            return;
        }

        String cacheKey = generateCacheKey(request);

        if (cache.size() >= maxCacheSize) {
            evictOldestEntries();
        }

        CacheEntry entry = new CacheEntry(
                response,
                System.currentTimeMillis(),
                defaultTtl.toMillis()
        );

        cache.put(cacheKey, entry);
        logger.debug("Cached response for key: {}", cacheKey);
    }

    public void evict(ServerHttpRequest request) {
        String cacheKey = generateCacheKey(request);
        cache.remove(cacheKey);
        logger.debug("Evicted cache for key: {}", cacheKey);
    }

    public void evictByPattern(String pattern) {
        cache.keySet().removeIf(key -> key.matches(pattern));
        logger.info("Evicted cache entries matching pattern: {}", pattern);
    }

    public void clear() {
        int size = cache.size();
        cache.clear();
        stats.clear();
        logger.info("Cleared all cache entries. Removed {} entries", size);
    }

    private String generateCacheKey(ServerHttpRequest request) {
        String method = request.getMethod().name();
        String path = request.getPath().value();
        String query = request.getURI().getQuery();

        StringBuilder keyBuilder = new StringBuilder();
        keyBuilder.append(method).append(":").append(path);

        if (query != null && !query.isEmpty()) {
            keyBuilder.append("?").append(query);
        }

        return keyBuilder.toString();
    }

    private boolean isExpired(CacheEntry entry) {
        long currentTime = System.currentTimeMillis();
        return (currentTime - entry.getCreatedAt()) > entry.getTtl();
    }

    private void evictOldestEntries() {
        int entriesToRemove = maxCacheSize / 10;

        cache.entrySet().stream()
                .sorted((e1, e2) -> 
                        Long.compare(e1.getValue().getCreatedAt(), 
                                e2.getValue().getCreatedAt()))
                .limit(entriesToRemove)
                .map(Map.Entry::getKey)
                .forEach(cache::remove);

        logger.info("Evicted {} oldest cache entries", entriesToRemove);
    }

    private void recordHit(String cacheKey) {
        stats.compute(cacheKey, (key, stat) -> {
            if (stat == null) {
                stat = new CacheStats();
            }
            stat.incrementHits();
            return stat;
        });
    }

    private void recordMiss(String cacheKey) {
        stats.compute(cacheKey, (key, stat) -> {
            if (stat == null) {
                stat = new CacheStats();
            }
            stat.incrementMisses();
            return stat;
        });
    }

    public int getCacheSize() {
        return cache.size();
    }

    public long getHitCount() {
        return stats.values().stream()
                .mapToLong(CacheStats::getHits)
                .sum();
    }

    public long getMissCount() {
        return stats.values().stream()
                .mapToLong(CacheStats::getMisses)
                .sum();
    }

    public double getHitRate() {
        long hits = getHitCount();
        long misses = getMissCount();
        long total = hits + misses;

        if (total == 0) {
            return 0.0;
        }

        return (double) hits / total;
    }

    public void setCacheEnabled(boolean enabled) {
        this.cacheEnabled = enabled;
        logger.info("Cache enabled: {}", enabled);
    }

    public void setDefaultTtl(Duration ttl) {
        this.defaultTtl = ttl;
        logger.info("Default TTL set to: {}", ttl);
    }

    public void setMaxCacheSize(int maxSize) {
        this.maxCacheSize = maxSize;
        logger.info("Max cache size set to: {}", maxSize);
    }

    private static class CacheEntry {
        private final String value;
        private final long createdAt;
        private final long ttl;

        public CacheEntry(String value, long createdAt, long ttl) {
            this.value = value;
            this.createdAt = createdAt;
            this.ttl = ttl;
        }

        public String getValue() {
            return value;
        }

        public long getCreatedAt() {
            return createdAt;
        }

        public long getTtl() {
            return ttl;
        }
    }

    private static class CacheStats {
        private long hits = 0;
        private long misses = 0;

        public void incrementHits() {
            hits++;
        }

        public void incrementMisses() {
            misses++;
        }

        public long getHits() {
            return hits;
        }

        public long getMisses() {
            return misses;
        }
    }
}
