const assert = require('assert');
const fs = require('fs');
const path = require('path');
const vm = require('vm');

function fakeSocket() {
  return {
    destroyed: false,
    localAddress: '192.168.86.249',
    remoteAddress: '192.168.86.21',
    writableLength: 0,
    writes: [],
    setNoDelay() {},
    setTimeout() {},
    on() { return this; },
    once() { return this; },
    destroy() {
      this.destroyed = true;
    },
    write(line) {
      this.writes.push(line);
      return true;
    },
  };
}

function loadApp(networkInterfaces) {
  const source = fs.readFileSync(path.join(__dirname, '..', 'app.js'), 'utf8') + `
globalThis.__openMouseTest = {
  peers,
  connecting,
  setStore(value) { store = value; },
  securePeer(peer) {
    if (!store.identity) {
      pairingSecurity.ensureIdentity(store);
    }
    if (!store.pairedPeers) {
      store.pairedPeers = {};
    }
    const identity = pairingSecurity.createIdentity();
    peer.publicKeyPem = identity.publicKeyPem;
    peer.fingerprint = identity.fingerprint;
    store.pairedPeers[peer.hostId] = {
      publicKeyPem: identity.publicKeyPem,
      fingerprint: identity.fingerprint,
      enabled: true,
    };
    if (peer.socket) {
      peer.socket.openmouseAuthenticated = true;
      peer.socket.openmouseRemoteId = peer.hostId;
      peer.socket.openmouseSessionKey = Buffer.alloc(32, 7);
    }
    return peer;
  },
  decodeTcpFrame(line, peer) {
    return pairingSecurity.openFrame(
      Buffer.alloc(32, 7),
      JSON.parse(line),
      {
        transport: 'tcp',
        senderHostId: store.hostId,
        receiverHostId: peer.hostId,
      }
    );
  },
  decodeUdpFrame(envelope, peer) {
    return pairingSecurity.openFrame(
      Buffer.alloc(32, 7),
      envelope.sealed,
      {
        transport: 'udp',
        senderHostId: store.hostId,
        receiverHostId: peer.hostId,
      }
    );
  },
  setMode(value) { mode = value; },
  setReceivingPeer(hostId) {
    mode = 'receiving';
    inboundControlPeerId = hostId;
  },
  setDiscoverySocket(value) { discoverySocket = value; },
  setOffscreenPointerState(value) {
    mode = 'offscreen';
    activePeerId = value.hostId;
    activeEdge = value.edge;
    activeRemoteBounds = value.bounds;
    remoteX = value.x;
    remoteY = value.y;
  },
  getRemotePointer() { return { x: remoteX, y: remoteY }; },
  setNativePointerCaptureReady(value) { nativePointerCaptureReady = value; },
  getRemoteMotionStats(senderId) {
    const state = remoteMouseStates[senderId];
    return state ? remoteMotionStatsSnapshot(state.motionStats) : null;
  },
  addressRank,
  applyOffscreenPointerDelta,
  broadcastPresence,
  candidateAddresses,
  connectDirectPeerHints,
  directPeerHintAddresses,
  maybeReconnectToBetterAdvertisedPath,
  beginCoordStream,
  clearPeerCoordQueue,
  coordFlushDelayForPayload,
  handleNativeKeyTapLine,
  handleFrame,
  pollPointer,
  remoteMouseMove,
  remoteMouseButton,
  sendCoordToPeer,
  flushPendingCoord,
  returnPointInsideLocalBoundary,
  pointerAtDisplayEdge,
};
`;
  const timers = [];
  const immediates = [];
  const createdConnections = [];
  const createdDatagramSockets = [];
  const nativeMouseWrites = [];
  const robotMoves = [];
  const robotScrolls = [];
  const robotPosition = { x: 500, y: 500 };
  let now = Date.now();
  class TestDate extends Date {}
  TestDate.now = () => now;
  const sandbox = {
    __dirname: path.join(__dirname, '..'),
    Buffer,
    Date: TestDate,
    Math,
    Number,
    Object,
    String,
    console,
    clearTimeout(handle) {
      if (handle) {
        handle.cancelled = true;
      }
    },
    clearImmediate(handle) {
      if (handle) {
        handle.cancelled = true;
      }
    },
    setImmediate(fn) {
      const handle = { fn, cancelled: false };
      immediates.push(handle);
      return handle;
    },
    setTimeout(fn, delay) {
      const handle = { fn, delay, cancelled: false };
      timers.push(handle);
      return handle;
    },
    setInterval() {
      return {};
    },
    process: {
      platform: 'darwin',
      exit() {},
    },
    require(id) {
      if (id === 'electron') {
        return {
          app: {
            getPath() { return '/tmp/openmouse-test'; },
            on() {},
          },
          BrowserWindow: function BrowserWindow() {},
          ipcMain: { on() {} },
          powerSaveBlocker: { start() {} },
          systemPreferences: { isTrustedAccessibilityClient() { return true; } },
          screen: {
            on() {},
            getPrimaryDisplay() { return { id: '1' }; },
            getAllDisplays() { return []; },
          },
          globalShortcut: {
            register() { return false; },
            unregister() {},
          },
          clipboard: {
            readText() { return ''; },
            writeText() {},
          },
        };
      }
      if (id === 'os') {
        return {
          hostname() { return 'local'; },
          networkInterfaces() { return networkInterfaces; },
          homedir() { return '/tmp'; },
        };
      }
      if (id === 'child_process') {
        return {
          execFileSync(command) {
            if (command === '/usr/sbin/arp') {
              return '? (169.254.148.80) at 36:a2:48:34:11:c0 on bridge0 [bridge]';
            }
            return Object.keys(networkInterfaces).map(name => `${name}:\n\tstatus: active`).join('\n');
          },
          spawn() {
            return {
              stdin: {
                writable: true,
                write(line) {
                  nativeMouseWrites.push(line);
                  return true;
                },
                end() {},
              },
              stderr: { on() {} },
              stdout: { on() {} },
              on() {},
              kill() {},
            };
          },
        };
      }
      if (id === 'net') {
        return {
          createConnection(options) {
            createdConnections.push(options);
            return fakeSocket();
          },
          createServer() {
            return { on() {}, listen() {} };
          },
        };
      }
      if (id === 'dgram') {
        return {
          createSocket() {
            const socket = {
              boundAddress: null,
              closed: false,
              sends: [],
              on() { return this; },
              bind(port, address, callback) {
                this.boundAddress = address;
                if (callback) {
                  callback();
                }
              },
              send(buffer, offset, length, port, address, callback) {
                this.sends.push({ port, address });
                if (callback) {
                  callback(null);
                }
              },
              setBroadcast() {},
              unref() {},
              close() {
                this.closed = true;
              },
            };
            createdDatagramSockets.push(socket);
            return socket;
          },
        };
      }
      if (id === 'robotjs') {
        return {
          getScreenSize() { return { width: 1440, height: 900 }; },
          getMousePos() { return { x: robotPosition.x, y: robotPosition.y }; },
          moveMouse(x, y) {
            robotMoves.push({ x, y });
            robotPosition.x = x;
            robotPosition.y = y;
          },
          scrollMouse(x, y) {
            robotScrolls.push({ x, y });
          },
        };
      }
      if (id === 'keycode') {
        return () => 'unknown';
      }
      if (id === './settings') {
        return {};
      }
      if (id === './log') {
        return () => {};
      }
      if (id === './event-capture') {
        return { default: { on() {}, start() {}, stop() {} } };
      }
      if (id === './shortcut') {
        return require(path.join(__dirname, '..', 'shortcut.js'));
      }
      return require(id);
    },
  };

  vm.runInNewContext(source, sandbox, { filename: 'app.js' });
  return {
    api: sandbox.__openMouseTest,
    timers,
    immediates,
    nativeMouseWrites,
    robotMoves,
    robotScrolls,
    setRobotPosition(x, y) {
      robotPosition.x = x;
      robotPosition.y = y;
    },
    setNow(value) {
      now = value;
    },
    createdConnections,
    createdDatagramSockets,
  };
}

