import { describe, expect, it } from "vitest";

import { filterFixturePrechecks } from "@/lib/db/fixture-prechecks";
import { prechecks } from "@/lib/fixtures";
import { filterPrechecksByDate, type PrecheckQuery } from "@/lib/prechecks/query";
import type { PrecheckDbQuery } from "@/lib/types";

/**
 * 指示書AC ★段階4★ — fixtures 版も同じ引数を受け取り、同じ結果を返す（段階2 D-5 の作法）。
 *
 * ★★`USE_FIXTURES` の true/false で件数が変わってはいけない★★（指示書AC 禁止事項6）。
 * ★絞り込み規則の「正」は `filterPrechecksByDate()`★ — ここはその写し（C-5）。
 *
 * ★fixtures の**既存の値**は変更していない★（禁止事項5）— ここは読むだけ。
 */

const ALL: PrecheckDbQuery = { date_from: null, date_to: null, facility_ids: null };

function memoryQuery(partial: Partial<PrecheckQuery>): PrecheckQuery {
  return {
    dateFrom: null,
    dateTo: null,
    status: "all",
    // ★絞り込まない（指示書AK 段階2 で足った2つ。`null` = 条件なし）★
    groupId: null,
    customerId: null,
    page: 1,
    perPage: 50,
    ...partial,
  };
}

/** fixtures の中でデータが載っている日（`at()` が相対日で作るので実行日から決める） */
const days = [...new Set(prechecks.map((r) => r.scheduled_at.slice(0, 10)))].sort();

/** `severity` ごとの件数（★片方だけ絞れている壊れ方の検出★） */
function bySeverity(items: { severity: string }[]): Record<string, number> {
  const out: Record<string, number> = {};
  for (const item of items) out[item.severity] = (out[item.severity] ?? 0) + 1;
  return out;
}

