#!/usr/bin/env node
/**
 * Minimal DNS server for local test domains -> 127.0.0.1
 * Works with /etc/resolver/<host> (macOS per-domain DNS override).
 */
import dgram from 'node:dgram';
import { DNS_PORT, LOCAL_IP, TEST_HOSTS } from './local-test-config.mjs';

const ip = LOCAL_IP.split('.').map(Number);
const bind = process.env.LOCAL_TEST_DNS_BIND ?? '127.0.0.1';
const hosts = new Set(TEST_HOSTS.flatMap((host) => [host, `${host}.`]));

function encodeName(name) {
  const parts = name.split('.').filter(Boolean);
  return Buffer.concat([
    ...parts.map((part) => Buffer.concat([Buffer.from([part.length]), Buffer.from(part, 'ascii')])),
    Buffer.from([0]),
  ]);
}

function readQuestionName(msg, offset) {
  const labels = [];
  let pos = offset;
  while (pos < msg.length) {
    const len = msg[pos];
    if (len === 0) {
      pos += 1;
      break;
    }
    labels.push(msg.subarray(pos + 1, pos + 1 + len).toString('ascii'));
    pos += 1 + len;
  }
  return { name: labels.join('.'), next: pos };
}

function questionMatches(name) {
  return hosts.has(name);
}

const server = dgram.createSocket('udp4');

server.on('message', (msg, rinfo) => {
  if (msg.length < 12) return;

  const qdCount = msg.readUInt16BE(4);
  if (qdCount !== 1) return;

  const question = readQuestionName(msg, 12);
  const qtype = msg.readUInt16BE(question.next);
  const qclass = msg.readUInt16BE(question.next + 2);
  const questionEnd = question.next + 4;

  if (!questionMatches(question.name) || (qtype !== 1 && qtype !== 28) || qclass !== 1) {
    return;
  }

  const questionSection = msg.subarray(12, questionEnd);
  const ttl = Buffer.from([0x00, 0x00, 0x00, 0x3c]);

  let answer;
  if (qtype === 1) {
    answer = Buffer.concat([
      Buffer.from([0xc0, 0x0c]),
      Buffer.from([0x00, 0x01, 0x00, 0x01]),
      ttl,
      Buffer.from([0x00, 0x04]),
      Buffer.from(ip),
    ]);
  } else {
    answer = Buffer.concat([
      Buffer.from([0xc0, 0x0c]),
      Buffer.from([0x00, 0x1c, 0x00, 0x01]),
      ttl,
      Buffer.from([0x00, 0x10]),
      Buffer.from([
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, ip[0], ip[1], ip[2], ip[3],
      ]),
    ]);
  }

  const header = Buffer.alloc(12);
  header.writeUInt16BE(msg.readUInt16BE(0), 0);
  header.writeUInt16BE(0x8180, 2);
  header.writeUInt16BE(1, 4);
  header.writeUInt16BE(1, 6);
  header.writeUInt16BE(0, 8);
  header.writeUInt16BE(0, 10);

  server.send(Buffer.concat([header, questionSection, answer]), rinfo.port, rinfo.address);
});

server.on('error', (err) => {
  if (err.code === 'EADDRINUSE') {
    console.error(`Local test DNS 端口 ${DNS_PORT} 已被占用`);
    process.exit(1);
  }
  throw err;
});

server.bind(DNS_PORT, bind, () => {
  console.log(`Local test DNS  ${TEST_HOSTS.join(', ')} -> ${LOCAL_IP}  @ ${bind}:${DNS_PORT}`);
});

for (const signal of ['SIGINT', 'SIGTERM']) {
  process.on(signal, () => {
    server.close(() => process.exit(0));
  });
}
