import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'

const secondaryGateways: Array<{
  close: ReturnType<typeof vi.fn>
  connect: ReturnType<typeof vi.fn>
  connectionState: string
  request: ReturnType<typeof vi.fn>
}> = []

let connectGate: Promise<void> | null = null

vi.mock('@/hermes', () => ({
  HermesGateway: class {
    connectionState = 'closed'
    connect = vi.fn(async () => {
      if (this.connectionState === 'connecting') {
        return
      }

      this.connectionState = 'connecting'

      if (connectGate) {
        await connectGate
      }

      this.connectionState = 'open'
    })
    request = vi.fn(async (method: string, params: Record<string, unknown>) => {
      if (this.connectionState !== 'open') {
        throw new Error('gateway is not connected')
      }

      return { method, params }
    })
    close = vi.fn()
    onEvent = vi.fn(() => () => {})
    onState = vi.fn(() => () => {})

    constructor() {
      secondaryGateways.push(this)
    }
  },
  setApiRequestConnection: vi.fn()
}))
vi.mock('@/store/session', () => ({ setConnection: vi.fn(), setGatewayState: vi.fn() }))
vi.mock('@/store/notify-baseline', () => ({ markNativeNotifyBaseline: vi.fn() }))

const {
  $gateway,
  closeSecondaryGateways,
  configureGatewayRegistry,
  ensureGatewayForAgent,
  ensureGatewayForProfile,
  gatewayActivationEpoch,
  disposeSecondariesForConnection,
  openGatewayForAgent,
  pruneSecondaryGateways,
  requestGatewayForAgent,
  requestGatewayForProfile,
  retainGatewayForAgent,
  setPrimaryGateway,
  setPrimaryGatewayConnection
} = await import('./gateway')

function installDesktop(getConnection: ReturnType<typeof vi.fn>): void {
  ;(window as unknown as { hermesDesktop: unknown }).hermesDesktop = {
    getConnection,
    touchBackend: vi.fn(async () => undefined)
  }
}

function makePrimary() {
  return {
    connectionState: 'open',
    request: vi.fn(async (method: string, params: Record<string, unknown>) => ({ method, params }))
  }
}

beforeEach(async () => {
  secondaryGateways.length = 0
  connectGate = null
  configureGatewayRegistry({ onEvent: vi.fn() })
  closeSecondaryGateways()
})

afterEach(() => {
  closeSecondaryGateways()
  vi.clearAllMocks()
  delete (window as unknown as { hermesDesktop?: unknown }).hermesDesktop
})

