import { describe, expect, it } from "vitest";
import { db, USE_FIXTURES } from "@/lib/db";
import { resolveFacilityIdsByMasters } from "@/lib/facilities/resolve-by-master";
import { customers, facilities, groups } from "@/lib/fixtures";

/**
 * 分類マスタの fixtures（`groups.ts` / `customers.ts`）の不変条件（指示書AK 段階1）。
 *
 * ------------------------------------------------------------------------
 * ★★何を守るためのテストか★★
 * ------------------------------------------------------------------------
 *  - ★fixtures 側を `notImplemented` にしないこと★（落とし穴9）—
 *    ★`USE_FIXTURES=true` で画面が落ちる★。ここで実際に `db.groups()` を呼んで確かめる
 *  - ★1グループ1顧客にしないこと★（落とし穴10）—
 *    ★3階層のUIを fixtures で確かめられなくなる★
 *  - ★「グループ0」を作らないこと★（落とし穴3）
 *
 * ★テスト実行時は `USE_FIXTURES` の既定（= true）で走る★
 *   （`lib/db/use-fixtures.ts`: `process.env.USE_FIXTURES !== "false"`）。
 */
describe("fixtures: 分類マスタ", () => {
  it("db.groups() / db.customers() が fixtures で動く（notImplemented にしない）", async () => {
    expect(USE_FIXTURES).toBe(true);
    await expect(db.groups()).resolves.toEqual(groups);
    await expect(db.customers()).resolves.toEqual(customers);
  });

  it("★複数グループ・複数顧客がある★（3階層のUIを確かめられること）", () => {
    expect(groups.length).toBeGreaterThanOrEqual(2);
    expect(customers.length).toBeGreaterThanOrEqual(2);
    // ★同一グループに2社★ … グループ単位の集計が「1社に定まらない」側（段階6 の 6-7）
    const byGroup = new Map<number | null, number>();
    for (const c of customers) {
      byGroup.set(c.group_id, (byGroup.get(c.group_id) ?? 0) + 1);
    }
    expect([...byGroup.values()].some((n) => n >= 2)).toBe(true);
    // ★傘下1社のグループ★ … 422 にならない側（6-6）
    expect([...byGroup.values()].some((n) => n === 1)).toBe(true);
    // ★ホールディングスなしの屋号★（`group_id: null`）
    expect(customers.some((c) => c.group_id === null)).toBe(true);
  });

  it("★id=0 のグループが無い／group_id に 0 を書いていない★（D-011）", () => {
    expect(groups.some((g) => g.id === 0)).toBe(false);
    expect(customers.some((c) => c.group_id === 0)).toBe(false);
  });

  it("id が一意で、customers.group_id は実在するグループを指す", () => {
    expect(new Set(groups.map((g) => g.id)).size).toBe(groups.length);
    expect(new Set(customers.map((c) => c.id)).size).toBe(customers.length);
    const ids = new Set(groups.map((g) => g.id));
    for (const c of customers) {
      if (c.group_id !== null) expect(ids.has(c.group_id)).toBe(true);
    }
  });

  /**
   * ★所属は `Facility` 自身が持つ★（指示書AK ★段階5★。段階1〜4 の
   * `customerIdByFacilityId` は削除済み）。★`customers` / `groups` との整合をここで固定する★ —
   * 片方だけ直すと ★屋号で絞ったときだけ施設が消える★という気づけない壊れ方をする。
   */
  it("施設12件すべてが、実在する屋号に1つだけ割り当たっている", () => {
    const customerById = new Map(customers.map((c) => [c.id, c]));
    const groupNameById = new Map(groups.map((g) => [g.id, g.group_name]));
    for (const f of facilities) {
      const customer = customerById.get(f.customer_id);
      expect(customer, `施設 ${f.id} の屋号(${f.customer_id}) が実在しない`).toBeDefined();
      // ★屋号名・ホールディングスは `customers` / `groups` の写しであること★
      expect(f.customer_name).toBe(customer?.customer_name);
      expect(f.group_id).toBe(customer?.group_id);
      // ★`group_id` が null なら名前も null★（「グループ0」を作らない。落とし穴3）
      expect(f.group_name).toBe(
        f.group_id === null ? null : (groupNameById.get(f.group_id) ?? null)
      );
    }
    // ★1顧客に寄せていないこと★（複数の屋号に散らす。落とし穴10）
    expect(new Set(facilities.map((f) => f.customer_id)).size).toBeGreaterThanOrEqual(2);
    // ★ホールディングスなしの施設がある★（段階5 で `group_name: null` を通すケース）
    expect(facilities.some((f) => f.group_id === null)).toBe(true);
  });
});

/**
 * 段階3（施設IDへの解決）が ★`USE_FIXTURES=true` でも動くこと★（指示書AK §6-3 / 落とし穴9）。
 *
 * ★★純粋関数の単体テストは `lib/facilities/__tests__/resolve-by-master.test.ts`★★。
 *   ここで確かめるのは ★fixtures 側のデータ取得（`db.facilityGroupIndex()`）が
 *   `notImplemented` でないこと★ と、★Prisma 版と同じ契約で解決できること★。
 */
describe("fixtures: 分類 → 施設IDの解決（指示書AK 段階3）", () => {
  it("db.facilityGroupIndex() が fixtures で動く（notImplemented にしない）", async () => {
    expect(USE_FIXTURES).toBe(true);
    const refs = await db.facilityGroupIndex();
    // ★施設を間引かないこと★（`deleted_at` の概念が無いので全件。罠7）
    expect(refs).toHaveLength(facilities.length);
    const customerIdByFacilityId = new Map(facilities.map((f) => [f.id, f.customer_id]));
    for (const ref of refs) {
      expect(customerIdByFacilityId.get(ref.id)).toBe(ref.customer_id);
    }
    // ★`group_id` は API の形（0 を漏らさない。落とし穴3）★
    expect(refs.some((r) => r.group_id === 0)).toBe(false);
    expect(refs.some((r) => r.group_id === null)).toBe(true);
  });

  it("★どちらも未指定なら DB を読まずに `null`★（条件なし）", async () => {
    await expect(
      resolveFacilityIdsByMasters({ groupId: null, customerId: null })
    ).resolves.toBeNull();
  });

  it("★屋号で絞ると、その屋号の施設だけが返る★", async () => {
    const expected = facilities
      .filter((f) => f.customer_id === 2)
      .map((f) => f.id)
      .sort((a, b) => a - b);
    const ids = await resolveFacilityIdsByMasters({ groupId: null, customerId: 2 });
    expect(ids).not.toBeNull();
    expect([...(ids as number[])].sort((a, b) => a - b)).toEqual(expected);
  });

  it("★ホールディングスで絞ると、傘下の複数屋号の施設が全部返る★", async () => {
    const customerIds = new Set(
      customers.filter((c) => c.group_id === 1).map((c) => c.id)
    );
    // ★傘下が2社あることを前提にしたケース★（fixtures がそうなっている。落とし穴10）
    expect(customerIds.size).toBeGreaterThanOrEqual(2);
    const expected = facilities
      .filter((f) => customerIds.has(f.customer_id))
      .map((f) => f.id)
      .sort((a, b) => a - b);
    const ids = await resolveFacilityIdsByMasters({ groupId: 1, customerId: null });
    expect([...(ids as number[])].sort((a, b) => a - b)).toEqual(expected);
  });

  it("★★実在しないIDは `[]`（0件ヒット）★★。★`null` にしないこと★", async () => {
    await expect(
      resolveFacilityIdsByMasters({ groupId: null, customerId: 9999 })
    ).resolves.toEqual([]);
  });
});
