import { describe, expect, it } from 'vitest'

import { decodeRelayChunk, encodeRelayChunk } from '../../src/modules/relay-transport/compression'
import { RelayCipher } from '../../src/modules/relay-transport/crypto'
import {
  decodeInnerFrame,
  decodeRelayEnvelope,
  encodeInnerFrame,
  encodeRelayEnvelope,
  legacyV1WireBytesForStreamData,
  RELAY_ENVELOPE_KIND,
  RELAY_MAX_STREAM_CHUNK_BYTES,
  RELAY_PROTOCOL_VERSION,
} from '../../src/modules/relay-transport/protocol'

interface BenchmarkRow {
  checkpoint: string
  old: number
  new: number
  unit: string
  evidence: 'exact codec measurement' | 'exact scheduler model' | 'bounded-window model'
  conclusion: string
}

interface CodecSample {
  profile: 'compressible-json' | 'incompressible'
  payloadBytes: number
  v1WireBytes: number
  baselineV2WireBytes: number
  optimizedV2WireBytes: number
  v1Frames: number
  baselineV2Frames: number
  optimizedV2Frames: number
}

const OLD_CREDIT_BYTES = 512 * 1024
const NEW_MAX_CREDIT_BYTES = 8 * 1024 * 1024
const NEW_MAX_CONNECTION_CREDIT_BYTES = 16 * 1024 * 1024
const BULK_FRAMES_AHEAD_OF_CONTROL = 65
const RESERVED_MAX_CONTROL_FRAMES = 1

interface SchedulingFrame {
  priority: 'control' | 'data'
  streamId: string
  sequence: number
}

function makePayload(length: number, profile: CodecSample['profile']): Uint8Array {
  if (profile === 'compressible-json') {
    const record = Buffer.from('{"type":"relay_event","status":"streaming","content":"hello from Cradle"}\n')
    return Uint8Array.from({ length }, (_, index) => record[index % record.length])
  }
  let state = 0xA341_316C
  return Uint8Array.from({ length }, () => {
    state ^= state << 13
    state ^= state >>> 17
    state ^= state << 5
    return state & 0xFF
  })
}

function baselineV2Wire(payload: Uint8Array): { wireBytes: number, frames: number } {
  const roomBytes = Buffer.byteLength('room_benchmark')
  const streamBytes = Buffer.byteLength('benchmark')
  let wireBytes = 0
  let frames = 0
  for (let offset = 0; offset < payload.byteLength; offset += RELAY_MAX_STREAM_CHUNK_BYTES) {
    const chunkBytes = Math.min(RELAY_MAX_STREAM_CHUNK_BYTES, payload.byteLength - offset)
    // V2: outer header + room + outer stream id + encrypted(inner header +
    // inner stream id + data). XChaCha added nonce(24) + tag(16).
    wireBytes += 16 + roomBytes + streamBytes + 7 + streamBytes + chunkBytes + 40
    frames++
  }
  return { wireBytes, frames }
}

function encodeOptimizedV2Payload(payload: Uint8Array): { wireBytes: number, frames: number } {
  const cipher = new RelayCipher(new Uint8Array(32).fill(7))
  let wireBytes = 0
  let frames = 0
  for (let offset = 0; offset < payload.byteLength; offset += RELAY_MAX_STREAM_CHUNK_BYTES) {
    const chunk = payload.slice(offset, offset + RELAY_MAX_STREAM_CHUNK_BYTES)
    const compressed = encodeRelayChunk(chunk)
    const frame = {
      kind: 'stream_data' as const,
      streamId: 'benchmark',
      seq: offset,
      data: compressed.data,
      ...(compressed.compression === 'zstd'
        ? { compression: 'zstd' as const, uncompressedBytes: compressed.uncompressedBytes }
        : {}),
    }
    const encoded = encodeRelayEnvelope({
      version: RELAY_PROTOCOL_VERSION,
      roomId: 'room_benchmark',
      seq: frames,
      kind: RELAY_ENVELOPE_KIND.dataFrame,
      priority: 'data',
      streamId: frame.streamId,
      payload: cipher.encrypt(encodeInnerFrame(frame)),
    })
    const decoded = decodeRelayEnvelope(encoded)
    const decodedFrame = decodeInnerFrame(cipher.decrypt(decoded.payload))
    expect(decodedFrame).toMatchObject({ kind: 'stream_data', streamId: frame.streamId, seq: offset })
    if (decodedFrame.kind === 'stream_data') {
      expect(decodeRelayChunk({
        data: decodedFrame.data,
        compression: decodedFrame.compression ?? 'none',
        uncompressedBytes: decodedFrame.uncompressedBytes ?? decodedFrame.data.byteLength,
      })).toEqual(chunk)
    }
    wireBytes += encoded.byteLength
    frames += 1
  }
  return { wireBytes, frames }
}