describe('requestGatewayForProfile', () => {
  it('requests through a pooled profile gateway without changing the active gateway', async () => {
    const primary = makePrimary()
    setPrimaryGateway(primary as never, 'default')
    installDesktop(
      vi.fn(async (profile: null | string) =>
        profile ? { port: 5151, profile, token: 'secondary-token' } : { port: 4242, token: 'primary-token' }
      )
    )
    await ensureGatewayForProfile('default')

    const result = await requestGatewayForProfile('worker', 'profiles.list', { include_sessions: true })

    expect(result).toEqual({ method: 'profiles.list', params: { include_sessions: true } })
    expect(secondaryGateways).toHaveLength(1)
    expect(secondaryGateways[0].request).toHaveBeenCalledWith('profiles.list', { include_sessions: true })
    expect(secondaryGateways[0].close).toHaveBeenCalledOnce()
    expect($gateway.get()).toBe(primary)
  })

  it('uses the primary socket and adds profile scope for a shared global remote route', async () => {
    const primary = makePrimary()
    setPrimaryGateway(primary as never, 'default')
    installDesktop(
      vi.fn(async (profile: null | string) => ({
        port: 4242,
        ...(profile ? { profile, sharedPrimary: true } : {}),
        token: 'primary-token'
      }))
    )
    await ensureGatewayForProfile('default')

    const result = await requestGatewayForProfile('venture', 'session.list', { limit: 20, profile: 'wrong' })

    expect(result).toEqual({ method: 'session.list', params: { limit: 20, profile: 'venture' } })
    expect(primary.request).toHaveBeenCalledWith('session.list', { limit: 20, profile: 'venture' })
    expect(secondaryGateways).toHaveLength(0)
    expect($gateway.get()).toBe(primary)
  })

  it('serializes concurrent requests while a secondary gateway is connecting', async () => {
    let releaseConnect: () => void = () => undefined
    connectGate = new Promise<void>(resolve => {
      releaseConnect = resolve
    })

    const primary = makePrimary()
    setPrimaryGateway(primary as never, 'default')
    installDesktop(
      vi.fn(async (profile: null | string) =>
        profile ? { port: 5151, profile, token: 'secondary-token' } : { port: 4242, token: 'primary-token' }
      )
    )
    await ensureGatewayForProfile('default')

    const first = requestGatewayForProfile('worker', 'profiles.list')
    await vi.waitFor(() => expect(secondaryGateways[0]?.connect).toHaveBeenCalledOnce())
    const second = requestGatewayForProfile('worker', 'profiles.list')
    const guardedSecond = second.catch(error => error)

    await Promise.resolve()
    releaseConnect()

    const [firstResult, secondResult] = await Promise.all([first, guardedSecond])

    expect(firstResult).toEqual({ method: 'profiles.list', params: {} })
    expect(secondResult).toEqual({ method: 'profiles.list', params: {} })
    expect(secondaryGateways[0].connect).toHaveBeenCalledOnce()
    expect(secondaryGateways[0].request).toHaveBeenCalledTimes(2)
    expect(secondaryGateways[0].close).toHaveBeenCalledOnce()
    expect($gateway.get()).toBe(primary)
  })

  it('prevents pruning while a background request is connecting or in flight', async () => {
    let releaseConnect: () => void = () => undefined
    connectGate = new Promise<void>(resolve => {
      releaseConnect = resolve
    })

    const primary = makePrimary()
    setPrimaryGateway(primary as never, 'default')
    installDesktop(
      vi.fn(async (profile: null | string) =>
        profile ? { port: 5151, profile, token: 'secondary-token' } : { port: 4242, token: 'primary-token' }
      )
    )
    await ensureGatewayForProfile('default')

    const request = requestGatewayForProfile('worker', 'profiles.list')
    await vi.waitFor(() => expect(secondaryGateways[0]?.connect).toHaveBeenCalledOnce())

    pruneSecondaryGateways(new Set())
    releaseConnect()

    await expect(request).resolves.toEqual({ method: 'profiles.list', params: {} })
    expect(secondaryGateways[0].close).toHaveBeenCalledOnce()

    pruneSecondaryGateways(new Set())
    expect(secondaryGateways[0].close).toHaveBeenCalledOnce()
  })
})

