import { existsSync } from "node:fs";
import { join } from "node:path";
import { describe, expect, it } from "vitest";

import {
  CHARACTERS,
  createCharacterCatalog,
  DEFAULT_CHARACTER,
  getCharacterById,
  getCharacterBySlug,
  mergeRemoteCharacterCatalog,
} from "@/data/constants/character";
import { CharacterListResponseSchema } from "@/data/schemas/character";

describe("local character catalog", () => {
  it("contains immutable and uniquely addressable character profiles", () => {
    expect(CHARACTERS.map((character) => character.slug)).toEqual([
      "elio",
      "maya",
      "nayeli",
    ]);
    expect(CHARACTERS.map((character) => character.id)).toEqual([
      "elio",
      "maya-tan",
      "nayeli-cervantes",
    ]);
    expect(new Set(CHARACTERS.map((character) => character.id)).size).toBe(
      CHARACTERS.length,
    );
    expect(Object.isFrozen(CHARACTERS)).toBe(true);
    expect(Object.isFrozen(DEFAULT_CHARACTER)).toBe(true);
    expect(Object.isFrozen(DEFAULT_CHARACTER.assets)).toBe(true);
    expect(Object.isFrozen(DEFAULT_CHARACTER.capabilities)).toBe(true);
    expect(Object.isFrozen(DEFAULT_CHARACTER.copy)).toBe(true);
  });

  it("merges backend authority with local routes and assets", () => {
    const snapshot = mergeRemoteCharacterCatalog(
      CharacterListResponseSchema.parse({
        defaultCharacterId: "elio",
        items: [
          {
            id: "maya-tan",
            displayName: "Maya Backend",
            isActive: true,
            sortOrder: 5,
            capabilities: { chat: true, privateContent: false },
          },
          {
            id: "elio",
            displayName: "Elio Backend",
            isActive: true,
            sortOrder: 10,
            capabilities: { chat: true, privateContent: true },
          },
          {
            id: "unknown",
            displayName: "Unknown",
            isActive: true,
            sortOrder: 1,
            capabilities: { chat: true, privateContent: true },
          },
        ],
      }),
    );

    expect(snapshot.catalog.characters.map((item) => item.slug)).toEqual([
      "maya",
      "elio",
    ]);
    expect(snapshot.catalog.getById("maya-tan")).toMatchObject({
      displayName: "Maya Backend",
      assets: { avatar: "/images/avatar/maya.png" },
      capabilities: { chat: true, privateZone: false, tip: true },
    });
    expect(snapshot.defaultCharacter.id).toBe("elio");
  });

  it("sorts catalog entries and rejects duplicate identities", () => {
    const catalog = createCharacterCatalog([...CHARACTERS].reverse());
    expect(catalog.characters.map((character) => character.slug)).toEqual([
      "elio",
      "maya",
      "nayeli",
    ]);
    expect(() =>
      createCharacterCatalog([CHARACTERS[0], CHARACTERS[0]]),
    ).toThrow("Duplicate character identity");
  });

  it("resolves characters by id and normalized slug", () => {
    expect(getCharacterById("maya-tan")?.displayName).toBe("Maya Tan");
    expect(getCharacterById("maya-tan")?.assets.splashCover).toBe(
      "/images/cover/maya-home.webp",
    );
    expect(getCharacterBySlug(" NAYELI ")?.displayName).toBe(
      "Nayeli Cervantes",
    );
    expect(getCharacterById("missing")).toBeNull();
    expect(getCharacterBySlug("missing")).toBeNull();
  });

  it("describes route capabilities and character-specific chat copy", () => {
    for (const character of CHARACTERS) {
      expect(character.capabilities).toEqual({
        chat: true,
        privateZone: true,
        tip: true,
      });
      expect(character.tagline.length).toBeGreaterThan(0);
      expect(character.emptyChatGreeting.length).toBeGreaterThan(0);
      expect(character.assets.chatBackground).toBe(
        "/images/chat/bg-chatpage.png",
      );
    }
  });

  it("references local assets that exist under public", () => {
    for (const character of CHARACTERS) {
      for (const assetPath of Object.values(character.assets)) {
        expect(
          existsSync(join(process.cwd(), "public", assetPath)),
          assetPath,
        ).toBe(true);
      }
    }
  });
});
