import { describe, expect, it, vi } from "vitest";

// ★★ルート本体は**静的 import** すること★★ — `it()` の中で `await import()` すると、
//   最初の1本だけがモジュール読み込み（`next/server` / ロガー等）を丸ごと抱え込み、
//   フルスイート実行時に既定のタイムアウトを踏んで不定期に落ちる
//   （`app/api/clean-logs/__tests__/route.test.ts` の実測）。
//   ★`vi.mock()` は import より先に巻き上げられる★ので、静的 import でもスタブは効く。
import { GET } from "@/app/api/clean-logs/export/route";
import { MAX_CSV_ROWS } from "@/lib/config";
import type { ApiError, CleanLogItem } from "@/lib/types";

/**
 * CSVエクスポートの行数上限（ユーザー指示 2026-09-15 / 指示書AI 段階8）
 *
 * ★期待値表（指示書AI 10-5）★
 *
 * | #   | 状況                   | サーバ                  |
 * |-----|------------------------|-------------------------|
 * | 8-1 | 0件                    | 200（ヘッダ行だけのCSV）|
 * | 8-2 | 19,999件               | 200                     |
 * | 8-3 | ★ちょうど 20,000件★    | ★★200（「上限」は含む）★★ |
 * | 8-4 | ★20,001件★             | ★★422★★                 |
 * | 8-5 | 1店舗・2年・1,300件    | 200（期間が長くても行数が少なければ出せる）|
 *
 * ★★8-3 と 8-4 の両方を必ず入れること★★ — 境界の片側だけだと、
 *   `>` と `>=` の取り違えが通ってしまう（`CLAUDE.md` §9.4）。
 *
 * ------------------------------------------------------------------------
 * ★★DBには接続しません★★
 * ------------------------------------------------------------------------
 * `@/lib/db` の `db` をスタブに差し替える。★`resolveFacilityIdsByName()` も
 *   同じ `db`（`facilityNameIndex()`）を通る★ので、ここ1箇所で足りる。
 *
 * ★行数だけが関心事なので、行の中身は使い回しの1件でよい★ —
 *   CSVの中身（列・エスケープ）は `lib/clean-logs/__tests__/csv.test.ts` の担当。
 */

/** `db.cleanLogs()` が返す行数。各テストが書き換える */
let rowCount = 0;

function row(id: number): CleanLogItem {
  return {
    id,
    facility_id: 1,
    facility_name: "メガバリューマート中央本店",
    model_name: "PUDU CC1",
    serial_number: "8111R5628060011",
    scheduled_at: "2026-08-27T02:00:00+09:00",
    status_1h: "待機中",
    status_1h_minutes: 60,
    status_30m: "待機中",
    status_30m_minutes: 30,
    departed: true,
    // `clean_task_reports.status` の生値（CSV実測で最多の値。意味は第10回 N-2 で受領済み。指示書AQ §1-2）
    task_status: 4,
    // ★結果待ち（D-173）★ — レポートあり・確定の行
    result_pending: false,
    completion_rate: 90.1,
    grade: "◎",
    consecutive_problem_days: 0,
    escalation_level: null,
  };
}

vi.mock("@/lib/db", () => ({
  db: {
    cleanLogs: async () =>
      Array.from({ length: rowCount }, (_unused, i) => row(i + 1)),
    // 施設名の解決に使う（施設名を指定しないテストでは読まれない）
    facilityNameIndex: async () => [
      { id: 1, facility_name: "メガバリューマート中央本店" },
    ],
  },
}));

async function get(query = ""): Promise<Response> {
  return GET(new Request(`http://localhost/api/clean-logs/export?${query}`));
}

describe("GET /clean-logs/export — 行数の上限", () => {
  it("8-1: 0件は200（ヘッダ行だけのCSV）", async () => {
    rowCount = 0;
    const res = await get();
    expect(res.status).toBe(200);
    expect(res.headers.get("Content-Type")).toContain("text/csv");
    const body = await res.text();
    // ヘッダ行だけ（BOM + 1行）。★0件を422にしないこと★
    expect(body.trimEnd().split("\n")).toHaveLength(1);
  });

  it("8-2: 上限より1件少なければ200", async () => {
    rowCount = MAX_CSV_ROWS - 1;
    const res = await get();
    expect(res.status).toBe(200);
  });

  it("8-3: ★ちょうど上限は200★（「上限」は含む）", async () => {
    rowCount = MAX_CSV_ROWS;
    const res = await get();
    expect(res.status).toBe(200);
    expect(res.headers.get("Content-Type")).toContain("text/csv");
  });

  it("8-4: ★上限+1件は422★（500ではない）", async () => {
    rowCount = MAX_CSV_ROWS + 1;
    const res = await get();
    expect(res.status).toBe(422);

    const body = (await res.json()) as ApiError;
    expect(body.error.code).toBe("validation_failed");
    // ★details に実数を入れる★ — 「あと何件減らせばよいか」が文言を読まずに分かる
    expect(body.error.details).toEqual({
      total: MAX_CSV_ROWS + 1,
      max: MAX_CSV_ROWS,
    });
    // 件数は文言にも出す（画面に出さずAPIを直接叩いた人のため）
    expect(body.error.message).toContain(String(MAX_CSV_ROWS + 1));
    expect(body.error.message).toContain(String(MAX_CSV_ROWS));
  });

  it("8-5: 期間が2年でも行数が少なければ200（★上限は日数ではなく行数★）", async () => {
    rowCount = 1_300;
    const res = await get(
      "date_from=2026-01-01&date_to=2028-01-01&facility_name=メガバリューマート中央本店"
    );
    expect(res.status).toBe(200);
  });

  it("日付の範囲が上限（2年）を超えていれば、行数を数える前に422", async () => {
    rowCount = 0;
    const res = await get("date_from=2026-01-01&date_to=2028-01-02");
    expect(res.status).toBe(422);
    const body = (await res.json()) as ApiError;
    expect(body.error.code).toBe("validation_failed");
  });
});
