import SuperJSON from "superjson";
import { describe, expect, it, test, vi } from "vitest";

import type { Session } from "@homarr/auth";
import type { Database, InferInsertModel } from "@homarr/db";
import { and, createId, eq, not } from "@homarr/db";
import {
  boardGroupPermissions,
  boards,
  boardUserPermissions,
  groupMembers,
  groupPermissions,
  groups,
  integrationItems,
  integrations,
  itemLayouts,
  items,
  layouts,
  sectionLayouts,
  sections,
  serverSettings,
  users,
} from "@homarr/db/schema";
import { createDb } from "@homarr/db/test";
import type { BoardPermission, GroupPermissionKey } from "@homarr/definitions";

import type { RouterOutputs } from "../..";
import { boardRouter } from "../board";
import * as boardAccess from "../board/board-access";
import { expectToBeDefined } from "./helper";

const defaultCreatorId = createId();
const defaultSession = {
  user: {
    id: defaultCreatorId,
    permissions: [],
    colorScheme: "light",
  },
  expires: new Date().toISOString(),
} satisfies Session;

// Mock the auth module to return an empty session
vi.mock("@homarr/auth", () => ({ auth: () => ({}) as Session }));

const createRandomUserAsync = async (db: Database) => {
  const userId = createId();
  await db.insert(users).values({
    id: userId,
    homeBoardId: null,
  });
  return userId;
};

describe("getAllBoards should return all boards accessable to the current user", () => {
  test("without session it should return only public boards", async () => {
    // Arrange
    const db = createDb();
    const caller = boardRouter.createCaller({ db, deviceType: undefined, session: null });

    const user1 = await createRandomUserAsync(db);
    const user2 = await createRandomUserAsync(db);

    await db.insert(boards).values([
      {
        id: createId(),
        name: "public",
        creatorId: user1,
        isPublic: true,
      },
      {
        id: createId(),
        name: "private",
        creatorId: user2,
        isPublic: false,
      },
    ]);

    // Act
    const result = await caller.getAllBoards();

    // Assert
    expect(result.length).toBe(1);
    expect(result[0]?.name).toBe("public");
  });

  test("with session containing board-view-all permission it should return all boards", async () => {
    // Arrange
    const db = createDb();
    const caller = boardRouter.createCaller({
      db,
      deviceType: undefined,
      session: {
        user: {
          id: defaultCreatorId,
          permissions: ["board-view-all"],
          colorScheme: "light",
        },
        expires: new Date().toISOString(),
      },
    });

    const user1 = await createRandomUserAsync(db);
    const user2 = await createRandomUserAsync(db);

    await db.insert(boards).values([
      {
        id: createId(),
        name: "public",
        creatorId: user1,
        isPublic: true,
      },
      {
        id: createId(),
        name: "private",
        creatorId: user2,
        isPublic: false,
      },
    ]);

    // Act
    const result = await caller.getAllBoards();

    // Assert
    expect(result.length).toBe(2);
    expect(result.map((board) => board.name)).toEqual(["public", "private"]);
  });

  test("with session user beeing creator it should return all private boards of them", async () => {
    // Arrange
    const db = createDb();
    const caller = boardRouter.createCaller({ db, deviceType: undefined, session: defaultSession });

    const user1 = await createRandomUserAsync(db);
    const user2 = await createRandomUserAsync(db);
    await db.insert(users).values({
      id: defaultCreatorId,
    });

    await db.insert(boards).values([
      {
        id: createId(),
        name: "public",
        creatorId: user1,
        isPublic: true,
      },
      {
        id: createId(),
        name: "private",
        creatorId: user2,
        isPublic: false,
      },
      {
        id: createId(),
        name: "private2",
        creatorId: defaultCreatorId,
        isPublic: false,
      },
    ]);

    // Act
    const result = await caller.getAllBoards();

    // Assert
    expect(result.length).toBe(2);
    expect(result.map(({ name }) => name)).toStrictEqual(["public", "private2"]);
  });

  test.each([["view"], ["modify"]] satisfies [BoardPermission][])(
    "with %s group board permission it should show board",
    async (permission) => {
      // Arrange
      const db = createDb();
      const caller = boardRouter.createCaller({
        db,
        deviceType: undefined,
        session: defaultSession,
      });

      const user1 = await createRandomUserAsync(db);
      const user2 = await createRandomUserAsync(db);
      await db.insert(users).values({
        id: defaultCreatorId,
      });
      const boardId = createId();

      await db.insert(boards).values([
        {
          id: createId(),
          name: "public",
          creatorId: user1,
          isPublic: true,
        },
        {
          id: boardId,
          name: "private1",
          creatorId: user2,
          isPublic: false,
        },
        {
          id: createId(),
          name: "private2",
          creatorId: user2,
          isPublic: false,
        },
      ]);

      const groupId = createId();
      await db.insert(groups).values({
        id: groupId,
        name: "group1",
        position: 1,
      });

      await db.insert(groupMembers).values({
        userId: defaultSession.user.id,
        groupId,
      });

      await db.insert(boardGroupPermissions).values({
        groupId,
        permission,
        boardId,
      });

      // Act
      const result = await caller.getAllBoards();

      // Assert
      expect(result.length).toBe(2);
      expect(result.map(({ name }) => name)).toStrictEqual(["public", "private1"]);
    },
  );

  test.each([["view"], ["modify"]] satisfies [BoardPermission][])(
    "with %s user board permission it should show board",
    async (permission) => {
      // Arrange
      const db = createDb();
      const caller = boardRouter.createCaller({
        db,
        deviceType: undefined,
        session: defaultSession,
      });

      const user1 = await createRandomUserAsync(db);
      const user2 = await createRandomUserAsync(db);
      await db.insert(users).values({
        id: defaultCreatorId,
      });
      const boardId = createId();

      await db.insert(boards).values([
        {
          id: createId(),
          name: "public",
          creatorId: user1,
          isPublic: true,
        },
        {
          id: boardId,
          name: "private1",
          creatorId: user2,
          isPublic: false,
        },
        {
          id: createId(),
          name: "private2",
          creatorId: user2,
          isPublic: false,
        },
      ]);

      await db.insert(boardUserPermissions).values({
        userId: defaultSession.user.id,
        permission,
        boardId,
      });

      // Act
      const result = await caller.getAllBoards();

      // Assert
      expect(result.length).toBe(2);
      expect(result.map(({ name }) => name)).toStrictEqual(["public", "private1"]);
    },
  );
});