const bridgeAndWifi = {
  bridge0: [{
    family: 'IPv4',
    internal: false,
    address: '169.254.148.80',
    netmask: '255.255.255.0',
  }],
  en0: [{
    family: 'IPv4',
    internal: false,
    address: '192.168.86.249',
    netmask: '255.255.255.0',
  }],
};

{
  const { api, createdConnections } = loadApp({
    bridge0: [{
      family: 'IPv4',
      internal: false,
      address: '169.254.148.79',
      netmask: '255.255.0.0',
    }],
  });
  api.setStore({
    autoConnect: true,
    hostId: 'remote',
    layout: {},
    directPeerAddresses: [],
  });
  api.setMode('onscreen');

  assert(api.directPeerHintAddresses().includes('169.254.148.80'));
  api.connectDirectPeerHints();
  assert.strictEqual(
    createdConnections.length,
    0,
    'unpaired direct-link devices should be discovered without opening a control socket'
  );
}

{
  const { api, createdDatagramSockets } = loadApp(bridgeAndWifi);
  const primaryBroadcasts = [];
  api.setStore({
    autoConnect: true,
    hostId: 'local',
    nickname: 'local',
    layout: {},
    releaseSequence: [27, 27, 27],
  });
  api.setDiscoverySocket({
    send(buffer, offset, length, port, address) {
      primaryBroadcasts.push(address);
    },
  });

  api.broadcastPresence();

  assert(primaryBroadcasts.includes('169.254.148.255'));
  assert(primaryBroadcasts.includes('192.168.86.255'));
  assert.strictEqual(createdDatagramSockets.length, 2);
  createdDatagramSockets.forEach(socket => {
    assert.strictEqual(socket.sends.length, 1);
    assert.strictEqual(socket.sends[0].address, '255.255.255.255');
    assert.strictEqual(socket.sends[0].port, 5872);
    assert.strictEqual(socket.closed, true);
  });
  assert(createdDatagramSockets.some(socket => socket.boundAddress === '169.254.148.80'));
  assert(createdDatagramSockets.some(socket => socket.boundAddress === '192.168.86.249'));
}

