import { describe, expect, it } from "vitest";

import { dbDateRange } from "@/lib/db/date-range";
import { fromDbJst, jstIsoFromDb, toDbJst } from "@/lib/format/date";
import { parseDate } from "@/lib/clean-logs/query";

/**
 * 指示書AC 段階2 ★D-2 / C-6★ — 日付境界の補正。
 *
 * ------------------------------------------------------------------------
 * ★★このテストがある理由 — ローカルDBでは間違いを検出できないから★★
 * ------------------------------------------------------------------------
 * `clean_timetables.start_at` は ★日本時間の壁時計値★ が入った DATETIME（D-115）。
 * 一方 `parseDate("2026-09-08")` が返すのは ★真の瞬間★（＝ `2026-09-07T15:00:00Z`）。
 * ★そのまま `where` に渡すと9時間ずれます★。
 *
 * ★★ところがローカルDB（`dwatch_local`）の `start_at`（壁時計）は
 *   0時:155 / 1時:185 / 2時:168 / 3時:30 で、★15時以降が0件★★★
 *   （2026-09-13 本作業の実測。指示書AC C-6 の実測とも一致）。
 *   ★9時間ずらしても日付が変わらない時間帯にしかデータが無い★ため、
 *   ★★補正を忘れてもローカルでは件数が1件も変わりません★★。
 *
 * ★★だから「壁時計 20:00 の清掃」を合成データで必ず通すこと★★（期待値表 #7）。
 * ★このテストを消さないこと★。
 */

/**
 * DB に入っているのと同じ「壁時計値としての `Date`」を作る
 * （`lib/db/__tests__/consecutive-source.test.ts` と同じヘルパー）。
 * ★`fromDbJst()` を通すと元の ISO に戻る★のが正しい状態。
 */
function dbWallClock(jstIso: string): Date {
  return new Date(jstIso.replace("+09:00", "Z"));
}

/** Prisma の `start_at: { gte, lt }` と同じ判定（★DBがやることの写し★） */
function matchesRange(
  startAt: Date,
  range: { gte?: Date; lt?: Date }
): boolean {
  if (range.gte && startAt.getTime() < range.gte.getTime()) return false;
  if (range.lt && startAt.getTime() >= range.lt.getTime()) return false;
  return true;
}

describe("dbDateRange（指示書AC 段階2 D-2 / C-6）", () => {
  it("下限は `toDbJst()` を通した壁時計値になる（★補正を忘れると9時間ずれる★）", () => {
    const range = dbDateRange("2026-09-08", null);

    // ★指示書AC C-6 の実測値をそのまま固定する★
    expect(parseDate("2026-09-08")?.toISOString()).toBe("2026-09-07T15:00:00.000Z");
    expect(range.gte?.toISOString()).toBe("2026-09-08T00:00:00.000Z");
    // ★★`parseDate()` の結果をそのまま渡していないこと★★
    expect(range.gte?.getTime()).not.toBe(parseDate("2026-09-08")?.getTime());
    expect(range.lt).toBeUndefined();
  });

  it("上限はその日を**含む**（＝翌日 00:00 未満）", () => {
    const range = dbDateRange(null, "2026-09-10");
    expect(range.gte).toBeUndefined();
    expect(range.lt?.toISOString()).toBe("2026-09-11T00:00:00.000Z");
  });

  it("どちらも `null` なら空オブジェクト（★呼び出し側は `start_at` のキーごと付けない★）", () => {
    expect(dbDateRange(null, null)).toEqual({});
  });

  it("形式が不正な文字列は「指定なし」として扱う（★検証は `parseCleanLogQuery()` の仕事★）", () => {
    // ★ここで422を返さないこと★ — 2箇所で検証すると片方だけ緩めたときに気づけない
    expect(dbDateRange("2026-9-8", "")).toEqual({});
    expect(dbDateRange("not-a-date", "2026-13-45")).toEqual({});
  });

  it("月をまたぐ範囲でも境界がずれない", () => {
    const range = dbDateRange("2026-08-30", "2026-09-01");
    expect(range.gte?.toISOString()).toBe("2026-08-30T00:00:00.000Z");
    expect(range.lt?.toISOString()).toBe("2026-09-02T00:00:00.000Z");
  });

  describe("★#7 壁時計 20:00 の清掃★（★ローカルDBには1件も無い時間帯★）", () => {
    /** ★9時間ずらすと日付が翌日に化ける行★ */
    const evening = dbWallClock("2026-09-10T20:00:00+09:00");

    it("その行が JST の 9/10 であることを前提として確認する", () => {
      expect(jstIsoFromDb(evening)).toBe("2026-09-10T20:00:00+09:00");
    });

    it("`date_from = date_to = 2026-09-10` の範囲に**含まれる**", () => {
      expect(matchesRange(evening, dbDateRange("2026-09-10", "2026-09-10"))).toBe(true);
    });

    it("前日・翌日だけを指定した範囲には**含まれない**", () => {
      expect(matchesRange(evening, dbDateRange("2026-09-09", "2026-09-09"))).toBe(false);
      expect(matchesRange(evening, dbDateRange("2026-09-11", "2026-09-11"))).toBe(false);
    });

    it("★★`toDbJst()` を忘れた実装なら、この行は 9/10 の範囲から落ちる★★（＝検出できる）", () => {
      // ★補正を忘れた実装★（`parseDate()` の結果をそのまま `where` に渡す）
      const buggy = {
        gte: parseDate("2026-09-10") as Date,
        lt: new Date((parseDate("2026-09-10") as Date).getTime() + 86_400_000),
      };
      // ★9時間ずれるので 20:00 の行は範囲外になる（＝画面から静かに消える）★
      expect(matchesRange(evening, buggy)).toBe(false);
      // ★深夜2時の行なら補正を忘れても落ちない＝ローカルDBでは気づけない★
      const midnight = dbWallClock("2026-09-10T02:00:00+09:00");
      expect(matchesRange(midnight, buggy)).toBe(true);
      expect(matchesRange(midnight, dbDateRange("2026-09-10", "2026-09-10"))).toBe(true);
    });
  });

  it("境界ちょうど（00:00:00 と 23:59:59）が両端とも含まれる", () => {
    const range = dbDateRange("2026-09-10", "2026-09-10");
    expect(matchesRange(dbWallClock("2026-09-10T00:00:00+09:00"), range)).toBe(true);
    expect(matchesRange(dbWallClock("2026-09-10T23:59:59+09:00"), range)).toBe(true);
    expect(matchesRange(dbWallClock("2026-09-09T23:59:59+09:00"), range)).toBe(false);
    expect(matchesRange(dbWallClock("2026-09-11T00:00:00+09:00"), range)).toBe(false);
  });

  it("`toDbJst()` / `fromDbJst()` の往復で元に戻る（★新しい変換を書いていないことの確認★）", () => {
    const from = parseDate("2026-09-08") as Date;
    expect(fromDbJst(toDbJst(from)).getTime()).toBe(from.getTime());
    expect(dbDateRange("2026-09-08", null).gte?.getTime()).toBe(toDbJst(from).getTime());
  });
});
