/**
 * Structured redacting logger — PLAN_RELAY_AGENT T2 (INV9: secrets never logged, no console.log).
 *
 * Meta keys whose name matches a secret substring are replaced with `[REDACTED]` before the
 * line is emitted, so a caller that accidentally passes `{ privateKey, cert, pairingCode,
 * agentToken }` can never leak the value. Emission goes through an injectable sink (default
 * stderr) — `console.log` is never used.
 */

export type LogLevel = 'debug' | 'info' | 'warn' | 'error'

export interface Logger {
  log(level: LogLevel, msg: string, meta?: Record<string, unknown>): void
}

/** Sink for a formatted line. Default writes to stderr (NOT console.log). */
export type LogSink = (line: string) => void

const LEVEL_ORDER: Readonly<Record<LogLevel, number>> = {
  debug: 10,
  info: 20,
  warn: 30,
  error: 40,
}

/**
 * Substrings that mark a meta key as secret (case-insensitive). Matches `privateKey` (key),
 * `cert`, `caChain` (chain? no — cert covers it via 'cert'), `pairingCode` (code), `agentToken`
 * (token), etc. `nonce`/`streamId`/`hostId` intentionally do NOT match (they are not secrets).
 */
const REDACT_SUBSTRINGS: readonly string[] = [
  'key',
  'cert',
  'code',
  'token',
  'secret',
  'proof',
  'password',
  'pem',
]

const REDACTED = '[REDACTED]'

function isSecretKey(name: string): boolean {
  const lower = name.toLowerCase()
  return REDACT_SUBSTRINGS.some((s) => lower.includes(s))
}

function redactMeta(meta: Record<string, unknown>): Record<string, unknown> {
  const out: Record<string, unknown> = {}
  for (const [k, v] of Object.entries(meta)) {
    out[k] = isSecretKey(k) ? REDACTED : v
  }
  return out
}

const defaultSink: LogSink = (line) => {
  process.stderr.write(`${line}\n`)
}

/**
 * Create a redacting logger at a minimum level. Lines below `level` are dropped. Secret meta
 * values are redacted by key name before serialization (INV9).
 */
export function createLogger(level: LogLevel, sink: LogSink = defaultSink): Logger {
  const threshold = LEVEL_ORDER[level]
  return {
    log(msgLevel, msg, meta) {
      if (LEVEL_ORDER[msgLevel] < threshold) return
      const safeMeta = meta ? redactMeta(meta) : undefined
      const metaPart = safeMeta ? ` ${JSON.stringify(safeMeta)}` : ''
      sink(`${msgLevel.toUpperCase()} ${msg}${metaPart}`)
    },
  }
}
