import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";

import { CLEAN_TIMETABLE_PRECHECK } from "@/lib/config";
import { db } from "@/lib/db";
import type { CleanLogDbQuery } from "@/lib/types";

/**
 * Prisma 版 `cleanLogRunMeta()`（指示書AV 段階3-3）。
 *
 * 固定すること:
 *   - ★`where` が `cleanLogs()` の `where` と同じ★（4-8。`mainTimetableWhere()` の共有）
 *   - ★2表（`clean_timetables` / `clean_timetable_histories`）の両方に同じ引数が渡る★（`findTimetables()`）
 *   - ★`end_at` は壁時計値のまま ISO になる★（`2026-10-05 21:10:00` → `"2026-10-05T21:10:00+09:00"`。
 *     9時間を足さない・引かない。`CLAUDE.md` §3 / 指示書AV 4-3）
 *
 * ★★DBには接続しない★★ — `USE_FIXTURES` を `false` に固定して `prismaSource` を走らせ、
 *   `getPrisma()` が最初に見るシングルトン `globalThis.__dwatchPrisma` にスタブを置く
 *   （`app/api/dashboard/__tests__/route.test.ts` と同じ作法。★`afterEach` で必ず片付ける★）。
 */

vi.mock("@/lib/db/use-fixtures", () => ({ USE_FIXTURES: false }));

type Args = Record<string, unknown>;

const calls: { cleanTimetable: Args[]; cleanTimetableHistory: Args[] } = {
  cleanTimetable: [],
  cleanTimetableHistory: [],
};

/** 本表が返す行（★`select` で射影して返す★） */
let mainRows: Record<string, unknown>[] = [];

function project(row: Record<string, unknown>, select: unknown): Record<string, unknown> {
  if (!select) return row;
  const out: Record<string, unknown> = {};
  for (const key of Object.keys(select as Record<string, unknown>)) out[key] = row[key];
  return out;
}

/**
 * ★`cleanTimetable` / `cleanTimetableHistory` 以外のモデルは空を返す★
 * （`cleanLogs()` は本清掃が0件なら関連テーブルを引かないが、念のため全モデルを空で受ける）。
 */
function prismaStub(): typeof globalThis.__dwatchPrisma {
  const empty = { findMany: async () => [] };
  const known: Record<string, unknown> = {
    cleanTimetable: {
      findMany: async (args?: Args) => {
        calls.cleanTimetable.push(args ?? {});
        return mainRows.map((row) => project(row, args?.select));
      },
    },
    cleanTimetableHistory: {
      findMany: async (args?: Args) => {
        calls.cleanTimetableHistory.push(args ?? {});
        return [];
      },
    },
  };
  return new Proxy(known, {
    get: (target, prop: string) => target[prop] ?? empty,
  }) as unknown as typeof globalThis.__dwatchPrisma;
}

/** DB に入っているのと同じ「壁時計値としての `Date`」（D-115。他のテストと同じヘルパー） */
function dbWallClock(jstIso: string): Date {
  return new Date(jstIso.replace("+09:00", "Z"));
}

/** メタの呼び出し ＝ `select` のキーがちょうど `id` / `section_id` / `end_at` の3つ */
function isMetaCall(args: Args): boolean {
  const select = args.select as Record<string, unknown> | undefined;
  if (!select) return false;
  return Object.keys(select).sort().join(",") === "end_at,id,section_id";
}

beforeEach(() => {
  calls.cleanTimetable = [];
  calls.cleanTimetableHistory = [];
  mainRows = [];
  globalThis.__dwatchPrisma = prismaStub();
});

afterEach(() => {
  // ★★他のテストへ漏らさないこと★★（`getPrisma()` はここを最初に見る）
  globalThis.__dwatchPrisma = undefined;
});

describe("Prisma 版 cleanLogRunMeta()（指示書AV 段階3-3）", () => {
  it.each<[string, CleanLogDbQuery]>([
    ["日付の範囲＋施設", { id: null, date_from: "2026-09-29", date_to: "2026-10-06", facility_ids: [1, 2] }],
    ["施設で絞らない（null）", { id: null, date_from: "2026-09-29", date_to: "2026-10-06", facility_ids: null }],
    ["0件ヒット（[]）", { id: null, date_from: "2026-09-29", date_to: "2026-10-06", facility_ids: [] }],
  ])("★`where` が `cleanLogs()` の `where` と同じ★（%s）", async (_label, query) => {
    await db.cleanLogs(query);
    await db.cleanLogRunMeta(query);

    const logsCall = calls.cleanTimetable.find((c) => "include" in c);
    const metaCall = calls.cleanTimetable.find(isMetaCall);
    expect(logsCall).toBeDefined();
    expect(metaCall).toBeDefined();
    expect(metaCall?.where).toEqual(logsCall?.where);
    // 本清掃だけ・期間が降りている（全件読みに戻っていない）
    expect(metaCall?.where).toMatchObject({ precheck: CLEAN_TIMETABLE_PRECHECK.main });
    expect(metaCall?.where).toHaveProperty("start_at");
  });

  it("★2表の両方に同じ引数が渡る★（`findTimetables()`）", async () => {
    await db.cleanLogRunMeta({ id: null, date_from: "2026-10-05", date_to: "2026-10-05", facility_ids: null });
    const mainCall = calls.cleanTimetable.find(isMetaCall);
    expect(mainCall).toBeDefined();
    expect(calls.cleanTimetableHistory.find(isMetaCall)).toEqual(mainCall);
  });

  it("★`end_at` は壁時計値のまま ISO になる★（9時間を足さない・引かない）", async () => {
    mainRows = [
      {
        id: BigInt(1001),
        section_id: BigInt(77),
        end_at: dbWallClock("2026-10-05T21:10:00+09:00"),
        start_at: dbWallClock("2026-10-05T19:00:00+09:00"),
        facility_id: BigInt(1),
      },
    ];
    const meta = await db.cleanLogRunMeta({
      id: null,
      date_from: "2026-10-05",
      date_to: "2026-10-05",
      facility_ids: null,
    });
    expect(meta).toEqual([
      { id: 1001, section_key: "section:77", planned_end_at: "2026-10-05T21:10:00+09:00" },
    ]);
  });
});
