package com.ycwl.basic.integration.common.service;

import com.fasterxml.jackson.core.type.TypeReference;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.ycwl.basic.integration.common.config.IntegrationProperties;
import com.ycwl.basic.utils.JacksonUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import java.util.stream.Collectors;

/**
 * 集成服务通用失败降级处理
 * 提供统一的降级策略，支持所有微服务集成
 * 使用 Caffeine 内存缓存，缓存命中时直接返回避免打崩下游服务
 */
@Slf4j
@Service
public class IntegrationFallbackService {

    private final IntegrationProperties integrationProperties;
    private final Cache<String, String> fallbackCache;

    private static final String DEFAULT_FALLBACK_PREFIX = "integration:fallback:";
    private static final long CACHE_TTL_MINUTES = 1;
    private static final long MAX_CACHE_SIZE = 50000;

    public IntegrationFallbackService(IntegrationProperties integrationProperties) {
        this.integrationProperties = integrationProperties;
        this.fallbackCache = Caffeine.newBuilder()
                .expireAfterWrite(CACHE_TTL_MINUTES, TimeUnit.MINUTES)
                .maximumSize(MAX_CACHE_SIZE)
                .build();
    }

    /**
     * 执行操作，优先返回缓存结果
     * 策略：有缓存直接返回，无缓存调用远程并缓存结果
     * 同一 cacheKey 有互斥锁，避免并发请求打崩下游服务
     */
    public <T> T executeWithFallback(String serviceName, String cacheKey, Supplier<T> operation, Class<T> resultClass) {
        String fullKey = buildFullCacheKey(serviceName, cacheKey);

        // Caffeine.get() 内置互斥锁：同一 key 只有一个线程执行 loader，其他线程等待
        String cachedValue = fallbackCache.get(fullKey, k -> {
            log.debug("[{}] 缓存未命中，调用远程, cacheKey: {}", serviceName, cacheKey);
            T result = operation.get();
            return result != null ? JacksonUtil.toJSONString(result) : null;
        });

        if (cachedValue == null) {
            return null;
        }
        return parseValue(serviceName, cacheKey, cachedValue, resultClass);
    }

    /**
     * 执行操作，优先返回缓存结果（支持TypeReference泛型）
     * 同一 cacheKey 有互斥锁，避免并发请求打崩下游服务
     */
    public <T> T executeWithFallback(String serviceName, String cacheKey, Supplier<T> operation, TypeReference<T> typeReference) {
        String fullKey = buildFullCacheKey(serviceName, cacheKey);

        // Caffeine.get() 内置互斥锁：同一 key 只有一个线程执行 loader，其他线程等待
        String cachedValue = fallbackCache.get(fullKey, k -> {
            log.debug("[{}] 缓存未命中，调用远程, cacheKey: {}", serviceName, cacheKey);
            T result = operation.get();
            return result != null ? JacksonUtil.toJSONString(result) : null;
        });

        if (cachedValue == null) {
            return null;
        }
        return parseValue(serviceName, cacheKey, cachedValue, typeReference);
    }

    private <T> T parseValue(String serviceName, String cacheKey, String value, Class<T> resultClass) {
        try {
            return JacksonUtil.parseObject(value, resultClass);
        } catch (Exception e) {
            log.warn("[{}] 解析缓存失败, cacheKey: {}", serviceName, cacheKey, e);
            return null;
        }
    }

    private <T> T parseValue(String serviceName, String cacheKey, String value, TypeReference<T> typeReference) {
        try {
            return JacksonUtil.parseObject(value, typeReference);
        } catch (Exception e) {
            log.warn("[{}] 解析缓存失败, cacheKey: {}", serviceName, cacheKey, e);
            return null;
        }
    }

    public void clearFallbackCache(String serviceName, String cacheKey) {
        String fullKey = buildFullCacheKey(serviceName, cacheKey);
        fallbackCache.invalidate(fullKey);
        log.debug("[{}] 清除缓存, key: {}", serviceName, fullKey);
    }

    public void clearAllFallbackCache(String serviceName) {
        String prefix = buildFullCacheKey(serviceName, "");
        List<String> keysToRemove = fallbackCache.asMap().keySet().stream()
                .filter(key -> key.startsWith(prefix))
                .collect(Collectors.toList());
        if (!keysToRemove.isEmpty()) {
            fallbackCache.invalidateAll(keysToRemove);
            log.info("[{}] 批量清除缓存，共删除 {} 项", serviceName, keysToRemove.size());
        }
    }

    public boolean hasFallbackCache(String serviceName, String cacheKey) {
        String fullKey = buildFullCacheKey(serviceName, cacheKey);
        return fallbackCache.getIfPresent(fullKey) != null;
    }

    public FallbackCacheStats getFallbackCacheStats(String serviceName) {
        String prefix = buildFullCacheKey(serviceName, "");
        long totalCount = fallbackCache.asMap().keySet().stream()
                .filter(key -> key.startsWith(prefix))
                .count();

        return FallbackCacheStats.builder()
                .serviceName(serviceName)
                .totalCacheCount((int) totalCount)
                .cacheKeyPattern(prefix + "*")
                .cacheTtlMinutes(CACHE_TTL_MINUTES)
                .build();
    }

    private String buildFullCacheKey(String serviceName, String cacheKey) {
        String prefix = getFallbackPrefix(serviceName);
        return prefix + serviceName + ":" + cacheKey;
    }

    private String getFallbackPrefix(String serviceName) {
        if (!integrationProperties.getFallback().isEnabled()) {
            return DEFAULT_FALLBACK_PREFIX;
        }
        IntegrationProperties.ServiceFallbackConfig serviceConfig = getServiceFallbackConfig(serviceName);
        if (serviceConfig != null && serviceConfig.getCachePrefix() != null) {
            return serviceConfig.getCachePrefix();
        }
        return integrationProperties.getFallback().getCachePrefix();
    }

    private IntegrationProperties.ServiceFallbackConfig getServiceFallbackConfig(String serviceName) {
        switch (serviceName.toLowerCase()) {
            case "zt-scenic":
                return integrationProperties.getFallback().getScenic();
            case "zt-device":
                return integrationProperties.getFallback().getDevice();
            default:
                return null;
        }
    }

    public boolean isFallbackEnabled(String serviceName) {
        if (!integrationProperties.getFallback().isEnabled()) {
            return false;
        }
        IntegrationProperties.ServiceFallbackConfig serviceConfig = getServiceFallbackConfig(serviceName);
        return serviceConfig == null || serviceConfig.isEnabled();
    }

    @lombok.Builder
    @lombok.Data
    public static class FallbackCacheStats {
        private String serviceName;
        private int totalCacheCount;
        private String cacheKeyPattern;
        private long cacheTtlMinutes;
    }
}