{
  const { api, createdConnections } = loadApp(bridgeAndWifi);
  api.setStore({ autoConnect: true, hostId: 'local', layout: {} });
  api.setMode('onscreen');
  api.peers.peer = {
    hostId: 'peer',
    nickname: 'peer',
    tcpPort: 5873,
    socket: fakeSocket(),
    connectedAddress: '192.168.86.21',
    remoteAddress: '192.168.86.21',
    localAddress: '192.168.86.249',
    lastAddress: '192.168.86.21',
    discoveryAddress: '192.168.86.21',
    interfaces: [
      { name: 'en0', address: '192.168.86.21' },
      { name: 'bridge0', address: '169.254.148.79' },
    ],
  };
  api.securePeer(api.peers.peer);

  assert.strictEqual(api.candidateAddresses(api.peers.peer)[0], '169.254.148.79');
  assert(api.addressRank('169.254.148.79', api.peers.peer) < api.addressRank('192.168.86.21', api.peers.peer));

  api.maybeReconnectToBetterAdvertisedPath(api.peers.peer);
  assert.strictEqual(createdConnections.length, 1);
  assert.strictEqual(createdConnections[0].host, '169.254.148.79');
}

{
  const { api, createdConnections } = loadApp({
    en0: bridgeAndWifi.en0,
  });
  api.setStore({ autoConnect: true, hostId: 'local', layout: {} });
  api.setMode('onscreen');
  api.peers.peer = {
    hostId: 'peer',
    nickname: 'peer',
    tcpPort: 5873,
    socket: fakeSocket(),
    connectedAddress: '169.254.148.79',
    remoteAddress: '169.254.148.79',
    localAddress: '169.254.148.80',
    lastAddress: '169.254.148.79',
    discoveryAddress: '192.168.86.21',
    interfaces: [
      { name: 'en0', address: '192.168.86.21' },
    ],
  };
  api.securePeer(api.peers.peer);

  api.maybeReconnectToBetterAdvertisedPath(api.peers.peer);
  assert.strictEqual(createdConnections.length, 0);
}