describe("createBoard should create a new board", () => {
  test("should create a new board with permission board-create", async () => {
    // Arrange
    const db = createDb();
    const session = {
      ...defaultSession,
      user: {
        ...defaultSession.user,
        permissions: ["board-create"] satisfies GroupPermissionKey[],
      },
    };
    const caller = boardRouter.createCaller({ db, deviceType: undefined, session });

    await db.insert(users).values({
      id: defaultCreatorId,
    });

    // Act
    await caller.createBoard({ name: "newBoard", columnCount: 24, isPublic: true });

    // Assert
    const dbBoard = await db.query.boards.findFirst({
      with: {
        sections: true,
        layouts: true,
      },
    });
    expect(dbBoard).toBeDefined();
    expect(dbBoard?.name).toBe("newBoard");
    expect(dbBoard?.isPublic).toBe(true);
    expect(dbBoard?.creatorId).toBe(defaultCreatorId);

    expect(dbBoard?.sections.length).toBe(1);
    const firstSection = dbBoard?.sections.at(0);
    expect(firstSection?.kind).toBe("empty");
    expect(firstSection?.xOffset).toBe(0);
    expect(firstSection?.yOffset).toBe(0);

    expect(dbBoard?.layouts.length).toBe(1);
    const firstLayout = dbBoard?.layouts.at(0);
    expect(firstLayout?.columnCount).toBe(24);
    expect(firstLayout?.breakpoint).toBe(0);
  });

  test("should throw error when user has no board-create permission", async () => {
    // Arrange
    const db = createDb();
    const caller = boardRouter.createCaller({ db, deviceType: undefined, session: defaultSession });

    // Act
    const actAsync = async () => await caller.createBoard({ name: "newBoard", columnCount: 12, isPublic: true });

    // Assert
    await expect(actAsync()).rejects.toThrowError("Permission denied");
  });
});

describe("rename board should rename board", () => {
  test("should rename board", async () => {
    // Arrange
    const db = createDb();
    const caller = boardRouter.createCaller({ db, deviceType: undefined, session: defaultSession });
    const spy = vi.spyOn(boardAccess, "throwIfActionForbiddenAsync");

    await db.insert(users).values({
      id: defaultCreatorId,
    });
    const boardId = createId();
    await db.insert(boards).values({
      id: boardId,
      name: "oldName",
      creatorId: defaultCreatorId,
    });

    // Act
    await caller.renameBoard({ id: boardId, name: "newName" });

    // Assert
    const dbBoard = await db.query.boards.findFirst({
      where: eq(boards.id, boardId),
    });
    expect(dbBoard).toBeDefined();
    expect(dbBoard?.name).toBe("newName");
    expect(spy).toHaveBeenCalledWith(expect.anything(), expect.anything(), "full");
  });

  test("should throw error when similar board name exists", async () => {
    // Arrange
    const db = createDb();
    const caller = boardRouter.createCaller({ db, deviceType: undefined, session: defaultSession });

    await db.insert(users).values({
      id: defaultCreatorId,
    });
    const boardId = createId();
    await db.insert(boards).values({
      id: boardId,
      name: "oldName",
      creatorId: defaultCreatorId,
    });
    await db.insert(boards).values({
      id: createId(),
      name: "newName",
      creatorId: defaultCreatorId,
    });

    // Act
    const actAsync = async () => await caller.renameBoard({ id: boardId, name: "Newname" });

    // Assert
    await expect(actAsync()).rejects.toThrowError("Board with similar name already exists");
  });

  test("should throw error when board not found", async () => {
    // Arrange
    const db = createDb();
    const caller = boardRouter.createCaller({ db, deviceType: undefined, session: defaultSession });

    // Act
    const actAsync = async () => await caller.renameBoard({ id: "nonExistentBoardId", name: "newName" });

    // Assert
    await expect(actAsync()).rejects.toThrowError("Board not found");
  });
});

describe("changeBoardVisibility should change board visibility", () => {
  test.each([["public"], ["private"]] satisfies ["private" | "public"][])(
    "should change board visibility to %s",
    async (visibility) => {
      // Arrange
      const db = createDb();
      const caller = boardRouter.createCaller({ db, deviceType: undefined, session: defaultSession });
      const spy = vi.spyOn(boardAccess, "throwIfActionForbiddenAsync");

      await db.insert(users).values({
        id: defaultCreatorId,
      });
      const boardId = createId();
      await db.insert(boards).values({
        id: boardId,
        name: "board",
        creatorId: defaultCreatorId,
        isPublic: visibility === "public",
      });

      // Act
      await caller.changeBoardVisibility({
        id: boardId,
        visibility,
      });

      // Assert
      const dbBoard = await db.query.boards.findFirst({
        where: eq(boards.id, boardId),
      });
      expect(dbBoard).toBeDefined();
      expect(dbBoard?.isPublic).toBe(visibility === "public");
      expect(spy).toHaveBeenCalledWith(expect.anything(), expect.anything(), "full");
    },
  );
});

describe("deleteBoard should delete board", () => {
  test("should delete board", async () => {
    // Arrange
    const db = createDb();
    const caller = boardRouter.createCaller({ db, deviceType: undefined, session: defaultSession });
    const spy = vi.spyOn(boardAccess, "throwIfActionForbiddenAsync");

    await db.insert(users).values({
      id: defaultCreatorId,
    });
    const boardId = createId();
    await db.insert(boards).values({
      id: boardId,
      name: "board",
      creatorId: defaultCreatorId,
    });

    // Act
    await caller.deleteBoard({ id: boardId });

    // Assert
    const dbBoard = await db.query.boards.findFirst({
      where: eq(boards.id, boardId),
    });
    expect(dbBoard).toBeUndefined();
    expect(spy).toHaveBeenCalledWith(expect.anything(), expect.anything(), "full");
  });

  test("should throw error when board not found", async () => {
    // Arrange
    const db = createDb();
    const caller = boardRouter.createCaller({ db, deviceType: undefined, session: defaultSession });

    // Act
    const actAsync = async () => await caller.deleteBoard({ id: "nonExistentBoardId" });

    // Assert
    await expect(actAsync()).rejects.toThrowError("Board not found");
  });
});

