/**
 * Native-tunnel frpc.toml writer — TASK B2h (PLAN_TUNNEL_AUTOMATION §3.2 / PLAN_NATIVE_TUNNEL §4).
 *
 * Emits the frp v0.61 TOML that a host's `frpc` presents to the VPS:
 *   - dials `serverAddr:443`, SNI-routed by nginx `ssl_preread` to frps :7000;
 *   - control-channel mTLS (frp-client cert/key + trusted CA) + shared token;
 *   - one `[[proxies]]` of `type = "http"` exposing the loopback base app as `<sub>.terminal...`.
 *
 * SUPERSEDES the retired v0.8 `[common]/tls_enable` shape in `frpScaffold.ts` (do not reuse that
 * grammar — this is the native-tunnel writer).
 *
 * ANTI-SSRF (hard invariant): `localIP` MUST be loopback. frpc forwards ONLY to the local base app,
 * never an arbitrary target — a non-loopback `localIP` would turn the tunnel into an open proxy.
 * All inputs are validated at this boundary (fail-fast, clear messages); no `console.log`.
 */

const DEFAULT_SERVER_ADDR = '8.138.1.192'
const SERVER_PORT = 443
const TLS_SERVER_NAME = 'frp.terminal.yaojia.wang'
const DEFAULT_LOCAL_IP = '127.0.0.1'
const MIN_PORT = 1
const MAX_PORT = 65535
const MAX_LABEL_LEN = 63
const DEL_CHAR_CODE = 0x7f
const FIRST_PRINTABLE_CODE = 0x20

/** Loopback forms accepted for `localIP` (anti-SSRF allowlist). */
const LOOPBACK_IPS: readonly string[] = ['127.0.0.1', '::1', 'localhost']

/** RFC 1035 DNS label: 1-63 chars, alnum, internal hyphens only (no leading/trailing hyphen). */
const LABEL_RE = /^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$/

export interface NativeFrpcOptions {
  /** Subdomain label -> reachable at `https://<subdomain>.terminal.yaojia.wang`. Label-safe. */
  readonly subdomain: string
  /** Loopback port of the local base app frpc forwards to (1-65535). */
  readonly localPort: number
  /** Shared frps auth token (kept out of logs; the file itself is chmod 600 by the caller). */
  readonly authToken: string
  /** Keystore path to this host's frp-client leaf cert (control-channel mTLS). */
  readonly certFile: string
  /** Keystore path to the matching private key. */
  readonly keyFile: string
  /** Keystore path to the CA that signed the frps control cert (server verification). */
  readonly trustedCaFile: string
  /** VPS address; defaults to the deployed relay `8.138.1.192`. */
  readonly serverAddr?: string
  /** Local forward target IP; MUST be loopback. Defaults to `127.0.0.1`. */
  readonly localIP?: string
}

/** A validation failure at the frpc.toml boundary. */
export class FrpcTomlError extends Error {
  constructor(message: string) {
    super(message)
    this.name = 'FrpcTomlError'
  }
}

/** True iff `value` contains an ASCII control character (C0 range or DEL). */
function hasControlChar(value: string): boolean {
  for (let i = 0; i < value.length; i += 1) {
    const code = value.charCodeAt(i)
    if (code < FIRST_PRINTABLE_CODE || code === DEL_CHAR_CODE) return true
  }
  return false
}

/** Non-empty, control-char-free path/secret at the boundary. */
function assertPresent(field: string, value: string): void {
  if (typeof value !== 'string' || value.length === 0) {
    throw new FrpcTomlError(`${field} is required`)
  }
  if (hasControlChar(value)) {
    throw new FrpcTomlError(`${field} contains control characters`)
  }
}

/** Escape a value for a TOML basic (double-quoted) string: backslash + quote (Windows paths). */
function tomlBasicString(value: string): string {
  return value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')
}

function assertLoopback(localIP: string): void {
  if (!LOOPBACK_IPS.includes(localIP)) {
    throw new FrpcTomlError(
      `localIP "${localIP}" is not loopback — frpc must forward only to the local base app (anti-SSRF)`,
    )
  }
}

function assertSubdomain(subdomain: string): void {
  if (typeof subdomain !== 'string' || subdomain.length === 0) {
    throw new FrpcTomlError('subdomain is required')
  }
  if (subdomain.length > MAX_LABEL_LEN || !LABEL_RE.test(subdomain)) {
    throw new FrpcTomlError(
      `subdomain "${subdomain}" is not a valid DNS label (alnum + internal hyphens, 1-63 chars)`,
    )
  }
}

function assertLocalPort(localPort: number): void {
  if (!Number.isInteger(localPort) || localPort < MIN_PORT || localPort > MAX_PORT) {
    throw new FrpcTomlError(
      `localPort must be an integer in ${MIN_PORT}-${MAX_PORT}, got ${localPort}`,
    )
  }
}

/**
 * Build a validated frp v0.61 `frpc.toml` for the native mTLS tunnel. Throws `FrpcTomlError` on any
 * invalid input (fail-fast). The returned string is the full config file contents.
 */
export function buildNativeFrpcToml(opts: NativeFrpcOptions): string {
  assertSubdomain(opts.subdomain)
  assertLocalPort(opts.localPort)
  assertPresent('authToken', opts.authToken)
  assertPresent('certFile', opts.certFile)
  assertPresent('keyFile', opts.keyFile)
  assertPresent('trustedCaFile', opts.trustedCaFile)

  const serverAddr = opts.serverAddr ?? DEFAULT_SERVER_ADDR
  assertPresent('serverAddr', serverAddr)

  const localIP = opts.localIP ?? DEFAULT_LOCAL_IP
  assertLoopback(localIP)

  const lines: readonly string[] = [
    `serverAddr = "${tomlBasicString(serverAddr)}"`,
    `serverPort = ${SERVER_PORT}`,
    '',
    'auth.method = "token"',
    `auth.token = "${tomlBasicString(opts.authToken)}"`,
    '',
    '# control-channel mTLS: present this host frp-client cert; verify the frps control cert',
    'transport.tls.enable = true',
    `transport.tls.serverName = "${TLS_SERVER_NAME}"`,
    'transport.tls.disableCustomTLSFirstByte = true',
    `transport.tls.certFile = "${tomlBasicString(opts.certFile)}"`,
    `transport.tls.keyFile = "${tomlBasicString(opts.keyFile)}"`,
    `transport.tls.trustedCaFile = "${tomlBasicString(opts.trustedCaFile)}"`,
    '',
    'loginFailExit = false',
    '',
    '[[proxies]]',
    `name = "${opts.subdomain}"`,
    'type = "http"',
    `localIP = "${localIP}"`,
    `localPort = ${opts.localPort}`,
    `subdomain = "${opts.subdomain}"`,
    '',
  ]
  return lines.join('\n')
}