{
  const { api, timers } = loadApp({
    en0: bridgeAndWifi.en0,
  });
  api.setStore({ autoConnect: true, hostId: 'local', layout: {} });
  api.peers.peer = {
    hostId: 'peer',
    nickname: 'peer',
    socket: fakeSocket(),
    connectedAddress: '192.168.86.21',
    remoteAddress: '192.168.86.21',
    localAddress: '192.168.86.249',
    lastAddress: '192.168.86.21',
    interfaces: [
      { name: 'en0', address: '192.168.86.21' },
    ],
  };
  api.securePeer(api.peers.peer);

  assert.strictEqual(api.sendCoordToPeer('peer', { x: 1, y: 1 }), true);
  assert.strictEqual(api.peers.peer.socket.writes.length, 1);
  assert.strictEqual(api.sendCoordToPeer('peer', { x: 2, y: 2 }), true);
  assert.strictEqual(api.sendCoordToPeer('peer', { x: 3, y: 3 }), true);
  assert.strictEqual(api.peers.peer.socket.writes.length, 1);
  assert.strictEqual(timers.length, 1);

  timers[0].fn();
  assert.strictEqual(api.peers.peer.socket.writes.length, 2);
  const latestFrame = api.decodeTcpFrame(
    api.peers.peer.socket.writes[1],
    api.peers.peer
  );
  assert.strictEqual(latestFrame.x, 3);
  assert.strictEqual(latestFrame.y, 3);
}

{
  const { api } = loadApp(bridgeAndWifi);
  const bounds = { x: 0, y: 0, width: 1512, height: 982 };
  ['right', 'left', 'top', 'bottom'].forEach(edge => {
    const point = api.returnPointInsideLocalBoundary(bounds, edge);
    assert.strictEqual(api.pointerAtDisplayEdge(point, bounds, edge), false);
  });
}

{
  const { api, immediates, nativeMouseWrites, setNow } = loadApp(bridgeAndWifi);

  setNow(1000);
  api.remoteMouseMove('peer', 10, 10, 1, 5000);
  setNow(1004);
  api.remoteMouseMove('peer', 20, 20, 2, 5004);
  setNow(1025);
  api.remoteMouseMove('peer', 30, 30, 4, 5025);

  assert.strictEqual(nativeMouseWrites.length, 0);
  assert.strictEqual(immediates.length, 1);
  setNow(1027);
  immediates[0].fn();
  assert.strictEqual(nativeMouseWrites.length, 1);
  assert(nativeMouseWrites[0].includes('move 30 30'));

  setNow(1030);
  assert.strictEqual(api.remoteMouseMove('peer', 5, 5, 2, 5030), false);
  assert.strictEqual(immediates.length, 1);
  const trace = api.getRemoteMotionStats('peer');
  assert.strictEqual(trace.arrivals, 4);
  assert.strictEqual(trace.renders, 1);
  assert.strictEqual(trace.coalesced, 2);
  assert.strictEqual(trace.stale, 1);
  assert.strictEqual(trace.seqSkipped, 1);
  assert.strictEqual(trace.source.maximum, 21);
  assert.strictEqual(trace.arrival.maximum, 21);
  assert.strictEqual(trace.source.overFrame, 1);
  assert.strictEqual(trace.arrival.overFrame, 1);
}

{
  const { api, immediates, timers, nativeMouseWrites, setNow } = loadApp(bridgeAndWifi);

  setNow(1000);
  api.remoteMouseMove('peer', 0, 100, 1, 5000);
  immediates[0].fn();
  assert(nativeMouseWrites[0].includes('move 0 100'));

  setNow(1020);
  api.remoteMouseMove('peer', 20, 100, 2, 5020);
  assert.strictEqual(timers.length, 1);
  assert.strictEqual(timers[0].delay, 4);

  setNow(1024);
  timers[0].fn();
  assert(nativeMouseWrites[1].includes('move 10 100'));

  setNow(1028);
  timers[1].fn();
  assert(nativeMouseWrites[2].includes('move 20 100'));

  setNow(1040);
  api.remoteMouseMove('peer', 26, 100, 3, 5040);
  setNow(1044);
  timers[2].fn();
  assert(nativeMouseWrites[3].includes('move 22 100'));
  setNow(1048);
  timers[3].fn();
  assert(nativeMouseWrites[4].includes('move 24 100'));
  setNow(1052);
  timers[4].fn();
  assert(nativeMouseWrites[5].includes('move 26 100'));

  assert.strictEqual(api.getRemoteMotionStats('peer').renders, 6);
  assert.strictEqual(api.getRemoteMotionStats('peer').interpolated, 3);
}