describe("getHomeBoard should return home board", () => {
  test("should return user home board when user has one", async () => {
    // Arrange
    const spy = vi.spyOn(boardAccess, "throwIfActionForbiddenAsync");
    const db = createDb();
    const caller = boardRouter.createCaller({ db, deviceType: undefined, session: defaultSession });

    const fullBoardProps = await createFullBoardAsync(db, "home");
    await db
      .update(users)
      .set({
        homeBoardId: fullBoardProps.boardId,
      })
      .where(eq(users.id, defaultCreatorId));

    // Act
    const result = await caller.getHomeBoard();

    // Assert
    expectInputToBeFullBoardWithName(result, {
      name: "home",
      ...fullBoardProps,
    });
    expect(spy).toHaveBeenCalledWith(expect.anything(), expect.anything(), "view");
  });
  test("should return global home board when user doesn't have one", async () => {
    // Arrange
    const spy = vi.spyOn(boardAccess, "throwIfActionForbiddenAsync");
    const db = createDb();
    const caller = boardRouter.createCaller({ db, deviceType: undefined, session: defaultSession });

    const fullBoardProps = await createFullBoardAsync(db, "home");
    await db.insert(serverSettings).values({
      settingKey: "board",
      value: SuperJSON.stringify({ homeBoardId: fullBoardProps.boardId }),
    });

    // Act
    const result = await caller.getHomeBoard();

    // Assert
    expectInputToBeFullBoardWithName(result, {
      name: "home",
      ...fullBoardProps,
    });
    expect(spy).toHaveBeenCalledWith(expect.anything(), expect.anything(), "view");
  });
  test("should throw error when home board not configured in serverSettings", async () => {
    // Arrange
    const db = createDb();
    const caller = boardRouter.createCaller({ db, deviceType: undefined, session: defaultSession });
    await createFullBoardAsync(db, "home");

    // Act
    const actAsync = async () => await caller.getHomeBoard();

    // Assert
    await expect(actAsync()).rejects.toThrowError("No home board found");
  });
});

describe("getBoardByName should return board by name", () => {
  it.each([["default"], ["something"]])("should return board by name %s when present", async (name) => {
    // Arrange
    const spy = vi.spyOn(boardAccess, "throwIfActionForbiddenAsync");
    const db = createDb();
    const caller = boardRouter.createCaller({ db, deviceType: undefined, session: defaultSession });

    const fullBoardProps = await createFullBoardAsync(db, name);

    // Act
    const result = await caller.getBoardByName({ name });

    // Assert
    expectInputToBeFullBoardWithName(result, {
      name,
      ...fullBoardProps,
    });
    expect(spy).toHaveBeenCalledWith(expect.anything(), expect.anything(), "view");
  });

  it("should throw error when not present", async () => {
    // Arrange
    const db = createDb();
    const caller = boardRouter.createCaller({ db, deviceType: undefined, session: defaultSession });
    await createFullBoardAsync(db, "default");

    // Act
    const actAsync = async () => await caller.getBoardByName({ name: "nonExistentBoard" });

    // Assert
    await expect(actAsync()).rejects.toThrowError("Board not found");
  });
});

describe("savePartialBoardSettings should save general settings", () => {
  it("should save general settings", async () => {
    // Arrange
    const spy = vi.spyOn(boardAccess, "throwIfActionForbiddenAsync");
    const db = createDb();
    const caller = boardRouter.createCaller({ db, deviceType: undefined, session: defaultSession });

    const newPageTitle = "newPageTitle";
    const newMetaTitle = "newMetaTitle";
    const newLogoImageUrl = "http://logo.image/url.png";
    const newFaviconImageUrl = "http://favicon.image/url.png";
    const newBackgroundImageAttachment = "scroll";
    const newBackgroundImageSize = "cover";
    const newBackgroundImageRepeat = "repeat";
    const newBackgroundImageUrl = "http://background.image/url.png";
    const newCustomCss = "body { background-color: blue; }";
    const newOpacity = 0.8;
    const newPrimaryColor = "#0000ff";
    const newSecondaryColor = "#ff00ff";

    const { boardId } = await createFullBoardAsync(db, "default");

    // Act
    await caller.savePartialBoardSettings({
      pageTitle: newPageTitle,
      metaTitle: newMetaTitle,
      logoImageUrl: newLogoImageUrl,
      faviconImageUrl: newFaviconImageUrl,
      backgroundImageAttachment: newBackgroundImageAttachment,
      backgroundImageRepeat: newBackgroundImageRepeat,
      backgroundImageSize: newBackgroundImageSize,
      backgroundImageUrl: newBackgroundImageUrl,
      customCss: newCustomCss,
      opacity: newOpacity,
      primaryColor: newPrimaryColor,
      secondaryColor: newSecondaryColor,
      id: boardId,
    });

    // Assert
    const dbBoard = await db.query.boards.findFirst({
      where: eq(boards.id, boardId),
    });
    expect(dbBoard).toBeDefined();
    expect(dbBoard?.pageTitle).toBe(newPageTitle);
    expect(dbBoard?.metaTitle).toBe(newMetaTitle);
    expect(dbBoard?.logoImageUrl).toBe(newLogoImageUrl);
    expect(dbBoard?.faviconImageUrl).toBe(newFaviconImageUrl);
    expect(dbBoard?.backgroundImageAttachment).toBe(newBackgroundImageAttachment);
    expect(dbBoard?.backgroundImageRepeat).toBe(newBackgroundImageRepeat);
    expect(dbBoard?.backgroundImageSize).toBe(newBackgroundImageSize);
    expect(dbBoard?.backgroundImageUrl).toBe(newBackgroundImageUrl);
    expect(dbBoard?.customCss).toBe(newCustomCss);
    expect(dbBoard?.opacity).toBe(newOpacity);
    expect(dbBoard?.primaryColor).toBe(newPrimaryColor);
    expect(dbBoard?.secondaryColor).toBe(newSecondaryColor);

    expect(spy).toHaveBeenCalledWith(expect.anything(), expect.anything(), "modify");
  });

  it("should throw error when board not found", async () => {
    const db = createDb();
    const caller = boardRouter.createCaller({ db, deviceType: undefined, session: defaultSession });

    const actAsync = async () =>
      await caller.savePartialBoardSettings({
        pageTitle: "newPageTitle",
        metaTitle: "newMetaTitle",
        logoImageUrl: "http://logo.image/url.png",
        faviconImageUrl: "http://favicon.image/url.png",
        id: "nonExistentBoardId",
      });

    await expect(actAsync()).rejects.toThrowError("Board not found");
  });
});

