/// <reference types="node" />
import { describe, expect, it, vi } from 'vitest'
import { createLogger } from './logger.js'

describe('createLogger', () => {
  it('emits a stable envelope with injected clock and context', () => {
    const write = vi.fn()
    const logger = createLogger({
      level: 'debug',
      context: { service: 'awp' },
      write,
      now: () => new Date('2026-08-19T03:00:00.000Z'),
    })
    logger.info('started', { node: 'n1' })
    expect(write).toHaveBeenCalledWith({
      service: 'awp',
      node: 'n1',
      timestamp: '2026-08-19T03:00:00.000Z',
      level: 'info',
      message: 'started',
    })
  })

  it('filters below the configured level', () => {
    const write = vi.fn()
    const logger = createLogger({ level: 'warn', write })
    logger.debug('d')
    logger.info('i')
    logger.warn('w')
    logger.error('e')
    expect(write).toHaveBeenCalledTimes(2)
    expect(write.mock.calls.map(([record]) => record.level)).toEqual(['warn', 'error'])
  })

  it('merges child context with call-site context winning', () => {
    const write = vi.fn()
    const logger = createLogger({ context: { service: 'base', region: 'ca' }, write })
    logger.child({ component: 'worker', region: 'th' }).info('x', { requestId: 'r1', region: 'sg' })
    expect(write.mock.calls[0]![0]).toMatchObject({
      service: 'base',
      component: 'worker',
      requestId: 'r1',
      region: 'sg',
    })
  })

  it('normalizes Error and arbitrary thrown values without throwing', () => {
    const write = vi.fn()
    const logger = createLogger({ write })
    const cause = new Error('root')
    const error = new Error('boom', { cause })
    logger.error('failed', error)
    logger.error('odd', { reason: 1n })

    expect(write.mock.calls[0]![0]).toMatchObject({
      error: { name: 'Error', message: 'boom', cause: { name: 'Error', message: 'root' } },
    })
    expect(write.mock.calls[1]![0]).toMatchObject({ error: { reason: '1' } })
  })

  it('makes circular Error causes JSON-safe', () => {
    const write = vi.fn()
    const error = new Error('loop')
    error.cause = error
    const logger = createLogger({ write })

    expect(() => logger.error('failed', error)).not.toThrow()
    expect(write.mock.calls[0]![0]).toMatchObject({
      error: { name: 'Error', message: 'loop', cause: '[Circular]' },
    })
    expect(() => JSON.stringify(write.mock.calls[0]![0])).not.toThrow()
  })

  it('makes circular context JSON-safe', () => {
    const write = vi.fn()
    const circular: Record<string, unknown> = {}
    circular.self = circular
    const logger = createLogger({ write })
    expect(() => logger.info('circular', circular)).not.toThrow()
    expect(write.mock.calls[0]![0]).toMatchObject({ self: '[Circular]' })
    expect(() => JSON.stringify(write.mock.calls[0]![0])).not.toThrow()
  })

  it('does not misclassify repeated non-circular references as cycles', () => {
    const write = vi.fn()
    const shared = { node: 'n1' }
    const logger = createLogger({ write })
    logger.info('shared', { first: shared, second: shared })

    expect(write.mock.calls[0]![0]).toMatchObject({
      first: { node: 'n1' },
      second: { node: 'n1' },
    })
  })
})