{
  const { api, timers } = loadApp(bridgeAndWifi);
  const socket = fakeSocket();
  const udpFrames = [];
  api.setStore({ autoConnect: true, hostId: 'local', layout: {} });
  api.setDiscoverySocket({
    send(buffer) {
      udpFrames.push(JSON.parse(buffer.toString('utf8')));
    },
  });
  api.peers.peer = {
    hostId: 'peer',
    nickname: 'peer',
    socket,
    connectedAddress: '192.168.86.21',
    remoteAddress: '192.168.86.21',
    localAddress: '192.168.86.249',
    lastAddress: '192.168.86.21',
    udpReachableAt: Date.now(),
    interfaces: [{ name: 'en0', address: '192.168.86.21' }],
  };
  api.securePeer(api.peers.peer);

  assert.strictEqual(api.beginCoordStream('peer', { x: 0, y: 0 }), true);
  assert.strictEqual(socket.writes.length, 1);
  assert.strictEqual(api.decodeTcpFrame(socket.writes[0], api.peers.peer).seq, 1);
  assert.strictEqual(udpFrames.length, 0);

  assert.strictEqual(api.sendCoordToPeer('peer', { x: 1, y: 1 }), true);
  assert.strictEqual(timers.length, 1);
  assert.strictEqual(timers[0].delay, 8);

  assert.strictEqual(api.sendCoordToPeer('peer', { x: 20, y: 20 }), true);
  assert.strictEqual(timers[0].cancelled, true);
  assert.strictEqual(timers.length, 2);
  assert(timers[1].delay <= 4);
  timers[1].fn();

  assert.strictEqual(socket.writes.length, 1);
  assert.strictEqual(udpFrames.length, 1);
  const udpCoord = api.decodeUdpFrame(udpFrames[0], api.peers.peer);
  assert.strictEqual(udpCoord.x, 20);
  assert.strictEqual(udpCoord.y, 20);
  assert.strictEqual(udpCoord.seq, 3);
  assert.strictEqual(udpFrames[0].protocol, 8);
  assert(Number.isSafeInteger(udpCoord.sampleAt));

  assert.strictEqual(api.beginCoordStream('peer', { x: 100, y: 100 }), true);
  assert.strictEqual(socket.writes.length, 2);
  assert.strictEqual(api.decodeTcpFrame(socket.writes[1], api.peers.peer).seq, 4);
}

{
  const { api, timers } = loadApp(bridgeAndWifi);
  const socket = fakeSocket();
  const udpFrames = [];
  api.setStore({ autoConnect: true, hostId: 'local', layout: {} });
  api.setDiscoverySocket({
    send(buffer) {
      udpFrames.push(JSON.parse(buffer.toString('utf8')));
    },
  });
  api.peers.peer = {
    hostId: 'peer',
    nickname: 'peer',
    socket,
    connectedAddress: '192.168.86.21',
    remoteAddress: '192.168.86.21',
    localAddress: '192.168.86.249',
    lastAddress: '192.168.86.21',
    interfaces: [{ name: 'en0', address: '192.168.86.21' }],
  };
  api.securePeer(api.peers.peer);

  api.beginCoordStream('peer', { x: 0, y: 0 });
  api.sendCoordToPeer('peer', { x: 1, y: 1 });
  timers[0].fn();

  assert.strictEqual(udpFrames.length, 0);
  assert.strictEqual(socket.writes.length, 2);
  assert.strictEqual(api.decodeTcpFrame(socket.writes[1], api.peers.peer).seq, 2);
}

{
  const { api, immediates, nativeMouseWrites } = loadApp(bridgeAndWifi);

  api.remoteMouseMove('peer', 40, 50);
  api.remoteMouseMove('peer', 45, 55);
  assert.strictEqual(immediates.length, 1);
  api.remoteMouseButton('peer', 'down', 'left', 45, 55);

  assert.strictEqual(immediates[0].cancelled, true);
  assert.strictEqual(nativeMouseWrites.length, 2);
  assert(nativeMouseWrites[0].includes('move 45 55'));
  assert(nativeMouseWrites[1].includes('down 45 55'));
}

