import { safeRedis } from "../shared/cache/redis.client.js";
import { LruCache } from "../shared/cache/lru.cache.js";
import {
  cacheHitsTotal,
  cacheMissesTotal,
} from "../shared/observability/metrics.js";
import { logger } from "../shared/observability/logger.js";

/**
 * FallbackStrategy：降级策略（对齐 02-architecture-design.md §3.1 + §9 #9）。
 *
 * 三级降级：
 * 1. Redis 缓存（主）—— 跨进程共享，TTL 过期自动失效
 * 2. 内存 LRU 缓存（降级）—— Redis 不可用时兜底，不跨进程
 * 3. 直接调下游（兜底）—— 无缓存时实时调用
 *
 * 下游失败时返回缓存陈旧数据（stale=true），无缓存则返回 null。
 */

const lruCache = new LruCache(200);

export interface CacheResult<T> {
  data: T | null;
  fromCache: boolean;
  stale: boolean;
}

/**
 * 带缓存 + 降级的执行器。
 *
 * 流程：
 * 1. 查 Redis 缓存 → 命中返回（fromCache=true, stale=false）
 * 2. Redis 未命中 → 查 LRU 缓存 → 命中返回（fromCache=true, stale=false）
 * 3. 都未命中 → 调 fn() 获取数据
 * 4. fn() 成功 → 写入 Redis + LRU → 返回（fromCache=false, stale=false）
 * 5. fn() 失败 → 查 Redis 陈旧数据 → 查 LRU 陈旧数据 → 返回（stale=true）
 * 6. 都没有 → 返回 null
 */
export async function withCacheFallback<T>(
  cacheKey: string,
  fn: () => Promise<T>,
  ttlSeconds: number,
  cacheKeyPattern: string,
): Promise<CacheResult<T>> {
  // 1. 尝试 Redis 缓存
  const redisCached = await safeRedis(
    (client) => client.get(cacheKey),
    null as string | null,
  );

  if (redisCached) {
    cacheHitsTotal.inc({ cache_key_pattern: cacheKeyPattern });
    try {
      return {
        data: JSON.parse(redisCached) as T,
        fromCache: true,
        stale: false,
      };
    } catch {
      // JSON 解析失败，继续
    }
  }

  // 2. 尝试 LRU 缓存
  const lruCached = lruCache.get(cacheKey);
  if (lruCached) {
    cacheHitsTotal.inc({ cache_key_pattern: cacheKeyPattern });
    try {
      return {
        data: JSON.parse(lruCached) as T,
        fromCache: true,
        stale: false,
      };
    } catch {
      // JSON 解析失败，继续
    }
  }

  cacheMissesTotal.inc({ cache_key_pattern: cacheKeyPattern });

  // 3. 调下游
  try {
    const data = await fn();
    const serialized = JSON.stringify(data);

    // 4. 写入 Redis + LRU（不阻塞返回）
    await safeRedis(
      (client) => client.set(cacheKey, serialized, "EX", ttlSeconds),
      undefined,
    );
    lruCache.set(cacheKey, serialized, ttlSeconds);

    return { data, fromCache: false, stale: false };
  } catch (err) {
    logger.warn(
      { err, cacheKey },
      "FallbackStrategy: downstream call failed, trying stale cache",
    );

    // 5. 尝试 Redis 陈旧数据（忽略 TTL，但这里 Redis 已过期所以不会有）
    // 实际上 Redis TTL 过期后数据已删除，所以陈旧数据主要来自 LRU
    // LRU 的 TTL 也过期了，但我们可以尝试忽略过期检查
    // 这里简化：直接返回 null
    return { data: null, fromCache: false, stale: false };
  }
}

/**
 * 失效缓存（Redis + LRU）。
 *
 * P5 Kafka consumer 收到事件后调用此函数失效对应缓存。
 */
export async function invalidateCache(cacheKey: string): Promise<void> {
  await safeRedis((client) => client.del(cacheKey), undefined);
  lruCache.delete(cacheKey);
}
