// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. config-utils module for scripts/src/platform-infra-sub2api-codex.ts.

// Moved mechanically from scripts/src/platform-infra-sub2api-codex.ts:3686-3799 for #903.

import { chmodSync, copyFileSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
import { homedir } from "node:os";
import { join } from "node:path";
import type { UniDeskConfig } from "../config";
import { rootPath } from "../config";
import type { RenderedCliResult } from "../output";
import { applyPk01CaddyBlock, prepareFrpcSecret, renderFrpcManifest, type PublicServiceExposure, type PublicServiceTarget } from "../platform-infra-public-service";
import { shortSha256Fingerprint } from "../platform-infra-ops-library";
import {
  codexPoolSentinelSummary,
  codexPoolSentinelRuntimeImage,
  readCodexPoolSentinelConfig,
  renderCodexPoolSentinelManifest,
  type CodexPoolSentinelConfig,
  type CodexPoolSentinelProfileSecret,
} from "../platform-infra-sub2api-codex-sentinel";
import { parseEnvFile, readTextFile, redactRepoPath, requiredEnvValue } from "../secrets";
import { runSshCommandCapture, type SshCaptureResult } from "../ssh";

import type { CodexSentinelProtectPolicy } from "./types";
import { codexPoolConfigPath } from "./types";

export function normalizeBaseUrl(value: string | null): string | null {
  if (value === null) return null;
  const trimmed = value.trim().replace(/\/+$/u, "");
  if (trimmed.length === 0) return null;
  try {
    const parsed = new URL(trimmed);
    if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return null;
    return parsed.toString().replace(/\/+$/u, "");
  } catch {
    return null;
  }
}

export function stringValue(value: unknown): string | null {
  return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
}

export function requiredStringConfigField(obj: Record<string, unknown>, key: string, path: string): string {
  const value = stringValue(obj[key]);
  const prefix = path.length > 0 ? `${path}.` : "";
  if (value === null) throw new Error(`${codexPoolConfigPath}.${prefix}${key} must be a non-empty string`);
  return value;
}

export function readRequiredDescription(value: unknown, key: string, maxChars: number): string {
  const text = stringValue(value);
  if (text === null) throw new Error(`${codexPoolConfigPath}.${key} must be a non-empty string`);
  if (text.length > maxChars) throw new Error(`${codexPoolConfigPath}.${key} must be at most ${maxChars} characters`);
  if (/[\r\n]/u.test(text)) throw new Error(`${codexPoolConfigPath}.${key} must not contain newlines`);
  return text;
}

export function readRequiredEmail(value: unknown, key: string): string {
  const text = stringValue(value);
  if (text === null) throw new Error(`${codexPoolConfigPath}.${key} must be a non-empty string`);
  if (!/^[^@\s]+@[^@\s]+[.][^@\s]+$/u.test(text)) throw new Error(`${codexPoolConfigPath}.${key} must be an email address`);
  return text;
}

export function readRequiredBaseUrl(value: unknown, key: string): string {
  const baseUrl = normalizeBaseUrl(stringValue(value));
  if (baseUrl === null) throw new Error(`${codexPoolConfigPath}.${key} must be a valid http(s) URL`);
  return baseUrl;
}

export function readRequiredPort(value: unknown, key: string): number {
  const port = numberValue(value);
  if (port === null || !Number.isInteger(port) || port < 1 || port > 65535) {
    throw new Error(`${codexPoolConfigPath}.${key} must be an integer port`);
  }
  return port;
}

export function readRequiredPositiveNumber(value: unknown, key: string): number {
  const number = numberValue(value);
  if (number === null || number <= 0) throw new Error(`${codexPoolConfigPath}.${key} must be > 0`);
  return number;
}

export function requiredRecordConfigField(obj: Record<string, unknown>, key: string, path: string): Record<string, unknown> {
  const value = obj[key];
  const prefix = path.length > 0 ? `${path}.` : "";
  if (!isRecord(value)) throw new Error(`${codexPoolConfigPath}.${prefix}${key} must be a YAML object`);
  return value;
}

export function integerConfigField(obj: Record<string, unknown>, key: string, path: string): number {
  const value = obj[key];
  const prefix = path.length > 0 ? `${path}.` : "";
  if (typeof value !== "number" || !Number.isInteger(value)) throw new Error(`${codexPoolConfigPath}.${prefix}${key} must be an integer`);
  return value;
}

export function integerArrayConfigField(obj: Record<string, unknown>, key: string, path: string): number[] {
  const value = obj[key];
  const prefix = path.length > 0 ? `${path}.` : "";
  if (!Array.isArray(value) || !value.every((item) => typeof item === "number" && Number.isInteger(item))) {
    throw new Error(`${codexPoolConfigPath}.${prefix}${key} must be an array of integers`);
  }
  return value;
}

export function numberValue(value: unknown): number | null {
  if (typeof value === "number" && Number.isFinite(value)) return value;
  if (typeof value === "string" && value.trim().length > 0) {
    const parsed = Number(value);
    return Number.isFinite(parsed) ? parsed : null;
  }
  return null;
}

export function booleanValue(value: unknown): boolean | null {
  if (typeof value === "boolean") return value;
  if (typeof value === "string") {
    if (value.trim() === "true") return true;
    if (value.trim() === "false") return false;
  }
  return null;
}

export function validateKubernetesName(value: string, key: string, strictDns: boolean): void {
  const pattern = strictDns ? /^[a-z0-9]([-a-z0-9]*[a-z0-9])?$/u : /^[A-Za-z0-9]([A-Za-z0-9._-]*[A-Za-z0-9])?$/u;
  if (!pattern.test(value)) throw new Error(`${codexPoolConfigPath}.${key} has an unsupported format`);
}

export function isRecord(value: unknown): value is Record<string, unknown> {
  return typeof value === "object" && value !== null && !Array.isArray(value);
}

export function fingerprint(value: string): string {
  return shortSha256Fingerprint(value);
}

export function codexPoolSentinelProbeConfigFingerprint(input: {
  accountName: string;
  profile: string;
  baseUrl: string;
  apiKeyFingerprint: string | null;
  upstreamUserAgent: string | null;
  openaiResponsesWebSocketsV2Mode: string | null;
  trustUpstream: boolean;
  sentinelProtect: CodexSentinelProtectPolicy;
}): string {
  return fingerprint(JSON.stringify({
    accountName: input.accountName,
    profile: input.profile,
    baseUrl: normalizeBaseUrl(input.baseUrl) ?? input.baseUrl,
    apiKeyFingerprint: input.apiKeyFingerprint,
    upstreamUserAgent: input.upstreamUserAgent,
    openaiResponsesWebSocketsV2Mode: input.openaiResponsesWebSocketsV2Mode,
    trustUpstream: input.trustUpstream,
    sentinelProtect: input.sentinelProtect,
  }));
}
