/**
 * 環境変数の検証（指示書I 段階5-B）。★純粋関数。`process.env` を直接読まない★
 *
 * `scripts/check-env.ts` が dotenv で読み込んだ値を渡す。
 * `lib/db/local-guard.ts` と同じ形（純粋関数＋薄い包み）にしてあり、
 * ★DB無し・環境変数無しでテストできる★。
 *
 * ★★要点: エラーメッセージに値そのものを出さないこと★★
 *   `DATABASE_URL` にはパスワードが入る。出力はターミナルに残り、CI のログにも残る。
 *   `lib/db/local-guard.ts` が同じ配慮をしている（末尾が「値はこのエラーには出しません」）。
 *   ★「何が問題か」だけを言い、値は書かない★。`AUTH_TOKEN` も同様
 *   （検査1 は「`changeme` のままです」とキーワード名だけ言えば足りる）。
 *
 * ★「本番判定」に `NODE_ENV === "production"` を使っていることについて★
 *   本指示書には「開発時だけ通す条件は `NODE_ENV` ではなく設定値で明示的に切れ」とあるが、
 *   ★矛盾しない★。あの指示は `ALLOW_DIRECT_ACCESS`（＝ リクエストを通す／弾くという
 *   **アプリの実際の挙動**）の話である。
 *   ★この検証スクリプトの分岐は「警告の文言をどちらに寄せるか」でしかなく、
 *     アプリの挙動を一切変えない★。
 *   後任が「`NODE_ENV` を使っている、規約違反だ」と誤読しないようここに明記する。
 *   判定そのものも引数（`isProduction: boolean`）で受け取り、テストで両方を回せる形にしてある。
 */

export type EnvCheckSeverity = "error" | "warn";

export interface EnvCheckFinding {
  severity: EnvCheckSeverity;
  /** 対象の環境変数名 */
  key: string;
  /** ★値を含めないこと★。何が問題かだけを書く */
  message: string;
}

/** pino が受け付けるレベル名。`lib/logger` の LogLevel（3つ）より広い点に注意 */
const PINO_LEVELS = ["trace", "debug", "info", "warn", "error", "fatal"];

/**
 * 危険な値を検出する。エラーが1件でもあれば呼び出し側は非0で終了する。
 *
 * @param env `process.env` 相当（dotenv 読み込み後）
 * @param isProduction 本番判定。★警告の寄せ方だけを変え、挙動は変えない★
 */
