/**
 * GET  /settings/advice          — アドバイス文言の一覧（既定値＋編集内容）
 * PATCH /settings/advice         — 1ルール分の文言を保存
 *
 * システム管理 →「運用設定」から使います。
 * 編集できるのは title / text / actions のみ。
 * level / icon / condition（発火条件）は仕様なので画面からは変更できません。
 */
import { NextResponse } from "next/server";
import { isJsonObject } from "@/lib/api/json-body";
import {
  DEFAULT_ADVICE_RULES,
  type AdviceRuleId,
  type EditableAdviceFields,
} from "@/lib/config/advice-rules";
import { withRequestId } from "@/lib/logger";
import { logApiError } from "@/lib/logger/api";
import { loadAdviceRules, resetAdviceRule, saveAdviceRule } from "@/lib/settings/advice";
import {
  isStringArray,
  validateAdviceFields,
  type AdviceFieldsToValidate,
} from "@/lib/settings/advice-validation";
import type { AdviceSetting, ApiError, ErrorCode } from "@/lib/types";

const ROUTE = "/api/settings/advice";

function errorBody(code: ErrorCode, message: string): ApiError {
  return { error: { code, message, details: null } };
}

function errorResponse(status: number, code: ErrorCode, message: string) {
  return NextResponse.json(errorBody(code, message), { status });
}

export async function GET(request: Request) {
  return withRequestId(request, async () => {
    // ★読み込みの例外は標準の形の500にする★（指示書BB A5・D18。`try` の外で投げると標準でない500・`api.error` なし）
    let rules: Awaited<ReturnType<typeof loadAdviceRules>>;
    try {
      rules = await loadAdviceRules();
    } catch (e) {
      const errorPayload = errorBody("internal_error", "データの取得に失敗しました");
      logApiError({ route: ROUTE, method: "GET", body: errorPayload, status: 500, err: e });
      return NextResponse.json(errorPayload, { status: 500 });
    }
    const items: AdviceSetting[] = Object.values(rules).map((r) => ({
      rule: r.id,
      phase: r.phase,
      level: r.level,
      icon: r.icon,
      title: r.title,
      text: r.text,
      actions: r.actions,
      condition: r.condition,
      /** 既定値から変更されているか（画面に「編集済み」バッジを出す用） */
      customized:
        r.title !== DEFAULT_ADVICE_RULES[r.id].title ||
        r.text !== DEFAULT_ADVICE_RULES[r.id].text ||
        JSON.stringify(r.actions) !==
          JSON.stringify(DEFAULT_ADVICE_RULES[r.id].actions),
    }));
    return NextResponse.json({ items });
  });
}

export async function PATCH(request: Request) {
  return withRequestId(request, async () => {
    // ★`unknown` で受けて `isJsonObject()` で狭める★ — JSONで何が来るかは実行時まで分からない。
    //   型で嘘をつくと `filter` や `as` で握りつぶす実装に流れる
    let body: unknown;

    try {
      body = await request.json();
    } catch {
      return errorResponse(422, "validation_failed", "JSONの形式が不正です");
    }
    // ★本文がオブジェクトでない（`null`・配列・数値・文字列）なら422★（指示書BB A5・D19。今の「JSON が読めない」と同じ文言）
    if (!isJsonObject(body)) {
      return errorResponse(422, "validation_failed", "JSONの形式が不正です");
    }

    // ★自前のプロパティだけを受ける★（指示書BB A10・D-182）— `DEFAULT_ADVICE_RULES[ruleId]` だと
    //   `"constructor"` / `"toString"` / `"__proto__"` が `Object.prototype` から引けて検証を通ってしまう
    if (typeof body.rule !== "string" || !Object.hasOwn(DEFAULT_ADVICE_RULES, body.rule)) {
      return errorResponse(422, "validation_failed", "ruleの値が不正です");
    }
    // ★`as` は検証の後だけ★
    const ruleId = body.rule as AdviceRuleId;

    try {
      if (body.reset) {
        const reverted = await resetAdviceRule(ruleId);
        return NextResponse.json({ rule: reverted.id, reset: true });
      }

      const fields: AdviceFieldsToValidate = {};
      if (typeof body.title === "string") fields.title = body.title;
      if (typeof body.text === "string") fields.text = body.text;
      // ★要素の型は見ない★ — 文字列以外が混ざっていたら
      //   `validateAdviceFields()` が 422 にする（落として保存すると
      //   `actions: [1,2,3]` が「全消去」になり、黙って既存の編集が消える）
      if (Array.isArray(body.actions)) fields.actions = body.actions;

      if (Object.keys(fields).length === 0) {
        return errorResponse(
          422,
          "validation_failed",
          "title / text / actions のいずれかを指定してください"
        );
      }

      // ★長さ・件数の上限（指示書AE 段階2-3 / D-124）★
      //   保存先が ds-device-notes とまったく同じ「全件読み込み→書き戻し」の
      //   JSON ストアなので、DEVICE_NOTE_MAX_LENGTH と同じ理由で上限が要る。
      //   上限値は lib/config/index.ts（CLAUDE.md §7。直書きしない）
      const invalid = validateAdviceFields(fields);
      if (invalid) {
        return NextResponse.json(invalid.error, { status: invalid.status });
      }

      // ★ここに来た時点で actions は全要素が文字列★（そうでなければ上で422）。
      //   その事実を型の世界へ渡すための型ガード。`as` で潰すと、検証を外したときに
      //   型エラーで気付けなくなる。
      //   ★キーは「指定されたものだけ」入れる★ — `title: undefined` を混ぜると
      //   保存側の `{ ...既定値, ...fields }` で既定値を undefined で上書きしてしまう。
      const toSave: Partial<EditableAdviceFields> = {};
      if (fields.title !== undefined) toSave.title = fields.title;
      if (fields.text !== undefined) toSave.text = fields.text;
      const actions = fields.actions;
      if (actions !== undefined && isStringArray(actions)) {
        // 複製して保存側に呼び出し元の配列参照を渡さない
        toSave.actions = [...actions];
      }

      const saved = await saveAdviceRule(ruleId, toSave);
      return NextResponse.json({
        rule: saved.id,
        title: saved.title,
        text: saved.text,
        actions: saved.actions,
      });
    } catch (e) {
      const errorPayload = errorBody("internal_error", "設定の保存に失敗しました");
      logApiError({
        route: ROUTE,
        method: "PATCH",
        body: errorPayload,
        status: 500,
        err: e,
      });
      return NextResponse.json(errorPayload, { status: 500 });
    }
  });
}