describe("saveBoard should save full board", () => {
  it("should remove section when not present in input", async () => {
    const spy = vi.spyOn(boardAccess, "throwIfActionForbiddenAsync");
    const db = createDb();
    const caller = boardRouter.createCaller({ db, deviceType: undefined, session: defaultSession });

    const { boardId, sectionId } = await createFullBoardAsync(db, "default");

    await caller.saveBoard({
      id: boardId,
      sections: [
        {
          id: createId(),
          kind: "empty",
          yOffset: 0,
          xOffset: 0,
        },
      ],
      items: [],
    });

    const board = await db.query.boards.findFirst({
      where: eq(boards.id, boardId),
      with: {
        sections: true,
      },
    });

    const section = await db.query.boards.findFirst({
      where: eq(sections.id, sectionId),
    });

    const definedBoard = expectToBeDefined(board);
    expect(definedBoard.sections.length).toBe(1);
    expect(definedBoard.sections[0]?.id).not.toBe(sectionId);
    expect(section).toBeUndefined();
    expect(spy).toHaveBeenCalledWith(expect.anything(), expect.anything(), "modify");
  });
  it("should remove item when not present in input", async () => {
    const spy = vi.spyOn(boardAccess, "throwIfActionForbiddenAsync");
    const db = createDb();
    const caller = boardRouter.createCaller({ db, deviceType: undefined, session: defaultSession });

    const { boardId, itemId, sectionId, layoutId } = await createFullBoardAsync(db, "default");

    await caller.saveBoard({
      id: boardId,
      sections: [
        {
          id: sectionId,
          kind: "empty",
          yOffset: 0,
          xOffset: 0,
        },
      ],
      items: [
        {
          id: createId(),
          kind: "clock",
          options: { is24HourFormat: true },
          integrationIds: [],
          layouts: [
            {
              layoutId,
              sectionId,
              height: 1,
              width: 1,
              xOffset: 0,
              yOffset: 0,
            },
          ],
          advancedOptions: {},
        },
      ],
    });

    const board = await db.query.boards.findFirst({
      where: eq(boards.id, boardId),
      with: {
        sections: true,
        items: true,
      },
    });

    const item = await db.query.items.findFirst({
      where: eq(items.id, itemId),
    });

    const definedBoard = expectToBeDefined(board);
    expect(definedBoard.sections.length).toBe(1);
    expect(definedBoard.items.length).toBe(1);
    expect(definedBoard.items[0]?.id).not.toBe(itemId);
    expect(item).toBeUndefined();
    expect(spy).toHaveBeenCalledWith(expect.anything(), expect.anything(), "modify");
  });
  it("should remove integration reference when not present in input", async () => {
    const spy = vi.spyOn(boardAccess, "throwIfActionForbiddenAsync");
    const db = createDb();
    const caller = boardRouter.createCaller({ db, deviceType: undefined, session: defaultSession });
    const anotherIntegration = {
      id: createId(),
      kind: "adGuardHome",
      name: "AdGuard Home",
      url: "http://localhost:3000",
    } as const;

    const { boardId, itemId, integrationId, sectionId, layoutId } = await createFullBoardAsync(db, "default");
    await db.insert(integrations).values(anotherIntegration);

    await caller.saveBoard({
      id: boardId,
      sections: [
        {
          id: sectionId,
          kind: "empty",
          xOffset: 0,
          yOffset: 0,
        },
      ],
      items: [
        {
          id: itemId,
          kind: "clock",
          options: { is24HourFormat: true },
          integrationIds: [anotherIntegration.id],
          layouts: [
            {
              layoutId,
              sectionId,
              height: 1,
              width: 1,
              xOffset: 0,
              yOffset: 0,
            },
          ],
          advancedOptions: {},
        },
      ],
    });

    const board = await db.query.boards.findFirst({
      where: eq(boards.id, boardId),
      with: {
        sections: true,
        items: {
          with: {
            integrations: true,
          },
        },
      },
    });

    const integration = await db.query.integrationItems.findFirst({
      where: eq(integrationItems.integrationId, integrationId),
    });

    const definedBoard = expectToBeDefined(board);
    expect(definedBoard.sections.length).toBe(1);
    expect(definedBoard.items.length).toBe(1);
    const firstItem = expectToBeDefined(definedBoard.items[0]);
    expect(firstItem.integrations.length).toBe(1);
    expect(firstItem.integrations[0]?.integrationId).not.toBe(integrationId);
    expect(integration).toBeUndefined();
    expect(spy).toHaveBeenCalledWith(expect.anything(), expect.anything(), "modify");
  });
  it.each([[{ kind: "empty" as const }], [{ kind: "category" as const, collapsed: false, name: "My first category" }]])(
    "should add section when present in input",
    async (partialSection) => {
      const spy = vi.spyOn(boardAccess, "throwIfActionForbiddenAsync");
      const db = createDb();
      const caller = boardRouter.createCaller({ db, deviceType: undefined, session: defaultSession });

      const { boardId, sectionId } = await createFullBoardAsync(db, "default");

      const newSectionId = createId();
      await caller.saveBoard({
        id: boardId,
        sections: [
          {
            id: newSectionId,
            xOffset: 0,
            yOffset: 1,
            ...partialSection,
          },
          {
            id: sectionId,
            kind: "empty",
            xOffset: 0,
            yOffset: 0,
          },
        ],
        items: [],
      });

      const board = await db.query.boards.findFirst({
        where: eq(boards.id, boardId),
        with: {
          sections: true,
        },
      });

      const section = await db.query.sections.findFirst({
        where: eq(sections.id, newSectionId),
      });

      const definedBoard = expectToBeDefined(board);
      expect(definedBoard.sections.length).toBe(2);
      const addedSection = expectToBeDefined(definedBoard.sections.find((section) => section.id === newSectionId));
      expect(addedSection).toBeDefined();
      expect(addedSection.id).toBe(newSectionId);
      expect(addedSection.kind).toBe(partialSection.kind);
      expect(addedSection.yOffset).toBe(1);
      if ("name" in partialSection) {
        expect(addedSection.name).toBe(partialSection.name);
      }
      expect(section).toBeDefined();
      expect(spy).toHaveBeenCalledWith(expect.anything(), expect.anything(), "modify");
    },
  );
  it("should add item when present in input", async () => {
    const spy = vi.spyOn(boardAccess, "throwIfActionForbiddenAsync");
    const db = createDb();
    const caller = boardRouter.createCaller({ db, deviceType: undefined, session: defaultSession });

    const { boardId, sectionId, layoutId } = await createFullBoardAsync(db, "default");

    const newItemId = createId();
    await caller.saveBoard({
      id: boardId,
      sections: [
        {
          id: sectionId,
          kind: "empty",
          yOffset: 0,
          xOffset: 0,
        },
      ],
      items: [
        {
          id: newItemId,
          kind: "clock",
          options: { is24HourFormat: true },
          integrationIds: [],
          layouts: [
            {
              layoutId,
              sectionId,
              height: 1,
              width: 1,
              xOffset: 3,
              yOffset: 2,
            },
          ],
          advancedOptions: {},
        },
      ],
    });

    const board = await db.query.boards.findFirst({
      where: eq(boards.id, boardId),
      with: {
        sections: true,
        items: {
          with: {
            layouts: true,
          },
        },
      },
    });

    const item = await db.query.items.findFirst({
      where: eq(items.id, newItemId),
    });

    const definedBoard = expectToBeDefined(board);
    expect(definedBoard.sections.length).toBe(1);
    expect(definedBoard.items.length).toBe(1);
    const addedItem = expectToBeDefined(definedBoard.items.find((item) => item.id === newItemId));
    expect(addedItem).toBeDefined();
    expect(addedItem.id).toBe(newItemId);
    expect(addedItem.kind).toBe("clock");
    expect(addedItem.options).toBe(SuperJSON.stringify({ is24HourFormat: true }));
    const firstLayout = expectToBeDefined(addedItem.layouts[0]);
    expect(firstLayout.sectionId).toBe(sectionId);
    expect(firstLayout.height).toBe(1);
    expect(firstLayout.width).toBe(1);
    expect(firstLayout.xOffset).toBe(3);
    expect(firstLayout.yOffset).toBe(2);
    expect(item).toBeDefined();
    expect(spy).toHaveBeenCalledWith(expect.anything(), expect.anything(), "modify");
  });
  it("should add integration reference when present in input", async () => {
    const spy = vi.spyOn(boardAccess, "throwIfActionForbiddenAsync");
    const db = createDb();
    const caller = boardRouter.createCaller({ db, deviceType: undefined, session: defaultSession });
    const integration = {
      id: createId(),
      kind: "plex",
      name: "Plex",
      url: "http://plex.local",
    } as const;

    const { boardId, itemId, sectionId, layoutId } = await createFullBoardAsync(db, "default");
    await db.insert(integrations).values(integration);

    await caller.saveBoard({
      id: boardId,
      sections: [
        {
          id: sectionId,
          kind: "empty",
          xOffset: 0,
          yOffset: 0,
        },
      ],
      items: [
        {
          id: itemId,
          kind: "clock",
          options: { is24HourFormat: true },
          integrationIds: [integration.id],
          layouts: [
            {
              sectionId,
              layoutId,
              height: 1,
              width: 1,
              xOffset: 0,
              yOffset: 0,
            },
          ],
          advancedOptions: {},
        },
      ],
    });

    const board = await db.query.boards.findFirst({
      where: eq(boards.id, boardId),
      with: {
        sections: true,
        items: {
          with: {
            integrations: true,
          },
        },
      },
    });

    const integrationItem = await db.query.integrationItems.findFirst({
      where: eq(integrationItems.integrationId, integration.id),
    });

    const definedBoard = expectToBeDefined(board);
    expect(definedBoard.sections.length).toBe(1);
    const firstItem = expectToBeDefined(definedBoard.items.find((item) => item.id === itemId));
    expect(firstItem.integrations.length).toBe(1);
    expect(firstItem.integrations[0]?.integrationId).toBe(integration.id);
    expect(integrationItem).toBeDefined();
    expect(spy).toHaveBeenCalledWith(expect.anything(), expect.anything(), "modify");
  });
  it("should update section when present in input", async () => {
    const db = createDb();
    const caller = boardRouter.createCaller({ db, deviceType: undefined, session: defaultSession });

    const { boardId, sectionId } = await createFullBoardAsync(db, "default");
    const newSectionId = createId();
    await db.insert(sections).values({
      id: newSectionId,
      kind: "category",
      name: "Before",
      yOffset: 1,
      xOffset: 0,
      boardId,
    });

    await caller.saveBoard({
      id: boardId,
      sections: [
        {
          id: sectionId,
          kind: "category",
          yOffset: 1,
          xOffset: 0,
          name: "Test",
          collapsed: true,
        },
        {
          id: newSectionId,
          kind: "category",
          name: "After",
          yOffset: 0,
          xOffset: 0,
          collapsed: false,
        },
      ],
      items: [],
    });

    const board = await db.query.boards.findFirst({
      where: eq(boards.id, boardId),
      with: {
        sections: true,
      },
    });

    const definedBoard = expectToBeDefined(board);
    expect(definedBoard.sections.length).toBe(2);
    const firstSection = expectToBeDefined(definedBoard.sections.find((section) => section.id === sectionId));
    expect(firstSection.id).toBe(sectionId);
    expect(firstSection.kind).toBe("empty");
    expect(firstSection.yOffset).toBe(1);
    expect(firstSection.name).toBe(null);
    const secondSection = expectToBeDefined(definedBoard.sections.find((section) => section.id === newSectionId));
    expect(secondSection.id).toBe(newSectionId);
    expect(secondSection.kind).toBe("category");
    expect(secondSection.yOffset).toBe(0);
    expect(secondSection.name).toBe("After");
  });
  it("should update item when present in input", async () => {
    const spy = vi.spyOn(boardAccess, "throwIfActionForbiddenAsync");
    const db = createDb();
    const caller = boardRouter.createCaller({ db, deviceType: undefined, session: defaultSession });

    const { boardId, itemId, sectionId, layoutId } = await createFullBoardAsync(db, "default");

    await caller.saveBoard({
      id: boardId,
      sections: [
        {
          id: sectionId,
          kind: "empty",
          yOffset: 0,
          xOffset: 0,
        },
      ],
      items: [
        {
          id: itemId,
          kind: "clock",
          options: { is24HourFormat: false },
          integrationIds: [],
          layouts: [
            {
              layoutId,
              sectionId,
              height: 3,
              width: 2,
              xOffset: 7,
              yOffset: 5,
            },
          ],
          advancedOptions: {},
        },
      ],
    });

    const board = await db.query.boards.findFirst({
      where: eq(boards.id, boardId),
      with: {
        sections: true,
        items: {
          with: {
            layouts: true,
          },
        },
      },
    });

    const definedBoard = expectToBeDefined(board);
    expect(definedBoard.sections.length).toBe(1);
    expect(definedBoard.items.length).toBe(1);
    const firstItem = expectToBeDefined(definedBoard.items.find((item) => item.id === itemId));
    expect(firstItem.id).toBe(itemId);
    expect(firstItem.kind).toBe("clock");
    expect(SuperJSON.parse<{ is24HourFormat: boolean }>(firstItem.options).is24HourFormat).toBe(false);
    const firstLayout = expectToBeDefined(firstItem.layouts[0]);
    expect(firstLayout.sectionId).toBe(sectionId);
    expect(firstLayout.height).toBe(3);
    expect(firstLayout.width).toBe(2);
    expect(firstLayout.xOffset).toBe(7);
    expect(firstLayout.yOffset).toBe(5);
    expect(spy).toHaveBeenCalledWith(expect.anything(), expect.anything(), "modify");
  });
  it("should fail when board not found", async () => {
    const db = createDb();
    const caller = boardRouter.createCaller({ db, deviceType: undefined, session: defaultSession });

    const actAsync = async () =>
      await caller.saveBoard({
        id: "nonExistentBoardId",
        sections: [],
        items: [],
      });

    await expect(actAsync()).rejects.toThrowError("Board not found");
  });
});

