import { test, expect } from "bun:test";
import crypto from "node:crypto";
import {
  decodeVncPassword,
  vncChallengeResponse,
  vncPortForDisplay,
  resolveVncPassword,
} from "./vnc-keepalive.ts";

// Independent reference for VNC's bit-reversed-key DES, to cross-check the module.
const rev = (b: number) => {
  let r = 0;
  for (let i = 0; i < 8; i++) r = (r << 1) | ((b >> i) & 1);
  return r & 0xff;
};
const vncKey = (buf: Buffer) => Buffer.from([...buf.subarray(0, 8)].map(rev));
const desEnc = (key: Buffer, data: Buffer) => {
  const c = crypto.createCipheriv("des-ecb", key, null);
  c.setAutoPadding(false);
  return Buffer.concat([c.update(data), c.final()]);
};
const FIXED_KEY = Buffer.from([23, 82, 107, 6, 35, 78, 88, 7]);

test("decodeVncPassword inverts the fixed-key obfuscation", () => {
  const pw = Buffer.from("s3cr3t\0\0", "binary"); // 8 bytes, trailing nulls
  const obf = desEnc(vncKey(FIXED_KEY), pw); // how vncpasswd stores it
  expect(decodeVncPassword(obf).toString()).toBe("s3cr3t");
});

test("vncChallengeResponse encrypts both challenge blocks with the bit-reversed password key", () => {
  const pw = Buffer.from("hunter12");
  const challenge = crypto.randomBytes(16);
  const expected = desEnc(vncKey(pw), challenge);
  const got = vncChallengeResponse(pw, challenge);
  expect(got.length).toBe(16);
  expect(got.equals(expected)).toBe(true);
});

test("vncPortForDisplay maps an X display to its RFB port", () => {
  expect(vncPortForDisplay(":1")).toBe(5901);
  expect(vncPortForDisplay(":0")).toBe(5900);
  expect(vncPortForDisplay(":5")).toBe(5905);
});

test("resolveVncPassword prefers the VNC_PASSWORD env var", () => {
  const pw = resolveVncPassword({ VNC_PASSWORD: "letmein9" } as NodeJS.ProcessEnv);
  expect(pw?.toString()).toBe("letmein9");
});

test("resolveVncPassword returns null when nothing is available", () => {
  const pw = resolveVncPassword({ VNC_PASSWD_FILE: "/nonexistent/path/xyz" } as NodeJS.ProcessEnv);
  expect(pw).toBeNull();
});
