import { describe, expect, it } from 'vitest'
import { APP_SUBPROTOCOL, encodeTokenSubprotocol } from 'relay-contracts'
import { readConfig } from '../src/config'
import { createPassthroughTransport, type WebSocketLike } from '../src/ws-transport'
import { encodeDpopSubprotocol } from '../src/dpop'

const cfg = readConfig({
  protocol: 'https:',
  host: 'alice.term.example.com',
  hostname: 'alice.term.example.com',
})

class MockWs implements WebSocketLike {
  static last: MockWs | null = null
  binaryType = ''
  protocol = ''
  readonly url: string
  readonly protocols: readonly string[]
  closed: { code?: number; reason?: string } | null = null
  onopen: ((ev: unknown) => void) | null = null
  onmessage: ((ev: { data: unknown }) => void) | null = null
  onclose: ((ev: { code?: number; reason?: string }) => void) | null = null
  onerror: ((ev: unknown) => void) | null = null
  constructor(url: string, protocols?: string | readonly string[]) {
    this.url = url
    this.protocols = protocols === undefined ? [] : typeof protocols === 'string' ? [protocols] : protocols
    MockWs.last = this
  }
  send(): void {}
  close(code?: number, reason?: string): void {
    const ev: { code?: number; reason?: string } = {}
    if (code !== undefined) ev.code = code
    if (reason !== undefined) ev.reason = reason
    this.closed = ev
    this.onclose?.(ev)
  }
  fireOpen(echoedProtocol: string): void {
    this.protocol = echoedProtocol
    this.onopen?.({})
  }
}

describe('createPassthroughTransport — B6 DPoP proof attachment', () => {
  const PROOF = 'aGVhZGVy.cGF5bG9hZA.c2ln'

  it('token + DPoP both ride Sec-WebSocket-Protocol in the frozen order, NEVER the URL', async () => {
    const t = createPassthroughTransport(cfg, {
      wsCtor: MockWs,
      capabilityToken: 'RAWTOK',
      dpopProof: PROOF,
    })
    const opened = t.open()
    MockWs.last!.fireOpen(APP_SUBPROTOCOL)
    await opened
    expect(MockWs.last!.protocols).toEqual([
      APP_SUBPROTOCOL,
      encodeTokenSubprotocol('RAWTOK'),
      encodeDpopSubprotocol(PROOF),
    ])
    expect(MockWs.last!.url).not.toContain('RAWTOK')
    expect(MockWs.last!.url).not.toContain(PROOF)
    expect(MockWs.last!.url).not.toContain(encodeDpopSubprotocol(PROOF))
    expect(MockWs.last!.url).not.toMatch(/\?/)
  })

  it('echo rule holds with a DPoP bearer: only APP_SUBPROTOCOL back is accepted', async () => {
    const t = createPassthroughTransport(cfg, { wsCtor: MockWs, dpopProof: PROOF })
    const opened = t.open()
    MockWs.last!.fireOpen(encodeDpopSubprotocol(PROOF)) // relay wrongly echoes the DPoP entry
    await expect(opened).rejects.toThrow()
    expect(MockWs.last!.closed).not.toBeNull()
  })

  it('no dpopProof → protocols unchanged from the v0.9 token-only path (backward compatible)', async () => {
    const t = createPassthroughTransport(cfg, { wsCtor: MockWs, capabilityToken: 'RAWTOK' })
    const opened = t.open()
    MockWs.last!.fireOpen(APP_SUBPROTOCOL)
    await opened
    expect(MockWs.last!.protocols).toEqual([APP_SUBPROTOCOL, encodeTokenSubprotocol('RAWTOK')])
  })
})
