import { describe, expect, it, vi } from "vitest";
import { CachedApiClient } from "./cached-client.js";
import type { ApiClient } from "./client.js";
import { RequestScopedCache, ClientMemoryCache } from "./cache.js";

function mockClient(): ApiClient {
  return {
    get: vi.fn(async () => ({ data: "fresh" })),
    post: vi.fn(async () => ({ data: "posted" })),
  } as unknown as ApiClient;
}

describe("CachedApiClient", () => {
  it("delegates to the underlying client on cache miss", async () => {
    const client = mockClient();
    const cached = new CachedApiClient({ client });
    const result = await cached.get("/flights");
    expect(result).toEqual({ data: "fresh" });
    expect(client.get).toHaveBeenCalledTimes(1);
  });

  it("returns cached value from request-scoped cache on hit", async () => {
    const client = mockClient();
    const requestScoped = new RequestScopedCache();
    const cached = new CachedApiClient({ client, requestScoped });

    // First call populates the cache
    await cached.get("/flights");
    // Second call should hit the request-scoped cache
    await cached.get("/flights");

    expect(client.get).toHaveBeenCalledTimes(1);
  });

  it("returns cached value from client-memory cache on hit", async () => {
    const client = mockClient();
    const clientMemory = new ClientMemoryCache<NonNullable<unknown>>({ max: 10, defaultTtlMs: 5000 });
    const cached = new CachedApiClient({ client, clientMemory });

    await cached.get("/flights");
    await cached.get("/flights");

    expect(client.get).toHaveBeenCalledTimes(1);
  });

  it("cache key differs by query params", async () => {
    const client = mockClient();
    const clientMemory = new ClientMemoryCache<NonNullable<unknown>>({ max: 10, defaultTtlMs: 5000 });
    const cached = new CachedApiClient({ client, clientMemory });

    await cached.get("/flights", { date: "2025-01-15" });
    await cached.get("/flights", { date: "2025-01-16" });

    expect(client.get).toHaveBeenCalledTimes(2);
  });
});
