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

import { DEFAULT_CHARACTER_ID } from "@/data/constants/character";
import { Result } from "@/utils/result";

import { ChatMediaCacheCoordinator } from "../chat_media_cache_coordinator";

afterEach(() => {
  vi.restoreAllMocks();
  vi.unstubAllGlobals();
});

describe("ChatMediaCacheCoordinator identity isolation", () => {
  it("looks up media only in the current identity namespace", async () => {
    const getMedia = vi.fn(async () => Result.ok(null));
    const mediaStorage = {
      getMedia,
      saveMedia: vi.fn(async () => Result.ok(undefined)),
    };
    const coordinator = new ChatMediaCacheCoordinator(
      mediaStorage,
      async () => Result.ok("user:account-a"),
    );

    const result = await coordinator.getCachedMedia({
      characterId: DEFAULT_CHARACTER_ID,
      messageId: "message-1",
      kind: "image",
    });

    expect(result).toEqual(Result.ok(null));
    expect(getMedia).toHaveBeenCalledTimes(1);
    expect(getMedia).toHaveBeenCalledWith(
      "user:account-a::character:elio:message-1:image",
    );
  });

  it("does not share a media key between characters", async () => {
    const getMedia = vi.fn(async () => Result.ok(null));
    const coordinator = new ChatMediaCacheCoordinator(
      {
        getMedia,
        saveMedia: vi.fn(async () => Result.ok(undefined)),
      },
      async () => Result.ok("user:account-a"),
    );

    await coordinator.getCachedMedia({
      characterId: "elio",
      messageId: "shared-message-id",
      kind: "image",
    });
    await coordinator.getCachedMedia({
      characterId: "character_aria",
      messageId: "shared-message-id",
      kind: "image",
    });

    expect(getMedia.mock.calls).toEqual([
      ["user:account-a::character:elio:shared-message-id:image"],
      ["user:account-a::character:character_aria:shared-message-id:image"],
    ]);
  });

  it("does not fall back to an anonymous cache without an identity", async () => {
    const error = vi
      .spyOn(console, "error")
      .mockImplementation(() => undefined);
    const getMedia = vi.fn(async () => Result.ok(null));
    const coordinator = new ChatMediaCacheCoordinator(
      {
        getMedia,
        saveMedia: vi.fn(async () => Result.ok(undefined)),
      },
      async () => Result.err(new Error("identity unavailable")),
    );

    const result = await coordinator.getCachedMedia({
      characterId: DEFAULT_CHARACTER_ID,
      messageId: "message-1",
      kind: "audio",
    });

    expect(Result.isErr(result)).toBe(true);
    expect(getMedia).not.toHaveBeenCalled();
    expect(error).toHaveBeenCalledWith(
      expect.stringContaining("ERROR [Result] Result.wrap caught exception"),
      expect.any(Object),
    );
  });

  it("logs expired remote media responses at info level", async () => {
    vi.stubGlobal(
      "fetch",
      vi.fn(async () =>
        new Response(null, { status: 400, statusText: "Bad Request" }),
      ),
    );
    const info = vi.spyOn(console, "info").mockImplementation(() => undefined);
    const error = vi
      .spyOn(console, "error")
      .mockImplementation(() => undefined);
    const coordinator = new ChatMediaCacheCoordinator({
      getMedia: vi.fn(async () => Result.ok(null)),
      saveMedia: vi.fn(async () => Result.ok(undefined)),
    });

    const result = await coordinator.cacheRemoteMedia(
      {
        characterId: DEFAULT_CHARACTER_ID,
        messageId: "message-expired",
        kind: "image",
        remoteUrl: "https://media.example/expired.jpg",
      },
      "user:account-a::character:elio",
    );

    expect(Result.isErr(result)).toBe(true);
    expect(info).toHaveBeenCalledWith(
      expect.stringContaining("INFO [Result] Result.wrap caught exception"),
      expect.any(Object),
    );
    expect(error).not.toHaveBeenCalled();
  });
});
