import { promises as fs } from "node:fs";
import os from "node:os";
import path from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";

// ★★ルート本体は**静的 import** すること★★（`app/api/clean-logs/__tests__/route.test.ts` の冒頭コメントと同じ理由）
import { GET, PATCH } from "@/app/api/settings/display-limits/route";
import { logApiError } from "@/lib/logger/api";
import {
  __setDisplayLimitStoreForTesting,
  createDisplayLimitStore,
  type DisplayLimitOverrides,
} from "@/lib/settings/display-limits";
import type { JsonStore } from "@/lib/settings/json-store";

/**
 * `GET / PATCH /settings/display-limits` の頑丈さ（指示書BB 表8 T2・J1〜J3。A5・D-182）。
 *
 * | # | 入力 | 期待 |
 * |---|---|---|
 * | T2 | GET で `loadDisplayLimitSettings()` が例外 | 500・internal_error・「データの取得に失敗しました」・`api.error` が1件 |
 * | J1 | PATCH の本文が `null` | 422・validation_failed・「JSONの形式が不正です」 |
 * | J2 | 本文が `[]` / `1` / `"x"` | 422（J1 と同じ） |
 * | J3 | 本文が `{}` | 今どおり（422「keyの値が不正です」） |
 */

vi.mock("@/lib/logger/api", () => ({ logApiError: vi.fn() }));

let dir = "";

beforeEach(async () => {
  dir = await fs.mkdtemp(path.join(os.tmpdir(), "dwatch-display-limits-route-"));
  const filePath = path.join(dir, "display-limits.json");
  __setDisplayLimitStoreForTesting(createDisplayLimitStore(filePath, () => {}), filePath);
  vi.mocked(logApiError).mockClear();
});

afterEach(async () => {
  __setDisplayLimitStoreForTesting(null);
  await fs.rm(dir, { recursive: true, force: true });
});

function patchRaw(raw: string): Promise<Response> {
  return PATCH(new Request("http://localhost/api/settings/display-limits", { method: "PATCH", body: raw }));
}

describe("GET /settings/display-limits — try の外の例外（指示書BB 表8 T2）", () => {
  it("★T2: 読み込みが例外 → 500・標準の形・api.error が1件★", async () => {
    const broken: JsonStore<DisplayLimitOverrides> = {
      read: async () => {
        throw new Error("[stub] 読み込みに失敗");
      },
      write: async () => {},
    };
    __setDisplayLimitStoreForTesting(broken, path.join(dir, "display-limits.json"));
    const res = await GET(new Request("http://localhost/api/settings/display-limits"));
    expect(res.status).toBe(500);
    expect(await res.json()).toEqual({
      error: { code: "internal_error", message: "データの取得に失敗しました", details: null },
    });
    expect(vi.mocked(logApiError)).toHaveBeenCalledTimes(1);
    expect(vi.mocked(logApiError).mock.calls[0][0]).toMatchObject({
      route: "/api/settings/display-limits",
      method: "GET",
      status: 500,
    });
  });

  it("読み込みが成功すれば今どおり 200", async () => {
    const res = await GET(new Request("http://localhost/api/settings/display-limits"));
    expect(res.status).toBe(200);
  });
});

describe("PATCH /settings/display-limits — 本文がオブジェクトでない（指示書BB 表8 J1〜J3。A5・D19）", () => {
  it.each(["null", "[]", "1", '"x"'])("★J1・J2: 本文 %s → 422「JSONの形式が不正です」★", async (raw) => {
    const res = await patchRaw(raw);
    expect(res.status).toBe(422);
    expect(await res.json()).toEqual({
      error: { code: "validation_failed", message: "JSONの形式が不正です", details: null },
    });
  });

  it("J3: 本文 {} → 今どおり（項目の検証で422「keyの値が不正です」）", async () => {
    const res = await patchRaw("{}");
    expect(res.status).toBe(422);
    expect(((await res.json()) as { error: { message: string } }).error.message).toBe("keyの値が不正です");
  });
});