describe("getBoardPermissions should return board permissions", () => {
  test("should return board permissions", async () => {
    // Arrange
    const db = createDb();
    const caller = boardRouter.createCaller({ db, deviceType: undefined, session: defaultSession });
    const spy = vi.spyOn(boardAccess, "throwIfActionForbiddenAsync");

    const user1 = await createRandomUserAsync(db);
    const user2 = await createRandomUserAsync(db);
    await db.insert(users).values({
      id: defaultCreatorId,
    });

    const boardId = createId();
    await db.insert(boards).values({
      id: boardId,
      name: "board",
      creatorId: defaultCreatorId,
    });

    await db.insert(boardUserPermissions).values([
      {
        userId: user1,
        permission: "view",
        boardId,
      },
      {
        userId: user2,
        permission: "modify",
        boardId,
      },
    ]);

    const groupId = createId();
    await db.insert(groups).values({
      id: groupId,
      name: "group1",
      position: 1,
    });

    await db.insert(boardGroupPermissions).values({
      groupId,
      permission: "view",
      boardId,
    });

    await db.insert(groupPermissions).values({
      groupId,
      permission: "admin",
    });

    // Act
    const result = await caller.getBoardPermissions({ id: boardId });

    // Assert
    expect(result.groups).toEqual([{ group: { id: groupId, name: "group1" }, permission: "view" }]);
    expect(result.users).toEqual(
      expect.arrayContaining([
        {
          user: { id: user1, name: null, image: null },
          permission: "view",
        },
        {
          user: { id: user2, name: null, image: null },
          permission: "modify",
        },
      ]),
    );
    expect(result.inherited).toEqual([{ group: { id: groupId, name: "group1" }, permission: "admin" }]);
    expect(spy).toHaveBeenCalledWith(expect.anything(), expect.anything(), "full");
  });
});