function measureCodec(profile: CodecSample['profile'], payloadBytes: number): CodecSample {
  const payload = makePayload(payloadBytes, profile)
  const baselineV2 = baselineV2Wire(payload)
  const optimizedV2 = encodeOptimizedV2Payload(payload)
  return {
    profile,
    payloadBytes,
    v1WireBytes: legacyV1WireBytesForStreamData(payload),
    baselineV2WireBytes: baselineV2.wireBytes,
    optimizedV2WireBytes: optimizedV2.wireBytes,
    v1Frames: 1,
    baselineV2Frames: baselineV2.frames,
    optimizedV2Frames: optimizedV2.frames,
  }
}

function windowCapMiBps(windowBytes: number, rttMs: number): number {
  return windowBytes / (1024 * 1024) / (rttMs / 1_000)
}

function legacyFifoControlPosition(bulkFrameCount: number): number {
  const queue: SchedulingFrame[] = [
    ...Array.from({ length: bulkFrameCount }, (_, sequence) => ({
      priority: 'data' as const,
      streamId: 'bulk',
      sequence,
    })),
    { priority: 'control', streamId: 'control', sequence: 0 },
  ]
  return queue.findIndex(frame => frame.priority === 'control')
}

function priorityRoundRobinOrder(queue: SchedulingFrame[]): SchedulingFrame[] {
  const control = queue.filter(frame => frame.priority === 'control')
  const streams = new Map<string, SchedulingFrame[]>()
  for (const frame of queue) {
    if (frame.priority === 'data') {
      const frames = streams.get(frame.streamId) ?? []
      frames.push(frame)
      streams.set(frame.streamId, frames)
    }
  }
  const order = [...control]
  while (streams.size > 0) {
    for (const [streamId, frames] of [...streams]) {
      const frame = frames.shift()
      if (frame) {
        order.push(frame)
      }
      if (frames.length === 0) {
        streams.delete(streamId)
      }
    }
  }
  return order
}

function deltaPercent(old: number, next: number): string {
  if (old === 0) {
    return next === 0 ? '0.0%' : 'new guarantee'
  }
  const delta = ((next - old) / old) * 100
  return `${delta >= 0 ? '+' : ''}${delta.toFixed(1)}%`
}

function printBenchmark(codecSamples: CodecSample[], rows: BenchmarkRow[]): void {
  const report = {
    kind: 'relay-v2-performance-benchmark',
    run: {
      id: 'relay-v1-v2-baseline-optimized-compare',
      protocol: { v1: 1, baselineV2: RELAY_PROTOCOL_VERSION, optimizedV2: RELAY_PROTOCOL_VERSION },
      measurementBoundary: 'same-process codec and scheduler model',
      payloadPatterns: ['compressible JSON-like stream', 'deterministic incompressible stream'],
      loss: 'none',
      randomSeed: 'not applicable',
    },
    conditions: {
      oldMaxStreamChunkBytes: 256 * 1024,
      maxStreamChunkBytes: RELAY_MAX_STREAM_CHUNK_BYTES,
      oldCreditBytes: OLD_CREDIT_BYTES,
      optimizedCreditRangeBytes: [OLD_CREDIT_BYTES, NEW_MAX_CREDIT_BYTES],
      optimizedConnectionCreditBytes: NEW_MAX_CONNECTION_CREDIT_BYTES,
      bulkFramesAheadOfControl: BULK_FRAMES_AHEAD_OF_CONTROL,
      reservedMaximumControlFrames: RESERVED_MAX_CONTROL_FRAMES,
    },
    codecSamples: codecSamples.map(sample => ({
      ...sample,
      v1ToV2WireByteDeltaPercent: deltaPercent(sample.v1WireBytes, sample.optimizedV2WireBytes),
      baselineV2ToOptimizedV2WireDeltaPercent: deltaPercent(
        sample.baselineV2WireBytes,
        sample.optimizedV2WireBytes,
      ),
    })),
    rows,
    caveat: 'Wire and scheduler rows are exact under the stated inputs. Throughput rows are window bounds, not Internet throughput measurements. Run benchmark:relay:runtime for virtual-session RTT and local real-relayd cold/warm/concurrency samples.',
  }
  const markdown = [
    `# Relay V2 benchmark — ${report.run.id}`,
    '',
    '## Codec: exact byte measurements',
    '',
    '| Profile | Logical payload | V1 wire bytes | V2 baseline | V2 optimized | V1 → V2 | Baseline → optimized | V2 frames |',
    '| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: |',
    ...report.codecSamples.map(sample =>
      `| ${sample.profile} | ${sample.payloadBytes} B | ${sample.v1WireBytes} | ${sample.baselineV2WireBytes} | ${sample.optimizedV2WireBytes} | ${sample.v1ToV2WireByteDeltaPercent} | ${sample.baselineV2ToOptimizedV2WireDeltaPercent} | ${sample.baselineV2Frames}/${sample.optimizedV2Frames} |`),
    '',
    '## Checkpoints',
    '',
    '| Checkpoint | Old | New | Delta | Evidence |',
    '| --- | ---: | ---: | ---: | --- |',
    ...rows.map(row =>
      `| ${row.checkpoint} | ${row.old} ${row.unit} | ${row.new} ${row.unit} | ${deltaPercent(row.old, row.new)} | ${row.evidence} |`),
    '',
    'V2 baseline is the PR wire format before endpoint compression/adaptive AEAD. Optimized V2 keeps the relay envelope version, adds independently decodable Zstandard chunks, keeps low-latency XChaCha for small frames, and uses native AES-GCM for bulk frames. Incompressible chunks are sent raw, so compression never expands the wire.',
  ].join('\n')
  console.info(markdown)
  console.info(JSON.stringify(report))
}

