/**
 * Tests for electron/gateway-ws-probe.cjs.
 *
 * Run with: node --test electron/gateway-ws-probe.test.cjs
 * (Wired into npm test:desktop:platforms in package.json.)
 *
 * The probe drives a real WebSocket handshake for the "Test remote" button.
 * Here we inject a fake socket so we can deterministically replay each handshake
 * outcome (open, frame, error, early close, never-opens) without a network.
 */

const test = require('node:test')
const assert = require('node:assert/strict')

const { probeGatewayWebSocket } = require('./gateway-ws-probe.cjs')

// Minimal WebSocket double: records listeners synchronously (the probe attaches
// them in its executor) and exposes emit() so the test can replay events.
function makeFakeWs() {
  const instances = []
  class FakeWs {
    constructor(url) {
      this.url = url
      this.listeners = {}
      this.closed = false
      instances.push(this)
    }
    addEventListener(type, fn) {
      ;(this.listeners[type] ||= []).push(fn)
    }
    close() {
      this.closed = true
    }
    emit(type, event) {
      for (const fn of this.listeners[type] || []) fn(event)
    }
  }
  return { FakeWs, instances }
}

const FAST = { connectTimeoutMs: 1_000, readyGraceMs: 10 }

test('probe resolves ok when the socket opens and stays open', async () => {
  const { FakeWs, instances } = makeFakeWs()
  const promise = probeGatewayWebSocket('ws://host/api/ws?token=t', { WebSocketImpl: FakeWs, ...FAST })
  instances[0].emit('open')
  const result = await promise
  assert.deepEqual(result, { ok: true })
  assert.equal(instances[0].closed, true)
})

test('probe resolves ok immediately when a frame arrives', async () => {
  const { FakeWs, instances } = makeFakeWs()
  const promise = probeGatewayWebSocket('ws://host/api/ws?token=t', {
    WebSocketImpl: FakeWs,
    connectTimeoutMs: 1_000,
    readyGraceMs: 10_000 // long grace: success must come from the frame, not the timer
  })
  instances[0].emit('open')
  instances[0].emit('message', { data: '{"jsonrpc":"2.0"}' })
  const result = await promise
  assert.deepEqual(result, { ok: true })
})

test('probe fails when the socket errors before opening', async () => {
  const { FakeWs, instances } = makeFakeWs()
  const promise = probeGatewayWebSocket('ws://host/api/ws?token=t', { WebSocketImpl: FakeWs, ...FAST })
  instances[0].emit('error', { message: 'ECONNREFUSED' })
  const result = await promise
  assert.equal(result.ok, false)
  assert.match(result.reason, /ECONNREFUSED/)
})

test('probe fails when the gateway closes before opening', async () => {
  const { FakeWs, instances } = makeFakeWs()
  const promise = probeGatewayWebSocket('ws://host/api/ws?token=t', { WebSocketImpl: FakeWs, ...FAST })
  instances[0].emit('close', { code: 1006 })
  const result = await promise
  assert.equal(result.ok, false)
  assert.match(result.reason, /before it opened/)
  assert.match(result.reason, /1006/)
})

test('probe fails when the gateway accepts then immediately closes (auth rejected)', async () => {
  const { FakeWs, instances } = makeFakeWs()
  const promise = probeGatewayWebSocket('ws://host/api/ws?token=t', { WebSocketImpl: FakeWs, ...FAST })
  instances[0].emit('open')
  instances[0].emit('close', { code: 4403, reason: 'forbidden' })
  const result = await promise
  assert.equal(result.ok, false)
  assert.match(result.reason, /credential rejected/)
  assert.match(result.reason, /4403/)
  assert.match(result.reason, /forbidden/)
})

test('probe times out when the socket never opens', async () => {
  const { FakeWs } = makeFakeWs()
  const result = await probeGatewayWebSocket('ws://host/api/ws?token=t', {
    WebSocketImpl: FakeWs,
    connectTimeoutMs: 20,
    readyGraceMs: 10
  })
  assert.equal(result.ok, false)
  assert.match(result.reason, /Timed out/)
})

test('probe fails gracefully when the constructor throws', async () => {
  class ThrowingWs {
    constructor() {
      throw new Error('bad url')
    }
  }
  const result = await probeGatewayWebSocket('ws://host/api/ws', { WebSocketImpl: ThrowingWs, ...FAST })
  assert.equal(result.ok, false)
  assert.match(result.reason, /bad url/)
})

test('probe reports unavailable when no WebSocket implementation is provided', async () => {
  const result = await probeGatewayWebSocket('ws://host/api/ws', { WebSocketImpl: undefined })
  assert.equal(result.ok, false)
  assert.match(result.reason, /not available/)
})