{
  const { api, immediates, timers, nativeMouseWrites, setNow } = loadApp(bridgeAndWifi);

  setNow(1000);
  api.remoteMouseMove('peer', 0, 50, 1, 5000);
  immediates[0].fn();

  setNow(1020);
  api.remoteMouseMove('peer', 20, 50, 2, 5020);
  setNow(1024);
  timers[0].fn();
  assert(nativeMouseWrites[1].includes('move 10 50'));

  setNow(1025);
  api.remoteMouseButton('peer', 'down', 'left', 20, 50);

  assert.strictEqual(timers[1].cancelled, true);
  assert(nativeMouseWrites[2].includes('move 20 50'));
  assert(nativeMouseWrites[3].includes('down 20 50'));
}

{
  const { api, robotMoves, setRobotPosition } = loadApp(bridgeAndWifi);
  const socket = fakeSocket();
  api.setStore({ autoConnect: true, hostId: 'local', layout: {} });
  api.peers.peer = {
    hostId: 'peer',
    nickname: 'peer',
    socket,
    connectedAddress: '192.168.86.21',
    remoteAddress: '192.168.86.21',
    localAddress: '192.168.86.249',
    lastAddress: '192.168.86.21',
    interfaces: [{ name: 'en0', address: '192.168.86.21' }],
  };
  api.securePeer(api.peers.peer);
  api.setOffscreenPointerState({
    hostId: 'peer',
    edge: 'right',
    bounds: { x: 0, y: 0, width: 1440, height: 900 },
    x: 100,
    y: 100,
  });

  api.handleNativeKeyTapLine('{"type":"pointer","dx":7,"dy":-4}');
  assert.strictEqual(api.getRemotePointer().x, 107);
  assert.strictEqual(api.getRemotePointer().y, 96);
  const coordFrame = socket.writes
    .map(line => api.decodeTcpFrame(line, api.peers.peer))
    .find(frame => frame.type === 'coord');
  assert(coordFrame);
  assert.strictEqual(coordFrame.x, 107);
  assert.strictEqual(coordFrame.y, 96);
  assert.strictEqual(robotMoves.length, 0);

  setRobotPosition(535, 512);
  api.setNativePointerCaptureReady(true);
  api.pollPointer();
  assert.strictEqual(api.getRemotePointer().x, 107);
  assert.strictEqual(api.getRemotePointer().y, 96);
  assert.strictEqual(robotMoves.length, 0);
}

{
  const { api, robotScrolls } = loadApp(bridgeAndWifi);
  const socket = fakeSocket();
  api.setStore({ autoConnect: true, hostId: 'local', layout: {} });
  api.peers.peer = {
    hostId: 'peer',
    nickname: 'peer',
    socket,
  };
  api.securePeer(api.peers.peer);
  api.setOffscreenPointerState({
    hostId: 'peer',
    edge: 'right',
    bounds: { x: 0, y: 0, width: 1440, height: 900 },
    x: 321,
    y: 654,
  });

  api.handleNativeKeyTapLine('{"type":"mouse","action":"down","button":"left"}');
  api.handleNativeKeyTapLine('{"type":"mouse","action":"up","button":"left"}');
  api.handleNativeKeyTapLine('{"type":"scroll","x":-3,"y":7}');

  const frames = socket.writes.map(line => api.decodeTcpFrame(line, api.peers.peer));
  assert.deepStrictEqual(
    frames.map(frame => [frame.type, frame.button, frame.x, frame.y]),
    [
      ['md', 'left', 321, 654],
      ['mu', 'left', 321, 654],
      ['wh', undefined, -3, 7],
    ]
  );

  api.setReceivingPeer('peer');
  api.handleFrame(socket, { type: 'wh', x: -3, y: 7 }, () => {});
  assert.deepStrictEqual(robotScrolls, [{ x: -3, y: 7 }]);
}

console.log('motion pipeline tests passed: native deltas, exclusive remote clicks, no repeated cursor warps, latest-wins receive, stale rejection, monotonic sequencing, 4/8ms adaptive send, 8/12ms speed-aware interpolation, UDP motion, TCP fallback, ordered clicks');
