/**
 * A2 — unit tests for the ioredis → RedisPublisher adapter (boot/redis.ts). The adapter is the only
 * non-trivial logic in the server entrypoint's Redis wiring: it must forward publish(channel, message)
 * verbatim to the underlying client and surface the driver's subscriber-count result unchanged. The
 * live `createRedisClient` (a thin `new Redis(url)`) needs a real broker and is covered by the boot
 * smoke, not here.
 */
import { describe, test, expect } from 'vitest'
import type { Redis } from 'ioredis'
import { createRedisPublisher } from '../src/boot/redis.js'
import { createRedisRevocationBus } from '../src/routing/bus.js'
import { RELAY_REVOCATIONS_CHANNEL, type KillSignal } from 'relay-contracts'

/** Minimal ioredis stand-in recording publish calls; cast to Redis at the seam (only `publish` is used). */
function fakeRedis(returnValue = 1): { calls: Array<[string, string]>; client: Redis } {
  const calls: Array<[string, string]> = []
  const client = {
    publish: async (channel: string, message: string): Promise<number> => {
      calls.push([channel, message])
      return returnValue
    },
  } as unknown as Redis
  return { calls, client }
}

describe('A2 createRedisPublisher (RedisPublisher adapter)', () => {
  test('forwards channel + message verbatim and returns the driver subscriber count', async () => {
    // Arrange
    const { calls, client } = fakeRedis(3)
    const publisher = createRedisPublisher(client)

    // Act
    const receivers = await publisher.publish('relay:revocations', 'payload')

    // Assert
    expect(receivers).toBe(3)
    expect(calls).toEqual([['relay:revocations', 'payload']])
  })

  test('drives createRedisRevocationBus onto the FROZEN relay:revocations channel with JSON payload', async () => {
    // Arrange
    const { calls, client } = fakeRedis()
    const bus = createRedisRevocationBus(createRedisPublisher(client))
    const signal: KillSignal = {
      scope: { kind: 'host', hostId: '11111111-1111-4111-8111-111111111111' },
      at: 1_700_000_000,
      reason: 'revoked',
    }

    // Act
    await bus.publish(signal)

    // Assert — bus publishes exactly one message on the shared channel, JSON-encoded.
    expect(calls).toHaveLength(1)
    const [channel, message] = calls[0]!
    expect(channel).toBe(RELAY_REVOCATIONS_CHANNEL)
    expect(JSON.parse(message)).toMatchObject({ scope: { kind: 'host', hostId: signal.scope.kind === 'host' ? signal.scope.hostId : '' } })
  })
})