describe("saveUserBoardPermissions should save user board permissions", () => {
  test.each([["view"], ["modify"]] satisfies [BoardPermission][])(
    "should save user board permissions",
    async (permission) => {
      // Arrange
      const db = createDb();
      const caller = boardRouter.createCaller({ db, deviceType: undefined, session: defaultSession });
      const spy = vi.spyOn(boardAccess, "throwIfActionForbiddenAsync");

      const user1 = await createRandomUserAsync(db);
      await db.insert(users).values({
        id: defaultCreatorId,
      });

      const boardId = createId();
      await db.insert(boards).values({
        id: boardId,
        name: "board",
        creatorId: defaultCreatorId,
      });

      // Act
      await caller.saveUserBoardPermissions({
        entityId: boardId,
        permissions: [
          {
            principalId: user1,
            permission,
          },
        ],
      });

      // Assert
      const dbUserPermission = await db.query.boardUserPermissions.findFirst({
        where: eq(boardUserPermissions.userId, user1),
      });
      expect(dbUserPermission).toBeDefined();
      expect(spy).toHaveBeenCalledWith(expect.anything(), expect.anything(), "full");
    },
  );
});

describe("saveGroupBoardPermissions should save group board permissions", () => {
  test.each([["view"], ["modify"]] satisfies [BoardPermission][])(
    "should save group board permissions",
    async (permission) => {
      // Arrange
      const db = createDb();
      const caller = boardRouter.createCaller({ db, deviceType: undefined, session: defaultSession });
      const spy = vi.spyOn(boardAccess, "throwIfActionForbiddenAsync");

      await db.insert(users).values({
        id: defaultCreatorId,
      });

      const groupId = createId();
      await db.insert(groups).values({
        id: groupId,
        name: "group1",
        position: 1,
      });

      const boardId = createId();
      await db.insert(boards).values({
        id: boardId,
        name: "board",
        creatorId: defaultCreatorId,
      });

      // Act
      await caller.saveGroupBoardPermissions({
        entityId: boardId,
        permissions: [
          {
            principalId: groupId,
            permission,
          },
        ],
      });

      // Assert
      const dbGroupPermission = await db.query.boardGroupPermissions.findFirst({
        where: eq(boardGroupPermissions.groupId, groupId),
      });
      expect(dbGroupPermission).toBeDefined();
      expect(spy).toHaveBeenCalledWith(expect.anything(), expect.anything(), "full");
    },
  );
});

