import { describe, it, expect, beforeEach, vi } from "vitest";

// Mock safeRedis 以控制 Redis 行为（单元测试不依赖真实 Redis）
vi.mock("../../src/shared/cache/redis.client.js", () => ({
  safeRedis: vi.fn().mockResolvedValue(null),
  getRedisClient: vi.fn(() => null),
  closeRedisClient: vi.fn(),
}));

import {
  withCacheFallback,
  invalidateCache,
} from "../../src/aggregation/fallback-strategy.js";
import { safeRedis } from "../../src/shared/cache/redis.client.js";

const mockSafeRedis = vi.mocked(safeRedis);

describe("FallbackStrategy（三级降级）", () => {
  beforeEach(() => {
    vi.clearAllMocks();
    mockSafeRedis.mockResolvedValue(null);
  });

  describe("Redis 缓存命中（第一级）", () => {
    it("Redis 命中时返回缓存数据，fromCache=true", async () => {
      const cachedData = { id: "test-1", name: "测试" };
      mockSafeRedis.mockResolvedValueOnce(JSON.stringify(cachedData));

      const result = await withCacheFallback(
        "test-redis-hit",
        () => Promise.resolve({ id: "test-1", name: "不应该被调用" }),
        30,
        "test",
      );

      expect(result.data).toEqual(cachedData);
      expect(result.fromCache).toBe(true);
      expect(result.stale).toBe(false);
    });
  });

  describe("Redis 未命中 → fn 成功（第三级直连）", () => {
    it("Redis miss 后调 fn 获取数据并写入缓存", async () => {
      const fnData = { id: "test-2", value: 42 };
      mockSafeRedis
        .mockResolvedValueOnce(null) // get → miss
        .mockResolvedValueOnce(undefined); // set → ok

      const result = await withCacheFallback(
        "test-redis-miss-fn-success",
        () => Promise.resolve(fnData),
        30,
        "test",
      );

      expect(result.data).toEqual(fnData);
      expect(result.fromCache).toBe(false);
      expect(result.stale).toBe(false);
      // safeRedis 被调用 2 次：get + set
      expect(mockSafeRedis).toHaveBeenCalledTimes(2);
    });
  });

  describe("LRU 缓存命中（第二级降级）", () => {
    it("Redis 不可用时从 LRU 缓存返回数据", async () => {
      // 第一次调用：miss Redis + miss LRU → 调 fn → 写入 LRU
      const fnData = { id: "test-3", label: "LRU测试" };
      mockSafeRedis
        .mockResolvedValueOnce(null) // get → miss
        .mockResolvedValueOnce(undefined); // set → ok

      await withCacheFallback(
        "test-lru-hit-key",
        () => Promise.resolve(fnData),
        60,
        "test",
      );

      // 第二次调用：miss Redis → hit LRU
      mockSafeRedis.mockResolvedValueOnce(null); // get → miss

      const result = await withCacheFallback(
        "test-lru-hit-key",
        () => Promise.resolve({ id: "test-3", label: "不应该被调用" }),
        60,
        "test",
      );

      expect(result.data).toEqual(fnData);
      expect(result.fromCache).toBe(true);
      expect(result.stale).toBe(false);
    });
  });

  describe("fn 失败降级", () => {
    it("fn 抛错时返回 null", async () => {
      mockSafeRedis.mockResolvedValueOnce(null); // get → miss

      const result = await withCacheFallback(
        "test-fn-fail",
        () => Promise.reject(new Error("downstream error")),
        30,
        "test",
      );

      expect(result.data).toBeNull();
      expect(result.fromCache).toBe(false);
      expect(result.stale).toBe(false);
    });
  });

  describe("Redis JSON 解析失败", () => {
    it("Redis 返回非法 JSON 时降级到 fn", async () => {
      mockSafeRedis
        .mockResolvedValueOnce("not-valid-json") // get → 返回非法 JSON
        .mockResolvedValueOnce(null) // LRU miss
        .mockResolvedValueOnce(undefined); // set → ok

      const result = await withCacheFallback(
        "test-bad-json",
        () => Promise.resolve({ data: "from-fn" }),
        30,
        "test",
      );

      expect(result.data).toEqual({ data: "from-fn" });
      expect(result.fromCache).toBe(false);
    });
  });

  describe("invalidateCache", () => {
    it("调用 safeRedis del + LRU delete", async () => {
      // 先写入 LRU
      mockSafeRedis
        .mockResolvedValueOnce(null) // get → miss
        .mockResolvedValueOnce(undefined); // set → ok
      await withCacheFallback(
        "test-invalidate",
        () => Promise.resolve({ val: 1 }),
        30,
        "test",
      );

      // 失效缓存
      mockSafeRedis.mockResolvedValueOnce(undefined); // del → ok
      await invalidateCache("test-invalidate");

      // 再次查询，应该 miss LRU
      mockSafeRedis
        .mockResolvedValueOnce(null) // get → miss
        .mockResolvedValueOnce(undefined); // set → ok
      const result = await withCacheFallback(
        "test-invalidate",
        () => Promise.resolve({ val: 2 }),
        30,
        "test",
      );

      expect(result.data).toEqual({ val: 2 });
      expect(result.fromCache).toBe(false);
    });
  });
});
