/**
 * Structured logger backed by pino.
 *
 * The previous implementation was a dependency-free shim that mimicked
 * pino's API so the swap could be a single-file change. Theme D pulls
 * the real package in: production JSON-per-line, fast async writes,
 * proper child loggers, error serialisation built in.
 *
 * Public surface (log.info({...}, 'msg'), log.child(...), levels) is
 * unchanged; callers don't need to be updated.
 */
import pino, { type Logger as PinoLogger, type LoggerOptions } from 'pino';
import { env } from '@/lib/env';

export type LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal';

/**
 * Fields scrubbed from every structured log record (remove:true). Covers
 * secret-shaped keys plus `email` PII (top-level + one nesting level) so an
 * accidental log.info({ email }) / log.error({ recipient: { email } }) never
 * writes an operator's or user's address to stdout. NOTE: pino redact only
 * applies to log.* — raw console.* calls bypass it (tracked separately).
 * Exported so the logger test asserts against the same list (no drift).
 */
export const REDACT_PATHS = [
    'password', 'token', 'sessionToken', 'pendingToken', 'email',
    '*.password', '*.token', '*.email',
];

export interface Logger {
    trace(ctx: Record<string, unknown>, msg?: string): void;
    debug(ctx: Record<string, unknown>, msg?: string): void;
    info(ctx: Record<string, unknown>, msg?: string): void;
    warn(ctx: Record<string, unknown>, msg?: string): void;
    error(ctx: Record<string, unknown>, msg?: string): void;
    fatal(ctx: Record<string, unknown>, msg?: string): void;
    child(bindings: Record<string, unknown>): Logger;
}

function activeLevel(): LogLevel {
    const raw = (env.LOG_LEVEL ?? 'info').toLowerCase();
    const allowed = ['trace', 'debug', 'info', 'warn', 'error', 'fatal'];
    return (allowed.includes(raw) ? raw : 'info') as LogLevel;
}

const options: LoggerOptions = {
    level: activeLevel(),
    base: undefined, // suppress pino's default pid/hostname noise; we'll add per-component bindings via child()
    timestamp: pino.stdTimeFunctions.isoTime,
    formatters: {
        // Pino's default level field is numeric (30, 40, ...). Use the
        // string for human + Grafana readability.
        level: (label) => ({ level: label }),
    },
    // Auto-serialise Error instances with name+message+stack.
    serializers: { err: pino.stdSerializers.err },
    redact: {
        paths: REDACT_PATHS,
        remove: true,
    },
};

const root: PinoLogger = pino(options);

function wrap(p: PinoLogger): Logger {
    return {
        trace: (ctx, msg) => p.trace(ctx, msg),
        debug: (ctx, msg) => p.debug(ctx, msg),
        info: (ctx, msg) => p.info(ctx, msg),
        warn: (ctx, msg) => p.warn(ctx, msg),
        error: (ctx, msg) => p.error(ctx, msg),
        fatal: (ctx, msg) => p.fatal(ctx, msg),
        child: (bindings) => wrap(p.child(bindings)),
    };
}

export const log: Logger = wrap(root);