describe('relay three-generation performance comparison', () => {
  it('emits a reproducible V1/V2 baseline-versus-optimized report and guards the gains', () => {
    const codecSamples = [
      ...(['compressible-json', 'incompressible'] as const).flatMap(profile => [
        measureCodec(profile, 1 * 1024),
        measureCodec(profile, 64 * 1024),
        measureCodec(profile, 256 * 1024),
      ]),
    ]
    const oldControlPosition = legacyFifoControlPosition(BULK_FRAMES_AHEAD_OF_CONTROL)
    const newSchedule = priorityRoundRobinOrder([
      ...Array.from({ length: BULK_FRAMES_AHEAD_OF_CONTROL }, (_, sequence) => ({
        priority: 'data' as const,
        streamId: 'bulk',
        sequence,
      })),
      { priority: 'control' as const, streamId: 'control', sequence: 0 },
    ])
    const newControlPosition = newSchedule.findIndex(frame => frame.priority === 'control')
    const fairDataOrder = priorityRoundRobinOrder([
      { priority: 'data', streamId: 'a', sequence: 1 },
      { priority: 'data', streamId: 'a', sequence: 2 },
      { priority: 'data', streamId: 'b', sequence: 1 },
    ]).map(frame => `${frame.streamId}${frame.sequence}`)
    expect(fairDataOrder).toEqual(['a1', 'b1', 'a2'])

    const rows: BenchmarkRow[] = [
      {
        checkpoint: 'control frames ahead of an interactive control frame',
        old: oldControlPosition,
        new: newControlPosition,
        unit: 'bulk frames',
        evidence: 'exact scheduler model',
        conclusion: 'V2 control priority bypasses queued bulk transfer frames.',
      },
      {
        checkpoint: 'maximum-size control frames admitted after bulk saturation',
        old: 0,
        new: RESERVED_MAX_CONTROL_FRAMES,
        unit: 'frames',
        evidence: 'exact scheduler model',
        conclusion: 'V2 keeps a maximum-frame byte reserve for ACK, close, and peer-notification traffic.',
      },
      {
        checkpoint: 'window cap at 20 ms RTT',
        old: windowCapMiBps(OLD_CREDIT_BYTES, 20),
        new: windowCapMiBps(NEW_MAX_CREDIT_BYTES, 20),
        unit: 'MiB/s',
        evidence: 'bounded-window model',
        conclusion: 'The new cap is reached only after ACK-driven credit growth.',
      },
      {
        checkpoint: 'window cap at 100 ms RTT',
        old: windowCapMiBps(OLD_CREDIT_BYTES, 100),
        new: windowCapMiBps(NEW_MAX_CREDIT_BYTES, 100),
        unit: 'MiB/s',
        evidence: 'bounded-window model',
        conclusion: 'A fixed 512 KiB window is increasingly restrictive as delay rises.',
      },
      {
        checkpoint: 'window cap at 200 ms RTT',
        old: windowCapMiBps(OLD_CREDIT_BYTES, 200),
        new: windowCapMiBps(NEW_MAX_CREDIT_BYTES, 200),
        unit: 'MiB/s',
        evidence: 'bounded-window model',
        conclusion: 'High-delay transfers can use 16x more in-flight bytes, bounded at 8 MiB per stream.',
      },
      {
        checkpoint: 'window cap at 400 ms RTT',
        old: windowCapMiBps(OLD_CREDIT_BYTES, 400),
        new: windowCapMiBps(NEW_MAX_CREDIT_BYTES, 400),
        unit: 'MiB/s',
        evidence: 'bounded-window model',
        conclusion: 'The gain scales with delay only when the path and receiver can sustain it.',
      },
      {
        checkpoint: 'bounded in-flight bytes per stream',
        old: OLD_CREDIT_BYTES / (1024 * 1024),
        new: NEW_MAX_CREDIT_BYTES / (1024 * 1024),
        unit: 'MiB',
        evidence: 'bounded-window model',
        conclusion: 'This is the deliberate high-BDP trade-off; the new sender remains capped at 8 MiB per stream.',
      },
    ]
    printBenchmark(codecSamples, rows)

    for (const sample of codecSamples) {
      expect(sample.optimizedV2WireBytes).toBeLessThanOrEqual(sample.baselineV2WireBytes)
      if (sample.profile === 'compressible-json') {
        expect(sample.optimizedV2WireBytes).toBeLessThan(sample.baselineV2WireBytes * 0.2)
      }
    }
    expect(rows.find(row => row.checkpoint === 'control frames ahead of an interactive control frame')?.new).toBe(0)
    expect(rows.find(row => row.checkpoint === 'window cap at 200 ms RTT')?.new).toBe(40)
  })
})
