import { describe, expect, it } from 'vitest'
import { aggregateHealth, type HealthCheck } from './health.js'

const check = (name: string, result: HealthCheck['check']): HealthCheck => ({ name, check: result })

describe('aggregateHealth', () => {
  it('rolls up to ok when every probe is ok', async () => {
    const r = await aggregateHealth([
      check('db', async () => ({ name: 'db', status: 'ok', latencyMs: 2 })),
      check('mail', async () => ({ name: 'mail', status: 'ok' })),
    ])
    expect(r.status).toBe('ok')
    expect(r.checks).toHaveLength(2)
  })

  it('a single degraded (no down) rolls up to degraded', async () => {
    const r = await aggregateHealth([
      check('db', async () => ({ name: 'db', status: 'ok' })),
      check('cache', async () => ({ name: 'cache', status: 'degraded', detail: 'slow' })),
    ])
    expect(r.status).toBe('degraded')
  })

  it('any down dominates degraded => down', async () => {
    const r = await aggregateHealth([
      check('cache', async () => ({ name: 'cache', status: 'degraded' })),
      check('db', async () => ({ name: 'db', status: 'down' })),
    ])
    expect(r.status).toBe('down')
  })

  it('AVAILABILITY FLOOR: a thrown probe becomes down — aggregate never throws', async () => {
    const r = await aggregateHealth([
      check('db', async () => ({ name: 'db', status: 'ok' })),
      check('mail', async () => {
        throw new Error('connection refused')
      }),
    ])
    expect(r.status).toBe('down')
    const mail = r.checks.find((c) => c.name === 'mail')
    expect(mail?.status).toBe('down')
    // info-disclosure floor: a thrown reason is scrubbed to a generic detail
    expect(mail?.detail).toBe('check failed')
  })

  it('SECURITY: a thrown reason carrying a secret is NEVER echoed into detail', async () => {
    const secret = 'smtp://user:hunter2@mail.internal:587'
    const r = await aggregateHealth([
      check('mail', async () => {
        throw new Error(secret)
      }),
    ])
    expect(r.status).toBe('down')
    const mail = r.checks[0]!
    expect(mail.detail).toBe('check failed')
    expect(mail.detail).not.toContain('hunter2')
    expect(mail.detail).not.toContain('mail.internal')
  })

  it('a non-Error rejection is also scrubbed to a generic detail (still down, still no throw)', async () => {
    const r = await aggregateHealth([
      check('jobs', async () => {
        throw 'queue gone' // eslint-disable-line @typescript-eslint/no-throw-literal
      }),
    ])
    expect(r.status).toBe('down')
    expect(r.checks[0]!.detail).toBe('check failed')
  })

  it('a check that RETURNS a down result keeps its own curated detail (only thrown reasons are scrubbed)', async () => {
    const r = await aggregateHealth([
      check('db', async () => ({ name: 'db', status: 'down', detail: 'query failed' })),
    ])
    expect(r.status).toBe('down')
    expect(r.checks[0]!.detail).toBe('query failed')
  })

  it('an empty check set rolls up to ok', async () => {
    const r = await aggregateHealth([])
    expect(r.status).toBe('ok')
    expect(r.checks).toEqual([])
  })

  it('AVAILABILITY FLOOR: a SYNCHRONOUSLY-throwing probe becomes down — aggregate never rejects', async () => {
    // A conforming `check(): Promise<…>` may be a non-async fn whose body throws before
    // returning (e.g. `() => adapter.ping()` with a null adapter). The aggregate must
    // still settle to a `down` result, never reject.
    const r = await aggregateHealth([
      check('db', async () => ({ name: 'db', status: 'ok' })),
      check('storage', () => {
        throw new Error('adapter is null')
      }),
    ])
    expect(r.status).toBe('down')
    const storage = r.checks.find((c) => c.name === 'storage')
    expect(storage?.status).toBe('down')
    expect(storage?.detail).toBe('check failed')
  })
})
