// Output sanitisation for the integration tier.
//
// Failing tests print error messages, stack traces, and (sometimes) JSON
// bodies. Any of those can carry a `Bearer <token>` header, a rig hostname,
// or a numeric user id straight into CI logs / PR comment artefacts. The
// release-source-zip script greps for this material as a third layer of
// defence, but at the test layer it's cheaper to mask before the value ever
// leaves the runner.
//
// Wiring: `package.json` `jest.projects[integration].setupFilesAfterEach`
// points every integration test file at this module, so the console patches
// are in place before any `it()` body runs. Wrapping `console.log/warn/error`
// is the cheapest way to catch the high-volume sources; the more exotic
// surfaces (jest reporter, snapshot serialiser) are out of scope for v1 —
// the secondary `Bearer` grep in the release script catches them.
//
// Patterns:
//   - `Bearer [A-Za-z0-9_\-]+`           → `Bearer <redacted-token>`
//   - any rig hostname (loaded at startup) → `<rig-host>`
//   - any rig access token (loaded at startup) → `<redacted-token>`
//   - any rig OAuth client secret → `<redacted-client-secret>`
//   - any rig numeric `userId` → `<rig-user-id>`
//
// The rig is loaded once at module load (cached by `loadTestRig`). When no
// rig is configured, the Bearer regex still fires — anything matching that
// shape in test output is masked.

import { loadTestRig, rigHostnames } from '../../_helpers/loadTestRig'

const BEARER_RE = /Bearer\s+[A-Za-z0-9_\-.~+/=]+/g
const TOKEN_QUERY_RE = /([?&]access_token=)[A-Za-z0-9_\-.~+/=]+/g

type Replacement = { from: string | RegExp; to: string }

function buildReplacements(): Replacement[] {
  const out: Replacement[] = [
    { from: BEARER_RE, to: 'Bearer <redacted-token>' },
    { from: TOKEN_QUERY_RE, to: '$1<redacted-token>' },
  ]
  const rig = loadTestRig()
  if (rig) {
    for (const host of rigHostnames(rig)) {
      // Escape regex metacharacters in host before global replace.
      out.push({ from: globalLiteral(host), to: '<rig-host>' })
    }
    for (const entry of Object.values(rig.rigs)) {
      if (entry.accessToken)
        out.push({ from: globalLiteral(entry.accessToken), to: '<redacted-token>' })
      if (entry.refreshToken) {
        out.push({ from: globalLiteral(entry.refreshToken), to: '<redacted-refresh>' })
      }
      out.push({
        from: globalLiteral(entry.oauthClientSecret),
        to: '<redacted-client-secret>',
      })
      // userId is less sensitive but the rig owner may prefer to keep their
      // bot accounts unidentifiable; redact uniformly.
      out.push({ from: globalLiteral(entry.userId), to: '<rig-user-id>' })
    }
  }
  return out
}

function globalLiteral(s: string): RegExp {
  return new RegExp(s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g')
}

const replacements = buildReplacements()

function sanitize(value: unknown): unknown {
  if (typeof value !== 'string') return value
  let out = value
  for (const r of replacements) {
    out = out.replace(r.from as RegExp, r.to)
  }
  return out
}

function patchConsole(method: 'log' | 'warn' | 'error' | 'info' | 'debug'): void {
  const original = console[method].bind(console)
  console[method] = (...args: unknown[]) => {
    original(...args.map(sanitize))
  }
}

patchConsole('log')
patchConsole('warn')
patchConsole('error')
patchConsole('info')
patchConsole('debug')
