import { afterEach, describe, expect, it, vi } from "vitest";
import { SenderNameCache } from "../../src/feishu/senderCache.js";

describe("SenderNameCache", () => {
  afterEach(() => {
    vi.useRealTimers();
  });

  it("get returns null for missing key", () => {
    const cache = new SenderNameCache();

    expect(cache.get("ou_user")).toBeNull();
  });

  it("set then get returns the name", () => {
    const cache = new SenderNameCache();

    cache.set("ou_user", "Alice");

    expect(cache.get("ou_user")).toBe("Alice");
  });

  it("expired entry returns null", () => {
    vi.useFakeTimers();
    vi.setSystemTime(new Date("2026-01-01T00:00:00Z"));
    const cache = new SenderNameCache({ ttlMs: 100 });

    cache.set("ou_user", "Alice");
    vi.advanceTimersByTime(101);

    expect(cache.get("ou_user")).toBeNull();
    expect(cache.size()).toBe(0);
  });

  it("getOrFetch uses cache on hit", async () => {
    const cache = new SenderNameCache();
    const fetchFn = vi.fn(async () => "Fetched Alice");
    cache.set("ou_user", "Alice");

    await expect(cache.getOrFetch("ou_user", fetchFn)).resolves.toBe("Alice");

    expect(fetchFn).not.toHaveBeenCalled();
  });

  it("getOrFetch calls fetchFn on miss", async () => {
    const cache = new SenderNameCache();
    const fetchFn = vi.fn(async () => "Alice");

    await expect(cache.getOrFetch("ou_user", fetchFn)).resolves.toBe("Alice");

    expect(fetchFn).toHaveBeenCalledTimes(1);
    expect(fetchFn).toHaveBeenCalledWith("ou_user");
  });

  it("getOrFetch caches the result of fetchFn", async () => {
    const cache = new SenderNameCache();
    const fetchFn = vi.fn(async () => "Alice");

    await expect(cache.getOrFetch("ou_user", fetchFn)).resolves.toBe("Alice");
    await expect(cache.getOrFetch("ou_user", fetchFn)).resolves.toBe("Alice");

    expect(fetchFn).toHaveBeenCalledTimes(1);
  });

  it("evicts the least recently used entry when full", () => {
    const cache = new SenderNameCache({ maxSize: 2 });

    cache.set("ou_1", "Alice");
    cache.set("ou_2", "Bob");
    expect(cache.get("ou_1")).toBe("Alice");
    cache.set("ou_3", "Carol");

    expect(cache.get("ou_1")).toBe("Alice");
    expect(cache.get("ou_2")).toBeNull();
    expect(cache.get("ou_3")).toBe("Carol");
  });

  it("clear empties the cache", () => {
    const cache = new SenderNameCache();

    cache.set("ou_user", "Alice");
    cache.clear();

    expect(cache.get("ou_user")).toBeNull();
    expect(cache.size()).toBe(0);
  });

  it("size returns correct count", () => {
    const cache = new SenderNameCache();

    cache.set("ou_1", "Alice");
    cache.set("ou_2", "Bob");

    expect(cache.size()).toBe(2);
  });

  it("getOrFetch with null fetchFn result does not cache null", async () => {
    const cache = new SenderNameCache();
    const fetchFn = vi.fn(async () => null);

    await expect(cache.getOrFetch("ou_user", fetchFn)).resolves.toBeNull();
    await expect(cache.getOrFetch("ou_user", fetchFn)).resolves.toBeNull();

    expect(fetchFn).toHaveBeenCalledTimes(2);
    expect(cache.size()).toBe(0);
  });
});