const createExistingLayout = (id: string) => ({
  id,
  name: "Base",
  columnCount: 10,
  breakpoint: 0,
});
const createNewLayout = (columnCount: number) => ({
  id: createId(),
  name: "New layout",
  columnCount,
  breakpoint: 1400,
});
describe("saveLayouts should save layout changes", () => {
  test("should add layout when not present in database", async () => {
    // Arrange
    const db = createDb();
    const caller = boardRouter.createCaller({ db, deviceType: undefined, session: defaultSession });

    const { boardId, layoutId } = await createFullBoardAsync(db, "default");
    const newLayout = createNewLayout(12);

    // Act
    await caller.saveLayouts({
      id: boardId,
      layouts: [createExistingLayout(layoutId), newLayout],
    });

    // Assert
    const layout = await db.query.layouts.findFirst({
      where: not(eq(layouts.id, layoutId)),
    });

    const definedLayout = expectToBeDefined(layout);
    expect(definedLayout.name).toBe(newLayout.name);
    expect(definedLayout.columnCount).toBe(newLayout.columnCount);
    expect(definedLayout.breakpoint).toBe(newLayout.breakpoint);
  });
  test("should add items and dynamic sections generated from grid-algorithm when new layout is added", async () => {
    // Arrange
    const db = createDb();
    const caller = boardRouter.createCaller({ db, deviceType: undefined, session: defaultSession });

    const { boardId, layoutId, sectionId, itemId } = await createFullBoardAsync(db, "default");
    const assignments = await createItemsAndSectionsAsync(db, {
      boardId,
      layoutId,
      sectionId,
    });
    const newLayout = createNewLayout(3);

    // Act
    await caller.saveLayouts({
      id: boardId,
      layouts: [createExistingLayout(layoutId), newLayout],
    });

    // Assert
    const layout = await db.query.layouts.findFirst({
      where: not(eq(layouts.id, layoutId)),
    });
    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
    await expectLayoutForRootLayoutAsync(db, sectionId, layout!.id, {
      ...assignments.inRoot,
      a: itemId,
    });
    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
    await expectLayoutForDynamicSectionAsync(db, assignments.inRoot.f, layout!.id, assignments.inDynamicSection);
  });
  test("should update layout when present in input", async () => {
    // Arrange
    const db = createDb();
    const caller = boardRouter.createCaller({ db, deviceType: undefined, session: defaultSession });

    const { boardId, layoutId } = await createFullBoardAsync(db, "default");
    const updatedLayout = createExistingLayout(layoutId);
    updatedLayout.breakpoint = 1400;
    updatedLayout.name = "Updated layout";

    // Act
    await caller.saveLayouts({
      id: boardId,
      layouts: [updatedLayout],
    });

    // Assert
    const layout = await db.query.layouts.findFirst({
      where: eq(layouts.id, layoutId),
    });

    const definedLayout = expectToBeDefined(layout);
    expect(definedLayout.name).toBe(updatedLayout.name);
    expect(definedLayout.columnCount).toBe(updatedLayout.columnCount);
    expect(definedLayout.breakpoint).toBe(updatedLayout.breakpoint);
  });
  test("should update position of items when column count changes", async () => {
    // Arrange
    const db = createDb();
    const caller = boardRouter.createCaller({ db, deviceType: undefined, session: defaultSession });

    const { boardId, layoutId, sectionId, itemId } = await createFullBoardAsync(db, "default");
    const assignments = await createItemsAndSectionsAsync(db, {
      boardId,
      layoutId,
      sectionId,
    });
    const updatedLayout = createExistingLayout(layoutId);
    updatedLayout.columnCount = 3;

    // Act
    await caller.saveLayouts({
      id: boardId,
      layouts: [updatedLayout],
    });

    // Assert
    await expectLayoutForRootLayoutAsync(db, sectionId, layoutId, {
      ...assignments.inRoot,
      a: itemId,
    });
    await expectLayoutForDynamicSectionAsync(db, assignments.inRoot.f, layoutId, assignments.inDynamicSection);
  });
  test("should remove layout when not present in input", async () => {
    // Arrange
    const db = createDb();
    const caller = boardRouter.createCaller({ db, deviceType: undefined, session: defaultSession });

    const { boardId, layoutId } = await createFullBoardAsync(db, "default");

    // Act
    await caller.saveLayouts({
      id: boardId,
      layouts: [createNewLayout(12)],
    });

    // Assert
    const layout = await db.query.layouts.findFirst({
      where: eq(layouts.id, layoutId),
    });
    expect(layout).toBeUndefined();
  });
  test("should fail when board not found", async () => {
    // Arrange
    const db = createDb();
    const caller = boardRouter.createCaller({ db, deviceType: undefined, session: defaultSession });

    const { layoutId } = await createFullBoardAsync(db, "default");

    // Act
    const actAsync = async () =>
      await caller.saveLayouts({
        id: createId(),
        layouts: [createExistingLayout(layoutId)],
      });

    // Assert
    await expect(actAsync()).rejects.toThrowError("Board not found");
  });
});

const expectInputToBeFullBoardWithName = (
  input: RouterOutputs["board"]["getHomeBoard"],
  props: { name: string } & Awaited<ReturnType<typeof createFullBoardAsync>>,
) => {
  expect(input.id).toBe(props.boardId);
  expect(input.name).toBe(props.name);
  expect(input.sections.length).toBe(1);
  const firstSection = expectToBeDefined(input.sections[0]);
  expect(firstSection.id).toBe(props.sectionId);
  expect(input.items.length).toBe(1);
  const firstItem = expectToBeDefined(input.items[0]);
  expect(firstItem.id).toBe(props.itemId);
  expect(firstItem.kind).toBe("clock");
  if (firstItem.kind === "clock") {
    expect(firstItem.options.is24HourFormat).toBe(true);
  }
  expect(firstItem.integrationIds.length).toBe(1);
  const firstIntegration = expectToBeDefined(firstItem.integrationIds[0]);
  expect(firstIntegration).toBe(props.integrationId);
};

const createFullBoardAsync = async (db: Database, name: string) => {
  await db.insert(users).values({
    id: defaultCreatorId,
  });

  const boardId = createId();
  await db.insert(boards).values({
    id: boardId,
    name,
    creatorId: defaultCreatorId,
  });

  const layoutId = createId();
  await db.insert(layouts).values({
    id: layoutId,
    name: "Base",
    columnCount: 10,
    breakpoint: 0,
    boardId,
  });

  const sectionId = createId();
  await db.insert(sections).values({
    id: sectionId,
    kind: "empty",
    yOffset: 0,
    xOffset: 0,
    boardId,
  });

  const itemId = createId();
  await db.insert(items).values({
    id: itemId,
    kind: "clock",
    boardId,
    options: SuperJSON.stringify({ is24HourFormat: true }),
  });

  await db.insert(itemLayouts).values({
    height: 1,
    width: 1,
    xOffset: 0,
    yOffset: 0,
    sectionId,
    itemId,
    layoutId,
  });

  const integrationId = createId();
  await db.insert(integrations).values({
    id: integrationId,
    kind: "adGuardHome",
    name: "AdGuard Home",
    url: "http://localhost:3000",
  });

  await db.insert(integrationItems).values({
    integrationId,
    itemId,
  });

  return {
    boardId,
    sectionId,
    layoutId,
    itemId,
    integrationId,
  };
};

