/**
 * `lib/db/error-list-source.ts` の期待値表（指示書BD §3-1・§3-2・§5-3。D-184）。
 *
 * 表 S（`isErrorListSelectable()`。★`ERROR_LIST_UNSELECTABLE_ERROR_TYPES` との完全一致★）
 * | #  | error_type              | 期待  | 理由 |
 * |----|-------------------------|-------|------|
 * | S1 | `D-S_CONTROL_FAILURE`   | false | 制御盤（ユーザー判断 2026-10-27） |
 * | S2 | `SECURITY_ON`           | false | セキュリティ |
 * | S3 | `NOT_STARTED`           | true  | ★清掃開始失敗は選べる★ |
 * | S4 | `EmergencyStop`         | true  | ロボットのエラー |
 * | S5 | `security_on`           | true  | ★小文字は別の値（緩めない）★ |
 * | S6 | `d-s_control_failure`   | true  | 同上 |
 * | S7 | ` SECURITY_ON`          | true  | ★前後の空白も緩めない★ |
 * | S8 | `SECURITY_ON_X`         | true  | ★前方一致にしない★ |
 * | S9 | `""`                    | true  | 空文字は選べない種類に入っていない |
 *
 * 表 A（`activeErrorListItems()`。選択肢）
 * | 入力 id（並び）       | inactive / deleted_at                  | 期待 |
 * |-----------------------|----------------------------------------|------|
 * | 131, 2, 10, 3, 4, 1   | 3 = inactive 1・4 = deleted_at あり・10 = inactive 2 | [1, 2, 131]（★id の昇順。数値で並べる — 文字列順なら 10 / 131 が 2 より前★） |
 */
import { describe, expect, it } from "vitest";
import { ERROR_LIST_NOTE_REQUIRED_IDS, ERROR_LIST_UNSELECTABLE_ERROR_TYPES } from "@/lib/config";
import {
  ACTIVE_ERROR_LIST_WHERE,
  activeErrorListItems,
  EMPTY_ERROR_LIST_INDEX,
  ERROR_LIST_SELECT,
  errorListIdsOf,
  errorListIndex,
  errorListItemFromRow,
  isActiveErrorListRow,
  isErrorListSelectable,
  loadErrorListIndexByIds,
  resolveErrorList,
  type ErrorListRowSource,
  type ErrorListSourcePrisma,
} from "@/lib/db/error-list-source";

const DELETED_AT = new Date("2026-10-01T10:00:00Z");

function listRow(id: number, overrides: Partial<ErrorListRowSource> = {}): ErrorListRowSource {
  return {
    id: BigInt(id),
    error_category_main_en: `MAIN-EN-${id}`,
    error_category_main_ja: `分類${id}`,
    error_category_sub_en: `sub-en-${id}`,
    error_category_sub_ja: `小分類${id}`,
    error_detail_en: `detail-en-${id}`,
    error_detail_ja: `詳細${id}`,
    inactive: 0,
    deleted_at: null,
    ...overrides,
  };
}

describe("isErrorListSelectable（表 S）", () => {
  it("設定の前提: 選べない種類は D-S_CONTROL_FAILURE と SECURITY_ON の2つ（NOT_STARTED を含まない）", () => {
    expect([...ERROR_LIST_UNSELECTABLE_ERROR_TYPES]).toEqual(["D-S_CONTROL_FAILURE", "SECURITY_ON"]);
  });

  it.each([
    ["S1", "D-S_CONTROL_FAILURE", false],
    ["S2", "SECURITY_ON", false],
    ["S3", "NOT_STARTED", true],
    ["S4", "EmergencyStop", true],
    ["S5", "security_on", true],
    ["S6", "d-s_control_failure", true],
    ["S7", " SECURITY_ON", true],
    ["S8", "SECURITY_ON_X", true],
    ["S9", "", true],
  ] as const)("%s: %j → %s", (_no, errorType, expected) => {
    expect(isErrorListSelectable(errorType)).toBe(expected);
  });
});

describe("errorListItemFromRow", () => {
  it("文字列は変換しない・id は number・inactive / deleted_at は出さない", () => {
    expect(errorListItemFromRow(listRow(40, { inactive: 1, deleted_at: DELETED_AT }))).toEqual({
      id: 40,
      error_category_main_en: "MAIN-EN-40",
      error_category_main_ja: "分類40",
      error_category_sub_en: "sub-en-40",
      error_category_sub_ja: "小分類40",
      error_detail_en: "detail-en-40",
      error_detail_ja: "詳細40",
      note_required: false,
    });
  });

  it("★note_required は ERROR_LIST_NOTE_REQUIRED_IDS（131）だけ true★", () => {
    expect([...ERROR_LIST_NOTE_REQUIRED_IDS]).toEqual([131]);
    expect(errorListItemFromRow(listRow(131)).note_required).toBe(true);
    expect(errorListItemFromRow(listRow(130)).note_required).toBe(false);
    expect(errorListItemFromRow(listRow(1)).note_required).toBe(false);
  });
});

