import { describe, expect, it, vi } from 'vitest'
import { decodeMuxFrame, encodeMuxFrame, encodeOpen, type MuxOpen } from 'relay-contracts'
import type { AgentConfig } from '../../src/config/agentConfig.js'
import { createLogger } from '../../src/log/logger.js'
import { holdTunnel, dataHeader } from '../../src/transport/tunnel.js'
import { createStreamRouter, identityTransform } from '../../src/transport/streamRouter.js'
import { FakeWs } from '../fixtures/fakes.js'

/**
 * T18 café-demo agent slice: pair (implied) → dial (mock) → OPEN → loopback splice against a stub
 * ws://127.0.0.1:3000 echo server → bytes flow BOTH ways (EXPLORE §5 demo, agent portion).
 */
const CFG: AgentConfig = {
  relayUrl: 'wss://relay/agent',
  enrollUrl: 'https://x/enroll',
  stateDir: '/tmp/x',
  localTargetUrl: 'ws://127.0.0.1:3000',
  subdomain: 'host-42',
  hostId: 'h-1',
}
const OPEN: MuxOpen = {
  streamId: 5,
  subdomain: 'host-42',
  requestPath: '/term?join=abc',
  originHeader: 'https://host-42.term.example.com',
  remoteAddrHash: 'x',
  capabilityTokenRef: 'jti',
}
const flush = () => new Promise((r) => setImmediate(r))

describe('café-demo agent slice (T18)', () => {
  it('splices bytes both ways through the loopback echo', async () => {
    const upstream = new FakeWs()
    const tunnel = holdTunnel(upstream)
    const loopback = new FakeWs()
    const dial = vi.fn(async () => loopback)
    const router = createStreamRouter(CFG, tunnel, dial as never, identityTransform, createLogger('error', () => {}))
    tunnel.dispatchTo(router, { onPing: () => {}, onPong: () => {} })

    // relay → agent: OPEN + a keystroke
    ws_emitOpen(upstream, OPEN)
    await flush()
    expect(dial).toHaveBeenCalledWith('/term?join=abc', 'https://host-42.term.example.com')

    upstream.emitMessage(encodeMuxFrame(dataHeader(5, 3), new Uint8Array([104, 105, 10]))) // "hi\n"
    expect(loopback.sent.at(-1)).toEqual(new Uint8Array([104, 105, 10]))

    // agent ← base app: echo output flows back as DATA upstream
    loopback.emit('message', new Uint8Array([79, 75])) // "OK"
    const last = decodeMuxFrame(upstream.sent.at(-1)!)
    expect(last.header.type).toBe('data')
    expect([...last.payload]).toEqual([79, 75])
  })
})

function ws_emitOpen(upstream: FakeWs, open: MuxOpen): void {
  const payload = encodeOpen(open)
  upstream.emitMessage(
    encodeMuxFrame(
      { version: 1, type: 'open', fin: false, rst: false, streamId: open.streamId, payloadLen: payload.length },
      payload,
    ),
  )
}