const addItemAsync = async (
  db: Database,
  item: Partial<Pick<InferInsertModel<typeof itemLayouts>, "height" | "width" | "xOffset" | "yOffset">> & {
    sectionId: string;
    layoutId: string;
    boardId: string;
  },
) => {
  const itemId = createId();
  await db.insert(items).values({
    id: itemId,
    kind: "clock",
    boardId: item.boardId,
    options: SuperJSON.stringify({ is24HourFormat: true }),
  });
  await db.insert(itemLayouts).values({
    itemId,
    layoutId: item.layoutId,
    sectionId: item.sectionId,
    height: item.height ?? 1,
    width: item.width ?? 1,
    xOffset: item.xOffset ?? 0,
    yOffset: item.yOffset ?? 0,
  });
  return itemId;
};

const addDynamicSectionAsync = async (
  db: Database,
  section: Partial<Pick<InferInsertModel<typeof sectionLayouts>, "xOffset" | "yOffset" | "width" | "height">> & {
    parentSectionId: string;
    boardId: string;
    layoutId: string;
  },
) => {
  const sectionId = createId();
  await db.insert(sections).values({
    id: sectionId,
    kind: "dynamic",
    boardId: section.boardId,
  });
  await db.insert(sectionLayouts).values({
    parentSectionId: section.parentSectionId,
    layoutId: section.layoutId,
    sectionId,
    xOffset: section.xOffset ?? 0,
    yOffset: section.yOffset ?? 0,
    width: section.width ?? 1,
    height: section.height ?? 1,
  });
  return sectionId;
};

const createItemsAndSectionsAsync = async (
  db: Database,
  options: { boardId: string; sectionId: string; layoutId: string },
) => {
  const { boardId, layoutId, sectionId } = options;
  // From:
  // abbbbbccdd
  // efffffccdd
  // efffffggdd
  // efffffgg
  // To:
  // a
  // bbb
  // cce
  // cce
  // dde
  // dd
  // dd
  // fff
  // fff
  // fff
  // fff
  // gg
  // gg
  const itemB = await addItemAsync(db, { boardId, layoutId, sectionId, xOffset: 1, width: 5 });
  const itemC = await addItemAsync(db, { boardId, layoutId, sectionId, xOffset: 6, width: 2, height: 2 });
  const itemD = await addItemAsync(db, { boardId, layoutId, sectionId, xOffset: 8, width: 2, height: 3 });
  const itemE = await addItemAsync(db, { boardId, layoutId, sectionId, yOffset: 1, height: 3 });
  const sectionF = await addDynamicSectionAsync(db, {
    yOffset: 1,
    xOffset: 1,
    width: 5,
    height: 3,
    parentSectionId: sectionId,
    boardId,
    layoutId,
  });
  const sectionG = await addDynamicSectionAsync(db, {
    yOffset: 2,
    xOffset: 6,
    width: 2,
    height: 2,
    parentSectionId: sectionId,
    boardId,
    layoutId,
  });
  // From:
  // hhhhh
  // iiijj
  // iii
  // To:
  // hhh
  // iii
  // iii
  // jj
  const itemH = await addItemAsync(db, { boardId, layoutId, sectionId: sectionF, width: 5 });
  const itemI = await addItemAsync(db, { boardId, layoutId, sectionId: sectionF, width: 3, height: 2, yOffset: 1 });
  const itemJ = await addItemAsync(db, { boardId, layoutId, sectionId: sectionF, width: 2, yOffset: 1, xOffset: 2 });

  return {
    inRoot: {
      b: itemB,
      c: itemC,
      d: itemD,
      e: itemE,
      f: sectionF,
      g: sectionG,
    },
    inDynamicSection: {
      h: itemH,
      i: itemI,
      j: itemJ,
    },
  };
};

const expectLayoutForRootLayoutAsync = async (
  db: Database,
  sectionId: string,
  layoutId: string,
  assignments: Record<string, string>,
) => {
  await expectLayoutInSectionAsync(
    db,
    sectionId,
    layoutId,
    `
a
bbb
cce
cce
dde
dd
dd
fff
fff
fff
fff
gg
gg`,
    assignments,
  );
};

const expectLayoutForDynamicSectionAsync = async (
  db: Database,
  sectionId: string,
  layoutId: string,
  assignments: Record<string, string>,
) => {
  await expectLayoutInSectionAsync(
    db,
    sectionId,
    layoutId,
    `
hhh
iii
iii
jj`,
    assignments,
  );
};

const expectLayoutInSectionAsync = async (
  db: Database,
  sectionId: string,
  layoutId: string,
  layout: string,
  assignments: Record<string, string>,
) => {
  const itemsInSection = await db.query.itemLayouts.findMany({
    where: and(eq(itemLayouts.sectionId, sectionId), eq(itemLayouts.layoutId, layoutId)),
  });
  const sectionsInSection = await db.query.sectionLayouts.findMany({
    where: and(eq(sectionLayouts.parentSectionId, sectionId), eq(sectionLayouts.layoutId, layoutId)),
  });
  const entries = [...itemsInSection, ...sectionsInSection];

  const lines = layout.split("\n").slice(1);
  const keys = Object.keys(assignments);
  const positions: Record<string, { x: number; y: number; w: number; h: number }> = {};
  for (let yOffset = 0; yOffset < lines.length; yOffset++) {
    const line = lines[yOffset];
    if (!line) continue;
    for (let xOffset = 0; xOffset < line.length; xOffset++) {
      const char = line[xOffset];
      if (!char) continue;
      if (!keys.includes(char)) continue;
      if (char in positions) continue;
      const width = line.split("").filter((lineChar) => lineChar === char).length;
      const height = lines.slice(yOffset).filter((line) => line.substring(xOffset).startsWith(char)).length;
      positions[char] = { x: xOffset, y: yOffset, w: width, h: height };
    }
  }

  for (const [key, { x, y, w, h }] of Object.entries(positions)) {
    const entry = entries.find((entry) => ("itemId" in entry ? entry.itemId : entry.sectionId) === assignments[key]);
    expect(entry, `Expect entry for ${key} to be defined in assignments=${JSON.stringify(assignments)}`).toBeDefined();
    expect(entry?.xOffset, `Expect xOffset of entry for ${key} to be ${x} for entry=${JSON.stringify(entry)}`).toBe(x);
    expect(entry?.yOffset, `Expect yOffset of entry for ${key} to be ${y} for entry=${JSON.stringify(entry)}`).toBe(y);
    expect(entry?.width, `Expect width of entry for ${key} to be ${w} for entry=${JSON.stringify(entry)}`).toBe(w);
    expect(entry?.height, `Expect height of entry for ${key} to be ${h} for entry=${JSON.stringify(entry)}`).toBe(h);
  }
};