describe('requestGatewayForAgent', () => {
  it('reuses the active primary socket when its registry connection owns the session', async () => {
    const primary = makePrimary()

    const getConnectionFor = vi.fn(async ({ connectionId, profile }) => ({
      connectionId,
      port: 5151,
      profile,
      token: 'secondary-token'
    }))

    setPrimaryGateway(primary as never, 'default')
    setPrimaryGatewayConnection({ connectionId: 'remote-primary' })

    ;(window as unknown as { hermesDesktop: unknown }).hermesDesktop = {
      getConnection: vi.fn(),
      getConnectionFor,
      getGatewayWsUrlFor: vi.fn(async () => ({ ok: true as const, wsUrl: 'wss://remote.invalid/api/ws' })),
      touchBackend: vi.fn(async () => undefined)
    }
    await ensureGatewayForProfile('default')

    await openGatewayForAgent('remote-primary', 'default')
    await ensureGatewayForAgent('remote-primary', 'default')

    const result = await requestGatewayForAgent('remote-primary', 'default', 'session.resume', {
      session_id: 'stored-session'
    })

    expect(result).toEqual({
      method: 'session.resume',
      params: { session_id: 'stored-session' }
    })
    expect(primary.request).toHaveBeenCalledWith('session.resume', { session_id: 'stored-session' })
    expect(getConnectionFor).not.toHaveBeenCalled()
    expect(secondaryGateways).toHaveLength(0)
    expect($gateway.get()).toBe(primary)
  })

  it('keeps another profile on the same registry source isolated from the primary', async () => {
    const primary = makePrimary()

    const getConnectionFor = vi.fn(async ({ connectionId, profile }) => ({
      connectionId,
      port: 5151,
      profile,
      token: 'secondary-token'
    }))

    setPrimaryGateway(primary as never, 'default')
    setPrimaryGatewayConnection({ connectionId: 'remote-primary' })

    ;(window as unknown as { hermesDesktop: unknown }).hermesDesktop = {
      getConnection: vi.fn(),
      getConnectionFor,
      getGatewayWsUrlFor: vi.fn(async () => ({ ok: true as const, wsUrl: 'wss://remote.invalid/api/ws' })),
      touchBackend: vi.fn(async () => undefined)
    }

    await requestGatewayForAgent('remote-primary', 'research', 'session.resume', {
      session_id: 'research-session'
    })

    expect(getConnectionFor).toHaveBeenCalledWith({ connectionId: 'remote-primary', profile: 'research' })
    expect(primary.request).not.toHaveBeenCalled()
    expect(secondaryGateways).toHaveLength(1)
  })

  it('leases separate registry sockets for duplicate profile names without changing the active gateway', async () => {
    const primary = makePrimary()
    const getConnection = vi.fn(async (profile: null | string) => ({ port: 4242, profile, token: 'legacy-token' }))

    const getConnectionFor = vi.fn(async ({ connectionId, profile }) => ({
      connectionId,
      port: connectionId === 'source-a' ? 5151 : 5252,
      profile,
      token: `${connectionId}-token`
    }))

    const getGatewayWsUrlFor = vi.fn(async ({ connectionId, profile }) => ({
      ok: true as const,
      wsUrl: `ws://${connectionId}/${profile}`
    }))

    setPrimaryGateway(primary as never, 'default')
    ;(window as unknown as { hermesDesktop: unknown }).hermesDesktop = {
      getConnection,
      getConnectionFor,
      getGatewayWsUrlFor,
      touchBackend: vi.fn(async () => undefined)
    }
    await ensureGatewayForProfile('default')

    const [fromA, fromB] = await Promise.all([
      requestGatewayForAgent('source-a', 'research', 'session.list', { limit: 1 }),
      requestGatewayForAgent('source-b', 'research', 'session.list', { limit: 2 })
    ])

    expect(fromA).toEqual({ method: 'session.list', params: { limit: 1 } })
    expect(fromB).toEqual({ method: 'session.list', params: { limit: 2 } })
    expect(getConnectionFor).toHaveBeenCalledWith({ connectionId: 'source-a', profile: 'research' })
    expect(getConnectionFor).toHaveBeenCalledWith({ connectionId: 'source-b', profile: 'research' })
    expect(getGatewayWsUrlFor).toHaveBeenCalledWith({ connectionId: 'source-a', profile: 'research' })
    expect(getGatewayWsUrlFor).toHaveBeenCalledWith({ connectionId: 'source-b', profile: 'research' })
    expect(getConnection).not.toHaveBeenCalled()
    expect(secondaryGateways).toHaveLength(2)
    expect(secondaryGateways[0].close).toHaveBeenCalledOnce()
    expect(secondaryGateways[1].close).toHaveBeenCalledOnce()
    expect($gateway.get()).toBe(primary)
  })

  it('routes an explicit local registry descriptor through getConnectionFor', async () => {
    const primary = makePrimary()

    const getConnection = vi.fn(async (profile: null | string) => ({
      mode: 'remote',
      profile,
      wsUrl: 'wss://legacy-remote.invalid/api/ws?token=legacy'
    }))

    const getConnectionFor = vi.fn(async ({ connectionId, profile }) => ({
      connectionId,
      mode: 'local',
      port: 5151,
      profile
    }))

    setPrimaryGateway(primary as never, 'default')
    ;(window as unknown as { hermesDesktop: unknown }).hermesDesktop = {
      getConnection,
      getConnectionFor,
      getGatewayWsUrlFor: vi.fn(async ({ connectionId, profile }) => ({
        ok: true as const,
        wsUrl: `ws://${connectionId}/${profile}`
      })),
      touchBackend: vi.fn(async () => undefined)
    }
    await ensureGatewayForProfile('default')

    await expect(requestGatewayForAgent('local', 'worker', 'profiles.list')).resolves.toEqual({
      method: 'profiles.list',
      params: {}
    })
    expect(getConnectionFor).toHaveBeenCalledWith({ connectionId: 'local', profile: 'worker' })
    expect(getConnection).not.toHaveBeenCalled()
    expect($gateway.get()).toBe(primary)
  })

  it('evicts registry sockets when their source is edited or removed', async () => {
    const primary = makePrimary()

    const getConnectionFor = vi.fn(async ({ connectionId, profile }) => ({ connectionId, port: 5151, profile }))
    const onActiveConnectionInvalidated = vi.fn()

    setPrimaryGateway(primary as never, 'default')
    configureGatewayRegistry({ onActiveConnectionInvalidated, onEvent: vi.fn() })
    ;(window as unknown as { hermesDesktop: unknown }).hermesDesktop = {
      getConnection: vi.fn(),
      getConnectionFor,
      getGatewayWsUrlFor: vi.fn(async ({ connectionId, profile }) => ({
        ok: true as const,
        wsUrl: `ws://${connectionId}/${profile}`
      })),
      touchBackend: vi.fn(async () => undefined)
    }

    await openGatewayForAgent('source-a', 'research')
    await requestGatewayForAgent('source-a', 'research', 'session.list')
    disposeSecondariesForConnection('source-a')

    expect(secondaryGateways[0].close).toHaveBeenCalledOnce()
    expect(onActiveConnectionInvalidated).not.toHaveBeenCalled()

    await requestGatewayForAgent('source-a', 'research', 'session.list')
    expect(secondaryGateways).toHaveLength(2)
    expect(getConnectionFor).toHaveBeenCalledTimes(2)
  })

  it('invalidates an active pinned source with its window profile and a route epoch', async () => {
    const primary = makePrimary()
    const onActiveConnectionChanged = vi.fn()
    const onActiveConnectionInvalidated = vi.fn()

    setPrimaryGateway(primary as never, 'pinned')
    configureGatewayRegistry({ onActiveConnectionChanged, onActiveConnectionInvalidated, onEvent: vi.fn() })
    ;(window as unknown as { hermesDesktop: unknown }).hermesDesktop = {
      getConnection: vi.fn(),
      getConnectionFor: vi.fn(async ({ connectionId, profile }) => ({ connectionId, port: 5151, profile })),
      getGatewayWsUrlFor: vi.fn(async ({ connectionId, profile }) => ({
        ok: true as const,
        wsUrl: `ws://${connectionId}/${profile}`
      })),
      touchBackend: vi.fn(async () => undefined)
    }

    await ensureGatewayForAgent('source-a', 'pinned')
    disposeSecondariesForConnection('source-a')

    const invalidationEpoch = gatewayActivationEpoch()
    expect(onActiveConnectionInvalidated).toHaveBeenCalledWith('pinned', invalidationEpoch)
    expect($gateway.get()).toBe(primary)

    // Same profile, different source advances the guard as soon as activation
    // starts, before its deferred socket connect can finish.
    let releaseConnect: () => void = () => undefined
    connectGate = new Promise<void>(resolve => {
      releaseConnect = resolve
    })
    const sourceBActivation = ensureGatewayForAgent('source-b', 'pinned')
    await vi.waitFor(() => expect(secondaryGateways[1]?.connect).toHaveBeenCalledOnce())
    expect(gatewayActivationEpoch()).toBeGreaterThan(invalidationEpoch)
    releaseConnect()
    await sourceBActivation
    expect(onActiveConnectionChanged).toHaveBeenLastCalledWith(expect.objectContaining({ connectionId: 'source-b' }))
  })

  it('does not activate or publish a source invalidated while its dial is pending', async () => {
    const primary = makePrimary()
    const onActiveConnectionChanged = vi.fn()

    setPrimaryGateway(primary as never, 'default')
    configureGatewayRegistry({ onActiveConnectionChanged, onEvent: vi.fn() })
    ;(window as unknown as { hermesDesktop: unknown }).hermesDesktop = {
      getConnection: vi.fn(async profile => ({ port: 4242, profile })),
      getConnectionFor: vi.fn(async ({ connectionId, profile }) => ({ connectionId, port: 5151, profile })),
      getGatewayWsUrlFor: vi.fn(async ({ connectionId, profile }) => ({
        ok: true as const,
        wsUrl: `ws://${connectionId}/${profile}`
      })),
      touchBackend: vi.fn(async () => undefined)
    }
    await ensureGatewayForProfile('default')

    let releaseConnect: () => void = () => undefined
    connectGate = new Promise<void>(resolve => {
      releaseConnect = resolve
    })
    const pendingActivation = ensureGatewayForAgent('source-b', 'default')
    await vi.waitFor(() => expect(secondaryGateways[0]?.connect).toHaveBeenCalledOnce())

    disposeSecondariesForConnection('source-b')
    releaseConnect()
    await pendingActivation

    expect(secondaryGateways[0].close).toHaveBeenCalled()
    expect(onActiveConnectionChanged).not.toHaveBeenCalled()
    expect($gateway.get()).toBe(primary)
  })

  it('does not activate or publish a source whose activation owner aborted while dialing', async () => {
    const primary = makePrimary()
    const onActiveConnectionChanged = vi.fn()
    const controller = new AbortController()

    setPrimaryGateway(primary as never, 'default')
    configureGatewayRegistry({ onActiveConnectionChanged, onEvent: vi.fn() })
    ;(window as unknown as { hermesDesktop: unknown }).hermesDesktop = {
      getConnection: vi.fn(async profile => ({ port: 4242, profile })),
      getConnectionFor: vi.fn(async ({ connectionId, profile }) => ({ connectionId, port: 5151, profile })),
      getGatewayWsUrlFor: vi.fn(async ({ connectionId, profile }) => ({
        ok: true as const,
        wsUrl: `ws://${connectionId}/${profile}`
      })),
      touchBackend: vi.fn(async () => undefined)
    }
    await ensureGatewayForProfile('default')

    let releaseConnect: () => void = () => undefined
    connectGate = new Promise<void>(resolve => {
      releaseConnect = resolve
    })
    const abandoned = ensureGatewayForAgent('source-b', 'default', { signal: controller.signal })
    await vi.waitFor(() => expect(secondaryGateways[0]?.connect).toHaveBeenCalledOnce())

    controller.abort()
    releaseConnect()

    expect(await abandoned).toBe(false)
    expect(onActiveConnectionChanged).not.toHaveBeenCalled()
    expect($gateway.get()).toBe(primary)
  })
})

