import { describe, test, expect } from 'vitest'
import { createMuxSession, type MuxSession } from '../../mux/mux-session.js'
import { createRelayNode } from '../../data-plane/relay-node.js'
import { loadDataPlaneConfig } from '../../data-plane/config.js'
import type { AgentListener, AgentTunnel } from '../../data-plane/agent-listener.js'
import type { UpgradeDecision, UpgradeRequest } from '../../data-plane/upgrade.js'

const config = loadDataPlaneConfig({
  BASE_DOMAIN: 'term.example.com',
  TLS_CERT_PATH: '/c',
  TLS_KEY_PATH: '/k',
  AGENT_CA_CERT_PATH: '/ca',
  RELAY_NODE_ID: 'node-1',
})

const noSchedule = () => ({ cancel: () => {} })

/** Build a real relay↔agent MuxSession pair; the agent echoes each DATA payload back verbatim. */
function wiredTunnel(hostId: string): AgentTunnel {
  let agent!: MuxSession
  const relay = createMuxSession({
    role: 'relay',
    sendWire: (f) => agent.onWire(f),
    onOpen: () => {},
    onData: () => {},
    onClose: () => {},
    onDead: () => {},
    maxFrameBytes: 1024,
    initialWindowBytes: 1_000_000,
    schedule: noSchedule,
  })
  agent = createMuxSession({
    role: 'agent',
    sendWire: (f) => relay.onWire(f),
    onOpen: (_open, stream) => {
      // The agent dials its local ws://127.0.0.1:3000 (here: an echo) — OPAQUE bytes only.
      stream.onData((bytes) => stream.writeData(bytes))
    },
    onData: () => {},
    onClose: () => {},
    onDead: () => {},
    maxFrameBytes: 1024,
    initialWindowBytes: 1_000_000,
    schedule: noSchedule,
  })
  return { hostId, accountId: 'acct-1', session: relay, closeTunnel: () => relay.close() }
}

function listenerWith(tunnels: Map<string, AgentTunnel>): AgentListener {
  return { attach: () => {}, tunnels: () => tunnels, close: () => {} }
}

function browserWs() {
  let onMsg: ((b: Uint8Array) => void) | null = null
  return {
    sent: [] as Uint8Array[],
    closedWith: undefined as number | undefined,
    send(b: Uint8Array) {
      this.sent.push(b)
    },
    close(code?: number) {
      this.closedWith = code
    },
    onMessage(h: (b: Uint8Array) => void) {
      onMsg = h
    },
    onClose() {},
    type: (b: Uint8Array) => onMsg?.(b),
  }
}

function okDecision(hostId: string): UpgradeDecision {
  return {
    ok: true,
    hostId,
    acceptedSubprotocol: 'term.relay.v1',
    open: { streamId: 0, subdomain: 'alice', requestPath: '/', originHeader: 'o', remoteAddrHash: 'h', capabilityTokenRef: 'jti' },
  }
}

describe('data-plane integration (T13) — end-to-end opaque splice through a real MuxSession pair', () => {
  test('INV2: bytes round-trip browser → agent → browser byte-identical, never parsed', async () => {
    const tunnels = new Map([['host-A', wiredTunnel('host-A')]])
    const node = createRelayNode({ config, listener: listenerWith(tunnels), authorize: async () => okDecision('host-A') })
    const ws = browserWs()
    await node.handleBrowserUpgrade({} as UpgradeRequest, ws)
    const payload = new TextEncoder().encode('echo-me-🔒-ciphertext')
    ws.type(payload)
    expect(ws.sent).toHaveLength(1)
    expect([...ws.sent[0]!]).toEqual([...payload]) // opaque round-trip
  })

  test('INV1: an upgrade authorized for a host not on this node cannot reach any tunnel', async () => {
    const tunnels = new Map([['host-A', wiredTunnel('host-A')]])
    const node = createRelayNode({ config, listener: listenerWith(tunnels), authorize: async () => okDecision('host-B') })
    const ws = browserWs()
    await node.handleBrowserUpgrade({} as UpgradeRequest, ws)
    expect(ws.closedWith).toBe(1013) // no tunnel for host-B; A is never reachable by another host's token
    expect(ws.sent).toHaveLength(0)
  })
})