export function checkEnv(
  env: Record<string, string | undefined>,
  isProduction: boolean
): EnvCheckFinding[] {
  const findings: EnvCheckFinding[] = [];

  // 検査1: AUTH_TOKEN が未設定・空・changeme のまま
  // （.env.example の初期値がまさに changeme。本番に持ち込むと共有トークンが公開情報になる）
  const authToken = env.AUTH_TOKEN;
  if (!authToken) {
    findings.push({
      severity: "error",
      key: "AUTH_TOKEN",
      message:
        "未設定または空です。未設定だと誰もログインできません（＝全機能が使えません）。",
    });
  } else if (authToken === "changeme") {
    findings.push({
      severity: "error",
      key: "AUTH_TOKEN",
      message:
        ".env.example の初期値 changeme のままです。必ず変更してください。",
    });
  }

  // 検査2: ALLOW_DIRECT_ACCESS=true が設定されている
  // （開発機では正常な設定なので落としてはいけない）
  if (env.ALLOW_DIRECT_ACCESS === "true") {
    findings.push({
      severity: isProduction ? "error" : "warn",
      key: "ALLOW_DIRECT_ACCESS",
      message: isProduction
        ? "本番で true になっています。nginx を通らない直接アクセスが素通りします。この行を削除してください。"
        : "true です。開発機では正しい設定ですが、★本番へ持ち込まないこと★。",
    });
  }

  // 検査3: USE_FIXTURES が未設定または false 以外
  // （lib/db/use-fixtures.ts は `!== "false"` なので、未設定だと true になる）
  if (isProduction && env.USE_FIXTURES !== "false") {
    findings.push({
      severity: "warn",
      key: "USE_FIXTURES",
      message:
        "false ではありません。未設定・その他の値はすべて true 扱いになり、実DBに繋がらずサンプルデータのまま動きます。",
    });
  }

  // 検査4: DATABASE_URL が未設定
  const databaseUrl = env.DATABASE_URL;
  if (!databaseUrl) {
    findings.push({
      severity: "error",
      key: "DATABASE_URL",
      message:
        "未設定です。起動しても画面が全部エラーになります（値はこの出力には出しません）。",
    });
  } else if (isProduction && isLocalHostUrl(databaseUrl)) {
    // 検査5: ホストが localhost / 127.0.0.1
    findings.push({
      severity: "warn",
      key: "DATABASE_URL",
      message:
        "ホストが localhost / 127.0.0.1 を指しています。本番のDBを指しているか確認してください（値はこの出力には出しません）。",
    });
  }

  // 検査6: LOG_LEVEL が想定外の値
  // ★debug を弾かないこと★ — lib/logger の LogLevel は info/warn/error の3つだが、
  //   resolveLevel() は開発時に pino のしきい値として debug を返す
  const logLevel = env.LOG_LEVEL;
  if (logLevel && !PINO_LEVELS.includes(logLevel)) {
    findings.push({
      severity: "warn",
      key: "LOG_LEVEL",
      message: `pino のレベル名（${PINO_LEVELS.join(" / ")}）ではありません。`,
    });
  }

  // 検査7: 書き込み用アカウント（DATABASE_WRITE_USER / DATABASE_WRITE_PASSWORD。指示書AT §3-2）
  //   ★値も、ユーザー名も出さない★
  const writeCredentials = databaseWriteCredentials(env);
  if (writeCredentials === "none") {
    findings.push({
      severity: "warn",
      key: "DATABASE_WRITE_USER",
      message:
        "DATABASE_WRITE_USER / DATABASE_WRITE_PASSWORD が未設定です。書き込みは参照用アカウントで行います。",
    });
  } else if (writeCredentials === "partial") {
    findings.push({
      severity: "error",
      key: "DATABASE_WRITE_USER",
      message:
        "DATABASE_WRITE_USER と DATABASE_WRITE_PASSWORD の片方だけが設定されています。両方設定するか、両方消してください（片方だけだと書き込みが失敗します）。",
    });
  } else if (!env.DATABASE_HOST || !env.DATABASE_NAME) {
    // 書き込み用の接続は部品変数で組み立てる（URL 形式は設けない。`%40` が壊れる問題を持ち込まないため）
    findings.push({
      severity: "error",
      key: "DATABASE_WRITE_USER",
      message:
        "書き込み用アカウントを使うときは DATABASE_HOST と DATABASE_NAME も必要です（接続先は参照用と同じDB）。",
    });
  }

  return findings;
}

/**
 * 書き込み用アカウントの環境変数（`DATABASE_WRITE_USER` / `DATABASE_WRITE_PASSWORD`）の状態（指示書AT §3-2）。
 *
 * - `none`    … 2つとも未設定（空文字も未設定とみなす）→ ★参照用の接続で書く★（いまはこれで動く）
 * - `partial` … 片方だけ → ★書き込みは例外★（黙って参照用に倒さない）
 * - `both`    … 2つとも設定 → 書き込み用の接続を別に作る（接続先は `DATABASE_HOST` / `PORT` / `NAME`）
 *
 * ★`lib/db/prisma.ts` の `getWritePrisma()` と `checkEnv()` の両方がこの1本で判定する★（基準を揃える）。
 * ★値は返さない★。
 */
export function databaseWriteCredentials(
  env: Record<string, string | undefined>
): "none" | "partial" | "both" {
  const hasUser = Boolean(env.DATABASE_WRITE_USER);
  const hasPassword = Boolean(env.DATABASE_WRITE_PASSWORD);
  if (hasUser && hasPassword) return "both";
  if (hasUser || hasPassword) return "partial";
  return "none";
}

/** `DATABASE_URL` のホストがローカルか。★url の値は返さない★ */
function isLocalHostUrl(url: string): boolean {
  try {
    const parsed = new URL(url);
    return parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1";
  } catch {
    // パースできない ＝ ローカル判定はできない。検査4 とは別問題なのでここでは黙る
    return false;
  }
}

/** エラーが1件でもあるか（呼び出し側の終了コード判定用） */
export function hasError(findings: readonly EnvCheckFinding[]): boolean {
  return findings.some((f) => f.severity === "error");
}

/** 出力用に整形する。★ここでも値を出さない★ */
export function formatFinding(finding: EnvCheckFinding): string {
  const prefix = finding.severity === "error" ? "ERROR" : "WARN ";
  return `${prefix}: ${finding.key} — ${finding.message}`;
}
