import { EventEmitter } from 'node:events'
import { describe, expect, it, vi } from 'vitest'
import { composeWorker, installGracefulShutdown } from './composition.js'
import type { HealthStatus, WorkerLoop } from './contracts.js'
import { parseWorkerEnvironment, workerEnvironmentFromProcess } from './environment.js'
import { createHealthReporter } from './observability.js'

const environment = parseWorkerEnvironment({
  NODE_ENV: 'test',
  PDF2HTML_WORKER_ID: 'worker-test',
  PDF2HTML_WORKER_SHUTDOWN_TIMEOUT_MS: '1000',
})

describe('worker composition shell', () => {
  it('rejects unknown and malformed environment values', () => {
    expect(() => parseWorkerEnvironment({ PDF2HTML_WORKER_ID: 'x', SURPRISE: '1' })).toThrow('Unknown')
    expect(() => parseWorkerEnvironment({ PDF2HTML_WORKER_ID: 'x', PDF2HTML_WORKER_HEALTH_PORT: '0' })).toThrow('between')
    expect(() => parseWorkerEnvironment({})).toThrow('required')
  })

  it('rejects unknown worker process variables but ignores unrelated ambient variables', () => {
    expect(() => workerEnvironmentFromProcess({
      PDF2HTML_WORKER_ID: 'x',
      PDF2HTML_WORKER_HELTH_PORT: '4000',
      PATH: '/usr/bin',
    })).toThrow('PDF2HTML_WORKER_HELTH_PORT')

    expect(workerEnvironmentFromProcess({ PDF2HTML_WORKER_ID: 'x', PATH: '/usr/bin' }).healthPort).toBe(3001)
  })

  it('records a synchronously throwing loop as a worker failure', async () => {
    const loop: WorkerLoop = { start: vi.fn(() => { throw new Error('sync loop failure') }) }
    const reporter = createHealthReporter('worker-test')
    const metrics = { increment: vi.fn(), observe: vi.fn() }
    const worker = composeWorker(environment, { loop, health: reporter, metrics })

    await worker.start()
    await vi.waitFor(() => expect(worker.health()).toMatchObject({ status: 'failed', detail: 'sync loop failure' }))
    expect(metrics.increment).toHaveBeenCalledWith('worker_failures_total')
  })

  it('records and propagates a deterministic shutdown timeout', async () => {
    vi.useFakeTimers()
    try {
      const loop: WorkerLoop = { start: vi.fn(() => new Promise<void>(() => {})) }
      const reporter = createHealthReporter('worker-test')
      const metrics = { increment: vi.fn(), observe: vi.fn() }
      const worker = composeWorker(environment, { loop, health: reporter, metrics })

      await worker.start()
      const stopping = worker.stop('test-timeout')
      const expected = `Worker shutdown exceeded ${environment.shutdownTimeoutMs}ms`
      const rejection = expect(stopping).rejects.toThrow(expected)
      await vi.advanceTimersByTimeAsync(environment.shutdownTimeoutMs)
      await rejection
      expect(worker.health()).toMatchObject({ status: 'failed', detail: expected })
      expect(metrics.increment).toHaveBeenCalledWith('worker_failures_total')
    } finally {
      vi.useRealTimers()
    }
  })

  it('injects dependencies and drains the loop on shutdown', async () => {
    let release!: () => void
    const loop: WorkerLoop = {
      start: vi.fn(async ({ signal }) => new Promise<void>((resolve) => {
        release = resolve
        signal.addEventListener('abort', resolve, { once: true })
      })),
    }
    const statuses: HealthStatus[] = []
    const reporter = createHealthReporter('worker-test')
    const health = { ...reporter, setStatus(status: HealthStatus, detail?: string) { statuses.push(status); reporter.setStatus(status, detail) } }
    const metrics = { increment: vi.fn(), observe: vi.fn() }
    const worker = composeWorker(environment, { loop, health, metrics })

    await worker.start()
    expect(worker.health().status).toBe('ready')
    await worker.stop('test')
    release()
    expect(statuses).toEqual(['starting', 'ready', 'stopping', 'stopped'])
    expect(metrics.increment).toHaveBeenCalledWith('worker_shutdowns_total', { reason: 'test' })
  })

  it('registers and removes graceful-shutdown signal handlers', async () => {
    const source = new EventEmitter()
    const worker = { start: vi.fn(), stop: vi.fn(async () => {}), health: vi.fn() }
    const dispose = installGracefulShutdown(worker, source)
    source.emit('SIGTERM')
    await vi.waitFor(() => expect(worker.stop).toHaveBeenCalledWith('SIGTERM'))
    dispose()
    expect(source.listenerCount('SIGTERM')).toBe(0)
    expect(source.listenerCount('SIGINT')).toBe(0)
  })
})
