/**
 * Logger sanity tests for the pino-backed implementation.
 *
 * Rather than spy on process.stdout (which is shared across parallel
 * jest workers and gives non-deterministic results), we build a sibling
 * pino instance with the same options pointed at a captured stream.
 * That tests the configuration (formatters, serializers, redact, level)
 * without coupling to global I/O.
 */

import pino, { type LoggerOptions } from 'pino';
import { REDACT_PATHS } from '../logger';

function buildLogger(opts: Partial<LoggerOptions> = {}): {
    log: pino.Logger;
    records: Array<Record<string, unknown>>;
} {
    const records: Array<Record<string, unknown>> = [];
    const stream = {
        write(line: string): boolean {
            for (const part of line.split('\n')) {
                if (!part.trim()) continue;
                try {
                    records.push(JSON.parse(part) as Record<string, unknown>);
                } catch {
                    // ignore non-JSON output
                }
            }
            return true;
        },
    };

    const log = pino(
        {
            level: 'info',
            base: undefined,
            timestamp: pino.stdTimeFunctions.isoTime,
            formatters: { level: (label) => ({ level: label }) },
            serializers: { err: pino.stdSerializers.err },
            redact: {
                paths: REDACT_PATHS,
                remove: true,
            },
            ...opts,
        },
        stream as pino.DestinationStream
    );
    return { log, records };
}

describe('logger (pino options)', () => {
    it('emits info with the message and context as JSON', () => {
        const { log, records } = buildLogger();
        log.info({ userId: 7 }, 'user signed in');
        expect(records).toHaveLength(1);
        expect(records[0].level).toBe('info');
        expect(records[0].msg).toBe('user signed in');
        expect(records[0].userId).toBe(7);
    });

    it('emits warn at level "warn"', () => {
        const { log, records } = buildLogger();
        log.warn({ reason: 'soft fail' }, 'something dodgy');
        expect(records[0].level).toBe('warn');
    });

    it('emits error and fatal at the right levels', () => {
        const { log, records } = buildLogger();
        log.error({ code: 'X' }, 'bad thing');
        log.fatal({ code: 'Y' }, 'worse thing');
        expect(records).toHaveLength(2);
        expect(records[0].level).toBe('error');
        expect(records[1].level).toBe('fatal');
    });

    it('serializes Error via the `err` serializer (type, message, stack)', () => {
        const { log, records } = buildLogger();
        log.error({ err: new Error('boom') }, 'caught');
        expect(records).toHaveLength(1);
        const err = records[0].err as { type?: string; message?: string; stack?: string };
        expect(err?.type).toBe('Error');
        expect(err?.message).toBe('boom');
        expect(typeof err?.stack).toBe('string');
    });

    it('child logger merges bindings into every emitted record', () => {
        const { log, records } = buildLogger();
        const child = log.child({ requestId: 'abc-123' });
        child.info({ userId: 7 }, 'with binding');
        expect(records[0].requestId).toBe('abc-123');
        expect(records[0].userId).toBe(7);
    });

    it('drops levels below the configured threshold (default info)', () => {
        const { log, records } = buildLogger();
        log.trace({}, 'trace');
        log.debug({}, 'debug');
        expect(records).toHaveLength(0);
    });

    it('redacts secret-shaped keys', () => {
        const { log, records } = buildLogger();
        log.info({ password: 'hunter2', token: 'abc', sessionToken: 'xyz' }, 'redacted ctx');
        expect(records[0].password).toBeUndefined();
        expect(records[0].token).toBeUndefined();
        expect(records[0].sessionToken).toBeUndefined();
    });

    it('redacts email PII (top-level and one level nested)', () => {
        const { log, records } = buildLogger();
        log.info({ email: 'user@example.com', recipient: { email: 'x@y.com' } }, 'has email');
        expect(records[0].email).toBeUndefined();
        expect((records[0].recipient as Record<string, unknown>)?.email).toBeUndefined();
    });
});

describe('logger (module surface)', () => {
    it('exports a log object with the expected method shape', async () => {
        const { log } = await import('../logger');
        expect(typeof log.trace).toBe('function');
        expect(typeof log.debug).toBe('function');
        expect(typeof log.info).toBe('function');
        expect(typeof log.warn).toBe('function');
        expect(typeof log.error).toBe('function');
        expect(typeof log.fatal).toBe('function');
        expect(typeof log.child).toBe('function');
        const child = log.child({ component: 'test' });
        expect(typeof child.info).toBe('function');
    });
});
