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

import { Result } from "@/utils/result";

const getHistoryMock = vi.hoisted(() => vi.fn());

vi.mock("@/data/repositories/chat_repository", () => ({
  getChatRepository: () => ({ getHistory: getHistoryMock }),
}));

import {
  fetchSplashLatestMessagePreview,
  loadSplashLatestMessagePreview,
} from "../splash_latest_message";
import { createSplashLatestMessageCache } from "../splash_latest_message_cache";

describe("fetchSplashLatestMessagePreview", () => {
  beforeEach(() => {
    getHistoryMock.mockReset();
  });

  it("uses the last message returned by history", async () => {
    getHistoryMock.mockResolvedValue(
      Result.ok({
        messages: [
          { type: "text", content: "Older message" },
          { type: "text", content: "Latest message" },
        ],
      }),
    );

    const result = await fetchSplashLatestMessagePreview();

    expect(getHistoryMock).toHaveBeenCalledWith(1, 0);
    expect(Result.isOk(result) && result.data).toBe("Latest message");
  });

  it("returns null when history is empty", async () => {
    getHistoryMock.mockResolvedValue(Result.ok({ messages: [] }));

    const result = await fetchSplashLatestMessagePreview();

    expect(Result.isOk(result) && result.data).toBeNull();
  });
});

describe("loadSplashLatestMessagePreview", () => {
  it("reuses a successful result for the same identity", async () => {
    const cache = createSplashLatestMessageCache();
    const fetchPreview = vi.fn().mockResolvedValue(Result.ok("Hello again"));
    const resolveIdentity = vi.fn().mockResolvedValue(Result.ok("user:1"));

    const first = await loadSplashLatestMessagePreview({
      cache,
      fetchPreview,
      resolveIdentity,
    });
    const second = await loadSplashLatestMessagePreview({
      cache,
      fetchPreview,
      resolveIdentity,
    });

    expect(Result.isOk(first) && first.data).toBe("Hello again");
    expect(Result.isOk(second) && second.data).toBe("Hello again");
    expect(fetchPreview).toHaveBeenCalledTimes(1);
  });

  it("treats a cached empty history as a cache hit", async () => {
    const cache = createSplashLatestMessageCache();
    cache.set("user:empty", null);
    const fetchPreview = vi.fn();

    const result = await loadSplashLatestMessagePreview({
      cache,
      fetchPreview,
      resolveIdentity: vi
        .fn()
        .mockResolvedValue(Result.ok("user:empty")),
    });

    expect(Result.isOk(result) && result.data).toBeNull();
    expect(fetchPreview).not.toHaveBeenCalled();
  });

  it("does not cache failed requests", async () => {
    const cache = createSplashLatestMessageCache();
    const fetchPreview = vi
      .fn()
      .mockResolvedValueOnce(Result.err(new Error("offline")))
      .mockResolvedValueOnce(Result.ok("Recovered"));
    const resolveIdentity = vi.fn().mockResolvedValue(Result.ok("user:1"));

    await loadSplashLatestMessagePreview({
      cache,
      fetchPreview,
      resolveIdentity,
    });
    const retry = await loadSplashLatestMessagePreview({
      cache,
      fetchPreview,
      resolveIdentity,
    });

    expect(Result.isOk(retry) && retry.data).toBe("Recovered");
    expect(fetchPreview).toHaveBeenCalledTimes(2);
  });

  it("bypasses the cache when the identity is unavailable", async () => {
    const cache = createSplashLatestMessageCache();
    const fetchPreview = vi.fn().mockResolvedValue(Result.ok("Network"));
    const resolveIdentity = vi
      .fn()
      .mockResolvedValue(Result.err(new Error("missing identity")));

    await loadSplashLatestMessagePreview({
      cache,
      fetchPreview,
      resolveIdentity,
    });
    await loadSplashLatestMessagePreview({
      cache,
      fetchPreview,
      resolveIdentity,
    });

    expect(fetchPreview).toHaveBeenCalledTimes(2);
  });
});
