import { Writable } from 'node:stream';

/**
 * In-memory ring buffer of recent error/fatal log records, so operators can
 * see "what went wrong with mcpd" via `mcpctl errors` without shelling into
 * the pod. Fed by a pino multistream (see server.ts) that tees level>=error
 * records here; the rest of logging is unaffected.
 *
 * Deliberately tiny + dependency-free: parse the serialized JSON line, keep
 * the fields worth showing, cap the buffer. Lost on restart (errors that
 * outlive a restart are someone else's problem — the cause usually recurs).
 */

/** pino numeric levels: warn=40, error=50, fatal=60. */
const ERROR_LEVEL = 50;

export interface ErrorLogEntry {
  time: number;
  level: number;
  msg?: string;
  /** Structured error kind, e.g. BACKEND_TOKEN_DEAD. */
  kind?: string;
  reqId?: string;
  /** Flattened error message/type when the record carried an `err` object. */
  err?: string;
}

export class ErrorLogBuffer {
  private buf: ErrorLogEntry[] = [];
  constructor(private readonly capacity = 200) {}

  /** Parse one serialized pino line; keep it only if level>=error. */
  recordLine(line: string): void {
    const trimmed = line.trim();
    if (trimmed === '') return;
    let o: Record<string, unknown>;
    try {
      o = JSON.parse(trimmed) as Record<string, unknown>;
    } catch {
      return; // non-JSON (e.g. a raw console line) — skip
    }
    const level = typeof o['level'] === 'number' ? (o['level'] as number) : 0;
    if (level < ERROR_LEVEL) return;
    const errObj = o['err'] as { message?: string; type?: string } | undefined;
    const entry: ErrorLogEntry = {
      time: typeof o['time'] === 'number' ? (o['time'] as number) : 0,
      level,
    };
    if (typeof o['msg'] === 'string') entry.msg = o['msg'] as string;
    if (typeof o['kind'] === 'string') entry.kind = o['kind'] as string;
    if (typeof o['reqId'] === 'string') entry.reqId = o['reqId'] as string;
    const errMsg = errObj?.message ?? errObj?.type;
    if (errMsg !== undefined) entry.err = errMsg;
    this.buf.push(entry);
    if (this.buf.length > this.capacity) this.buf.shift();
  }

  /** Most recent first, capped at `limit`. */
  recent(limit = 50): ErrorLogEntry[] {
    return this.buf.slice(-limit).reverse();
  }

  /** A Writable for pino.multistream — receives serialized JSON log lines. */
  stream(): Writable {
    return new Writable({
      write: (chunk: Buffer | string, _enc, cb) => {
        for (const line of chunk.toString('utf-8').split('\n')) this.recordLine(line);
        cb();
      },
    });
  }
}

/** Process-wide singleton fed by the logger and read by the /logs/errors route. */
export const errorLogBuffer = new ErrorLogBuffer();
