/**
 * T1 · Data-plane config — env → frozen DataPlaneConfig (INV9 fail-fast, no secret logged).
 *
 * Startup MUST fail if TLS server material, the AGENT_CA_CERT_PATH mTLS trust-anchor, or
 * BASE_DOMAIN is absent (a missing CA would silently degrade "mTLS" to accept-any-cert,
 * Finding-4). Zod validates at the boundary; the returned object is frozen (immutable).
 */
import { z } from 'zod'

const MAX_FRAME_BYTES_DEFAULT = 1024 * 1024 // 1 MiB (§4.1 payloadLen ceiling)
const INITIAL_WINDOW_BYTES_DEFAULT = 256 * 1024 // 256 KiB (§4.1 credit window)
const HEARTBEAT_INTERVAL_MS_DEFAULT = 15_000 // §4.1: PING every 15s
const ROUTE_TTL_MS_DEFAULT = 45_000 // Redis route:{host_id} TTL
const BIND_HOST_DEFAULT = '0.0.0.0'
const BIND_PORT_DEFAULT = 8443
const AGENT_BIND_PORT_DEFAULT = 8444

export interface DataPlaneConfig {
  readonly baseDomain: string
  readonly bindHost: string
  readonly bindPort: number
  readonly agentBindPort: number
  readonly tlsCertPath: string
  readonly tlsKeyPath: string
  readonly agentCaCertPath: string
  readonly agentCaChainPath: string
  readonly relayNodeId: string
  readonly maxFrameBytes: number
  readonly initialWindowBytes: number
  readonly heartbeatIntervalMs: number
  readonly routeTtlMs: number
}

const requiredPath = z.string().min(1)
const port = z.coerce.number().int().gt(0).max(65535)
const positiveInt = z.coerce.number().int().positive()

const ConfigSchema = z.object({
  baseDomain: z.string().min(1),
  bindHost: z.string().min(1).default(BIND_HOST_DEFAULT),
  bindPort: port.default(BIND_PORT_DEFAULT),
  agentBindPort: port.default(AGENT_BIND_PORT_DEFAULT),
  tlsCertPath: requiredPath,
  tlsKeyPath: requiredPath,
  agentCaCertPath: requiredPath,
  agentCaChainPath: requiredPath,
  relayNodeId: z.string().min(1),
  maxFrameBytes: positiveInt.default(MAX_FRAME_BYTES_DEFAULT),
  initialWindowBytes: positiveInt.default(INITIAL_WINDOW_BYTES_DEFAULT),
  heartbeatIntervalMs: positiveInt.default(HEARTBEAT_INTERVAL_MS_DEFAULT),
  routeTtlMs: positiveInt.default(ROUTE_TTL_MS_DEFAULT),
})

/**
 * Load + validate the data-plane config from `env`. Throws a clear, secret-free error
 * (fail-fast, INV9) when a required key is missing or a numeric key is non-numeric.
 * `agentCaChainPath` falls back to `agentCaCertPath` when unset (single-tier CA).
 */
export function loadDataPlaneConfig(env: NodeJS.ProcessEnv): DataPlaneConfig {
  const parsed = ConfigSchema.safeParse({
    baseDomain: env.BASE_DOMAIN,
    bindHost: env.BIND_HOST,
    bindPort: env.BIND_PORT,
    agentBindPort: env.AGENT_BIND_PORT,
    tlsCertPath: env.TLS_CERT_PATH,
    tlsKeyPath: env.TLS_KEY_PATH,
    agentCaCertPath: env.AGENT_CA_CERT_PATH,
    agentCaChainPath: env.AGENT_CA_CHAIN_PATH ?? env.AGENT_CA_CERT_PATH,
    relayNodeId: env.RELAY_NODE_ID,
    maxFrameBytes: env.MAX_FRAME_BYTES,
    initialWindowBytes: env.INITIAL_WINDOW_BYTES,
    heartbeatIntervalMs: env.HEARTBEAT_INTERVAL_MS,
    routeTtlMs: env.ROUTE_TTL_MS,
  })
  if (!parsed.success) {
    // Report only WHICH keys are wrong — never echo values (INV9: no secret in logs).
    const fields = parsed.error.issues.map((i) => i.path.join('.')).join(', ')
    throw new Error(`invalid data-plane config: ${fields}`)
  }
  return Object.freeze(parsed.data)
}