describe("filterFixturePrechecks（指示書AC 段階4）", () => {
  it("#1 範囲なし（全部 null）→ fixtures の全件", () => {
    expect(filterFixturePrechecks(prechecks, ALL)).toHaveLength(prechecks.length);
  });

  it("★★#2 日付範囲 → `filterPrechecksByDate()` と完全一致★★", () => {
    for (const day of days) {
      const sql = filterFixturePrechecks(prechecks, { ...ALL, date_from: day, date_to: day });
      const memory = filterPrechecksByDate(
        prechecks,
        memoryQuery({ dateFrom: day, dateTo: day })
      );
      expect(sql).toEqual(memory);
      expect(sql.length).toBeGreaterThan(0);
    }
  });

  it("#2 複数日の範囲でも一致する", () => {
    const sql = filterFixturePrechecks(prechecks, {
      ...ALL,
      date_from: days[0],
      date_to: days[days.length - 1],
    });
    expect(sql).toEqual(
      filterPrechecksByDate(
        prechecks,
        memoryQuery({ dateFrom: days[0], dateTo: days[days.length - 1] })
      )
    );
    expect(sql).toHaveLength(prechecks.length);
  });

  it("★★#3 `severity` の内訳が全件版と一致する★★", () => {
    for (const day of days) {
      const sql = filterFixturePrechecks(prechecks, { ...ALL, date_from: day, date_to: day });
      const memory = filterPrechecksByDate(
        prechecks,
        memoryQuery({ dateFrom: day, dateTo: day })
      );
      expect(bySeverity(sql)).toEqual(bySeverity(memory));
    }
    // ★全件は事前チェック異常だけ★（ユーザー判断 2026-10-29。旧: `launch_failed`（出発失敗）もそろっていた）
    expect(Object.keys(bySeverity([...prechecks]))).toEqual(["precheck"]);
  });

  it("★★#4 絞り込みの基準は `scheduled_at`（＝本清掃の予定時刻）★★。`detected_at` ではない", () => {
    // ★`severity: "precheck"` の行は `detected_at` が予定時刻の60分前★ —
    //   `detected_at` で絞ると、日をまたぐ行（0時台の清掃の1時間前＝前日）が消える。
    const crossingMidnight = prechecks.filter(
      (r) => r.detected_at.slice(0, 10) !== r.scheduled_at.slice(0, 10)
    );
    for (const row of crossingMidnight) {
      const day = row.scheduled_at.slice(0, 10);
      const rows = filterFixturePrechecks(prechecks, { ...ALL, date_from: day, date_to: day });
      expect(rows.map((r) => r.id)).toContain(row.id);
      const detectedDay = row.detected_at.slice(0, 10);
      expect(
        filterFixturePrechecks(prechecks, {
          ...ALL,
          date_from: detectedDay,
          date_to: detectedDay,
        }).map((r) => r.id)
      ).not.toContain(row.id);
    }
  });

  it("上限はその日を**含む**（★翌日 00:00 未満★）", () => {
    const day = days[0];
    const rows = filterFixturePrechecks(prechecks, { ...ALL, date_to: day });
    expect(rows.some((r) => r.scheduled_at.slice(0, 10) === day)).toBe(true);
    expect(rows.every((r) => r.scheduled_at.slice(0, 10) <= day)).toBe(true);
  });

  it("`date_from` がデータの最古日より前 → #1 と同じ件数", () => {
    expect(filterFixturePrechecks(prechecks, { ...ALL, date_from: "2000-01-01" })).toHaveLength(
      prechecks.length
    );
  });

  it("`facility_ids` で絞れる（★`null` は「絞らない」★）", () => {
    const id = prechecks[0].facility_id;
    const rows = filterFixturePrechecks(prechecks, { ...ALL, facility_ids: [id] });
    expect(rows).toEqual(prechecks.filter((r) => r.facility_id === id));
    expect(rows.length).toBeGreaterThan(0);
    expect(filterFixturePrechecks(prechecks, { ...ALL, facility_ids: null })).toHaveLength(
      prechecks.length
    );
  });

  it("★複数の施設ID（分類の解決結果）で絞れる★", () => {
    const ids = [...new Set(prechecks.map((r) => r.facility_id))].slice(0, 2);
    expect(ids.length).toBe(2);
    const rows = filterFixturePrechecks(prechecks, { ...ALL, facility_ids: ids });
    expect(rows).toEqual(prechecks.filter((r) => ids.includes(r.facility_id)));
    expect(rows.length).toBeGreaterThan(0);
  });

  it("存在しない `facility_ids` → 0件（★条件が静かに消えていないことの検出★）", () => {
    expect(filterFixturePrechecks(prechecks, { ...ALL, facility_ids: [999_999] })).toEqual(
      []
    );
  });

  /**
   * ★★`[]`（分類で絞って0件ヒット）は0件★★ — ★`null`（絞らない）に化けさせないこと★
   * （★罠1 / 落とし穴1★。全件表示に化けても例外もテスト失敗も出ないので、ここで固定する）。
   */
  it("★`facility_ids: []` → 0件（全件に化けないこと）★", () => {
    expect(filterFixturePrechecks(prechecks, { ...ALL, facility_ids: [] })).toEqual([]);
  });

  it("日付と施設の AND が効く", () => {
    const target = prechecks[0];
    const day = target.scheduled_at.slice(0, 10);
    const rows = filterFixturePrechecks(prechecks, {
      date_from: day,
      date_to: day,
      facility_ids: [target.facility_id],
    });
    expect(
      rows.every(
        (r) => r.facility_id === target.facility_id && r.scheduled_at.slice(0, 10) === day
      )
    ).toBe(true);
    expect(rows.map((r) => r.id)).toContain(target.id);
  });

  it("★入力の配列を壊さない（純粋関数）★", () => {
    const before = prechecks.length;
    filterFixturePrechecks(prechecks, { ...ALL, date_from: days[0], date_to: days[0] });
    expect(prechecks).toHaveLength(before);
  });
});