describe('retainGatewayForAgent (#93602)', () => {
  function installRegistryDesktop() {
    ;(window as unknown as { hermesDesktop: unknown }).hermesDesktop = {
      getConnection: vi.fn(async (profile: null | string) => ({ port: 4242, profile, token: 't' })),
      getConnectionFor: vi.fn(async ({ connectionId, profile }) => ({ connectionId, port: 5151, profile })),
      getGatewayWsUrlFor: vi.fn(async ({ connectionId, profile }) => ({
        ok: true as const,
        wsUrl: `ws://${connectionId}/${profile}`
      })),
      touchBackend: vi.fn(async () => undefined)
    }
  }

  it('holds the registry socket across multiple leased requests — no mid-turn disposal', async () => {
    const primary = makePrimary()
    setPrimaryGateway(primary as never, 'default')
    installRegistryDesktop()
    await ensureGatewayForProfile('default')

    const release = await retainGatewayForAgent('mini', 'helper')

    // The whole member-turn RPC sequence: each call takes and releases its own
    // per-request lease. Without the retain, refcount 0 between calls disposes
    // the socket and the runtime session with it.
    await requestGatewayForAgent('mini', 'helper', 'session.create', { title: 'g' })
    await requestGatewayForAgent('mini', 'helper', 'image.attach_bytes', { session_id: 'rt-1' })
    await requestGatewayForAgent('mini', 'helper', 'prompt.submit', { session_id: 'rt-1', text: 'hi' })

    expect(secondaryGateways).toHaveLength(1)
    expect(secondaryGateways[0].close).not.toHaveBeenCalled()
    expect(secondaryGateways[0].request).toHaveBeenCalledTimes(3)

    release()
    expect(secondaryGateways[0].close).toHaveBeenCalledOnce()
  })

  it('release is idempotent — a double release never underflows into a foreign disposal', async () => {
    const primary = makePrimary()
    setPrimaryGateway(primary as never, 'default')
    installRegistryDesktop()
    await ensureGatewayForProfile('default')

    const release = await retainGatewayForAgent('mini', 'helper')
    release()
    release()

    expect(secondaryGateways[0].close).toHaveBeenCalledOnce()

    // A fresh retain still works after the double release.
    const again = await retainGatewayForAgent('mini', 'helper')
    await requestGatewayForAgent('mini', 'helper', 'prompt.submit', { session_id: 'rt-2', text: 'hi' })
    expect(secondaryGateways[1].close).not.toHaveBeenCalled()
    again()
    expect(secondaryGateways[1].close).toHaveBeenCalledOnce()
  })

  it('without the retain, the leased socket closes after each request (the #93602 race)', async () => {
    const primary = makePrimary()
    setPrimaryGateway(primary as never, 'default')
    installRegistryDesktop()
    await ensureGatewayForProfile('default')

    await requestGatewayForAgent('mini', 'helper', 'session.create', { title: 'g' })

    // Refcount hit 0 → disposed: this is the socket close that reaps the
    // runtime session server-side and makes the later prompt.submit 4001.
    expect(secondaryGateways[0].close).toHaveBeenCalledOnce()
  })

  it('plain-profile retain leases the pooled profile socket and releases it', async () => {
    const primary = makePrimary()
    setPrimaryGateway(primary as never, 'default')
    installDesktop(
      vi.fn(async (profile: null | string) =>
        profile ? { port: 5151, profile, token: 'secondary-token' } : { port: 4242, token: 'primary-token' }
      )
    )
    await ensureGatewayForProfile('default')

    const release = await retainGatewayForAgent(null, 'worker')
    await requestGatewayForProfile('worker', 'session.create', { title: 'g' })
    await requestGatewayForProfile('worker', 'prompt.submit', { session_id: 'rt-1', text: 'hi' })

    expect(secondaryGateways).toHaveLength(1)
    expect(secondaryGateways[0].close).not.toHaveBeenCalled()

    release()
    expect(secondaryGateways[0].close).toHaveBeenCalledOnce()
  })
})