describe("activeErrorListItems（表 A）", () => {
  const input = [
    listRow(131),
    listRow(2),
    listRow(10, { inactive: 2 }),
    listRow(3, { inactive: 1 }),
    listRow(4, { deleted_at: DELETED_AT }),
    listRow(1),
  ];

  it("★有効な行だけ・id の昇順（数値順）★", () => {
    expect(activeErrorListItems(input).map((i) => i.id)).toEqual([1, 2, 131]);
  });

  it("入力の配列を並べ替えない", () => {
    const before = input.map((r) => r.id);
    activeErrorListItems(input);
    expect(input.map((r) => r.id)).toEqual(before);
  });

  it("isActiveErrorListRow: inactive 0 かつ deleted_at null だけ true", () => {
    expect(isActiveErrorListRow({ inactive: 0, deleted_at: null })).toBe(true);
    expect(isActiveErrorListRow({ inactive: 1, deleted_at: null })).toBe(false);
    expect(isActiveErrorListRow({ inactive: 0, deleted_at: DELETED_AT })).toBe(false);
  });

  it("SQL 側の写し（ACTIVE_ERROR_LIST_WHERE）は inactive 0・deleted_at null", () => {
    expect(ACTIVE_ERROR_LIST_WHERE).toEqual({ inactive: 0, deleted_at: null });
  });
});

describe("名前解決（errorListIndex / resolveErrorList）", () => {
  const index = errorListIndex([listRow(40), listRow(7, { inactive: 1 }), listRow(8, { deleted_at: DELETED_AT })]);

  it("null → null（未選択）", () => {
    expect(resolveErrorList(null, index)).toBeNull();
  });

  it("索引に無い id → null（不明）", () => {
    expect(resolveErrorList(BigInt(9999), index)).toBeNull();
  });

  it("★無効化・削除済みの行も解決する（絞らない）★", () => {
    expect(resolveErrorList(BigInt(7), index)?.id).toBe(7);
    expect(resolveErrorList(BigInt(8), index)?.id).toBe(8);
    expect(resolveErrorList(BigInt(40), index)?.error_detail_ja).toBe("詳細40");
  });

  it("空の索引は何も解決しない", () => {
    expect(resolveErrorList(BigInt(40), EMPTY_ERROR_LIST_INDEX)).toBeNull();
  });
});

describe("errorListIdsOf", () => {
  it("非 null だけを重複なく集める（初出順）", () => {
    const ids = errorListIdsOf([
      { error_list_id: BigInt(40) },
      { error_list_id: null },
      { error_list_id: BigInt(131) },
      { error_list_id: BigInt(40) },
    ]);
    expect(ids).toEqual([BigInt(40), BigInt(131)]);
  });

  it("全部 null なら []", () => {
    expect(errorListIdsOf([{ error_list_id: null }])).toEqual([]);
  });
});

describe("loadErrorListIndexByIds（Prisma のスタブ）", () => {
  function stub(rows: ErrorListRowSource[]) {
    const calls: Parameters<ErrorListSourcePrisma["errorList"]["findMany"]>[0][] = [];
    const prisma: ErrorListSourcePrisma = {
      errorList: {
        findMany: async (args) => {
          calls.push(args);
          const wanted = args.where.id.in.map((id) => id.toString());
          return rows.filter((r) => wanted.includes(r.id.toString()));
        },
      },
    };
    return { prisma, calls };
  }

  it("★id が0件ならクエリを撃たない★", async () => {
    const { prisma, calls } = stub([listRow(40)]);
    const index = await loadErrorListIndexByIds(prisma, []);
    expect(index.size).toBe(0);
    expect(calls).toHaveLength(0);
  });

  it("★1回だけ撃つ・where は id IN だけ（inactive / deleted_at で絞らない）・select は ERROR_LIST_SELECT★", async () => {
    const { prisma, calls } = stub([listRow(40), listRow(7, { inactive: 1 }), listRow(8, { deleted_at: DELETED_AT })]);
    const index = await loadErrorListIndexByIds(prisma, [BigInt(40), BigInt(7), BigInt(8), BigInt(9999)]);
    expect(calls).toHaveLength(1);
    expect(calls[0]).toEqual({
      where: { id: { in: [BigInt(40), BigInt(7), BigInt(8), BigInt(9999)] } },
      select: ERROR_LIST_SELECT,
    });
    expect([...index.keys()].sort()).toEqual(["40", "7", "8"]);
  });

  it("ERROR_LIST_SELECT は API に要る列と inactive / deleted_at（created_at / updated_at は読まない）", () => {
    expect(Object.keys(ERROR_LIST_SELECT).sort()).toEqual(
      [
        "deleted_at",
        "error_category_main_en",
        "error_category_main_ja",
        "error_category_sub_en",
        "error_category_sub_ja",
        "error_detail_en",
        "error_detail_ja",
        "id",
        "inactive",
      ].sort()
    );
  });
});
