const SECRET_KEY_REGEX = /(api[_-]?key|auth|authorization|token|secret|password|cookie)/i;

export interface RedactionResult<T = unknown> {
  value: T;
  redactions: number;
}

function redactString(input: string): RedactionResult<string> {
  let redactions = 0;
  let output = input;

  // Redact common Authorization header patterns.
  const beforeAuth = output;
  output = output.replace(/\bBearer\s+[A-Za-z0-9._\-~=+/]+\b/g, '[REDACTED_BEARER]');
  if (output !== beforeAuth) {
    redactions += 1;
  }

  // Redact very long high-entropy tokens (heuristic).
  const beforeLong = output;
  output = output.replace(/\b[A-Za-z0-9_\-]{32,}\b/g, '[REDACTED_TOKEN]');
  if (output !== beforeLong) {
    redactions += 1;
  }

  return { value: output, redactions };
}

function shouldRedactKey(key: string): boolean {
  return SECRET_KEY_REGEX.test(key);
}

export function redactForAudit(value: unknown, opts?: { maxDepth?: number }): RedactionResult {
  const maxDepth = opts?.maxDepth ?? 6;

  const seen = new WeakSet<object>();

  const walk = (v: unknown, depth: number): RedactionResult => {
    if (depth > maxDepth) {
      return { value: '[TRUNCATED_DEPTH]', redactions: 0 };
    }

    if (typeof v === 'string') {
      return redactString(v);
    }

    if (v === null || v === undefined) {
      return { value: v, redactions: 0 };
    }

    if (typeof v === 'number' || typeof v === 'boolean') {
      return { value: v, redactions: 0 };
    }

    if (Array.isArray(v)) {
      let redactions = 0;
      const arr: unknown[] = [];
      for (const item of v) {
        const r = walk(item, depth + 1);
        arr.push(r.value);
        redactions += r.redactions;
      }
      return { value: arr, redactions };
    }

    if (typeof v === 'object') {
      const obj = v as Record<string, unknown>;
      if (seen.has(obj)) {
        return { value: '[CYCLE]', redactions: 0 };
      }
      seen.add(obj);

      let redactions = 0;
      const out: Record<string, unknown> = {};
      for (const [k, val] of Object.entries(obj)) {
        if (shouldRedactKey(k)) {
          out[k] = '[REDACTED]';
          redactions += 1;
          continue;
        }
        const r = walk(val, depth + 1);
        out[k] = r.value;
        redactions += r.redactions;
      }
      return { value: out, redactions };
    }

    // bigint/symbol/function - stringify to be safe.
    return { value: String(v), redactions: 0 };
  };

  return walk(value, 0);
}

export function containsSecretLikeKeys(value: unknown, opts?: { maxDepth?: number }): boolean {
  const maxDepth = opts?.maxDepth ?? 6;
  const seen = new WeakSet<object>();

  const walk = (v: unknown, depth: number): boolean => {
    if (depth > maxDepth) {
      return false;
    }
    if (v === null || v === undefined) {
      return false;
    }
    if (typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean') {
      return false;
    }
    if (Array.isArray(v)) {
      return v.some((item) => walk(item, depth + 1));
    }
    if (typeof v === 'object') {
      const obj = v as Record<string, unknown>;
      if (seen.has(obj)) {
        return false;
      }
      seen.add(obj);

      for (const [k, val] of Object.entries(obj)) {
        if (shouldRedactKey(k)) {
          return true;
        }
        if (walk(val, depth + 1)) {
          return true;
        }
      }
      return false;
    }
    return false;
  };

  return walk(value, 0);
}