describe('attached shared-remote group turns (#96493)', () => {
  function installAttachedSharedRemote() {
    ;(window as unknown as { hermesDesktop: unknown }).hermesDesktop = {
      getConnection: vi.fn(async (profile: null | string) => ({ port: 4242, profile, token: 't' })),
      getConnectionFor: vi.fn(async ({ connectionId, profile }: { connectionId: string; profile: string }) => ({
        connectionId,
        port: 9119,
        profile,
        sharedRemote: true
      })),
      getGatewayWsUrlFor: vi.fn(async ({ connectionId, profile }: { connectionId: string; profile: string }) => ({
        ok: true as const,
        wsUrl: `ws://${connectionId}/${profile}`
      })),
      touchBackend: vi.fn(async () => undefined)
    }
  }

  it('reuses the primary socket for a named profile on the attached shared remote', async () => {
    const primary = makePrimary()
    setPrimaryGateway(primary as never, 'default')
    setPrimaryGatewayConnection({ connectionId: 'homelab' })
    installAttachedSharedRemote()
    await ensureGatewayForProfile('default')

    const release = await retainGatewayForAgent('homelab', 'voter')
    await requestGatewayForAgent('homelab', 'voter', 'session.create', { title: 'Group: room' })
    await requestGatewayForAgent('homelab', 'voter', 'prompt.submit', { session_id: 'rt-1', text: 'hi' })

    expect(secondaryGateways).toHaveLength(0)
    expect(primary.request).toHaveBeenCalledTimes(2)
    expect(primary.request).toHaveBeenNthCalledWith(1, 'session.create', {
      title: 'Group: room',
      profile: 'voter'
    })
    expect(primary.request).toHaveBeenNthCalledWith(2, 'prompt.submit', {
      session_id: 'rt-1',
      text: 'hi',
      profile: 'voter'
    })

    release()
    expect(secondaryGateways).toHaveLength(0)
  })

  it('still dials a secondary when the attached source is not a shared remote', async () => {
    const primary = makePrimary()
    setPrimaryGateway(primary as never, 'default')
    setPrimaryGatewayConnection({ connectionId: 'homelab' })
    ;(window as unknown as { hermesDesktop: unknown }).hermesDesktop = {
      getConnection: vi.fn(async (profile: null | string) => ({ port: 4242, profile, token: 't' })),
      getConnectionFor: vi.fn(async ({ connectionId, profile }: { connectionId: string; profile: string }) => ({
        connectionId,
        port: 5151,
        profile,
        sharedRemote: false
      })),
      getGatewayWsUrlFor: vi.fn(async ({ connectionId, profile }: { connectionId: string; profile: string }) => ({
        ok: true as const,
        wsUrl: `ws://${connectionId}/${profile}`
      })),
      touchBackend: vi.fn(async () => undefined)
    }
    await ensureGatewayForProfile('default')

    await requestGatewayForAgent('homelab', 'voter', 'session.create', { title: 'g' })

    expect(secondaryGateways).toHaveLength(1)
    expect(primary.request).not.toHaveBeenCalled()
  })

  it('reuses the primary when the shared-remote probe fails instead of dialing a ghost secondary', async () => {
    const primary = makePrimary()
    setPrimaryGateway(primary as never, 'default')
    setPrimaryGatewayConnection({ connectionId: 'homelab' })
    ;(window as unknown as { hermesDesktop: unknown }).hermesDesktop = {
      getConnection: vi.fn(async (profile: null | string) => ({ port: 4242, profile, token: 't' })),
      getConnectionFor: vi.fn(async () => {
        throw new Error('Timed out connecting to profile "voter"')
      }),
      getGatewayWsUrlFor: vi.fn(async () => ({ ok: true as const, wsUrl: 'ws://homelab/voter' })),
      touchBackend: vi.fn(async () => undefined)
    }
    await ensureGatewayForProfile('default')

    await requestGatewayForAgent('homelab', 'voter', 'session.create', { title: 'g' })

    expect(secondaryGateways).toHaveLength(0)
    expect(primary.request).toHaveBeenCalledOnce()
  })

  it('openGatewayForAgent and ensureGatewayForAgent do not dial a secondary', async () => {
    const primary = makePrimary()
    setPrimaryGateway(primary as never, 'default')
    setPrimaryGatewayConnection({ connectionId: 'homelab' })
    installAttachedSharedRemote()
    await ensureGatewayForProfile('default')

    await openGatewayForAgent('homelab', 'voter')
    expect(await ensureGatewayForAgent('homelab', 'voter')).toBe(true)
    expect(secondaryGateways).toHaveLength(0)
  })

  it('ensureGatewayForAgent is false when the attached primary socket is closed', async () => {
    const primary = { connectionState: 'closed', request: vi.fn() }
    setPrimaryGateway(primary as never, 'default')
    setPrimaryGatewayConnection({ connectionId: 'homelab' })
    installAttachedSharedRemote()

    expect(await ensureGatewayForAgent('homelab', 'voter')).toBe(false)
    expect(secondaryGateways).toHaveLength(0)
  })
})
