#!/usr/bin/env node
/**
 * Unified HTTPS reverse proxy for local test domains.
 *
 *   test.tkmind.cn     -> Vite :5173   (H5 + HMR)
 *   testadm.tkmind.cn  -> admin :8082
 *   testpla.tkmind.cn  -> Plaza :3001
 *   testops.tkmind.cn  -> Ops   :3002
 *
 * Requires root on macOS for :443:
 *   sudo pnpm dev:local-proxy
 */
import http from 'node:http';
import https from 'node:https';
import fs from 'node:fs';
import {
  ADMIN_HOST,
  H5_HOST,
  OPS_HOST,
  PLAZA_HOST,
  PUBLIC_PORT,
  PUBLIC_SCHEME,
  publicUrl,
} from './local-test-config.mjs';
import { ensureLocalTestTls } from './local-test-tls.mjs';

const listenHost = process.env.LOCAL_TEST_PROXY_BIND ?? '0.0.0.0';
const targetHost = process.env.LOCAL_TEST_PROXY_TARGET ?? '127.0.0.1';
const vitePort = Number(process.env.VITE_PORT ?? 5173);
const adminPort = Number(process.env.ADMIN_PORT ?? 8082);
const plazaPort = Number(process.env.PLAZA_PORT ?? 3001);
const opsPort = Number(process.env.OPS_PORT ?? 3002);
const useHttps = PUBLIC_SCHEME === 'https';

const ROUTES = {
  [H5_HOST.toLowerCase()]: { port: vitePort },
  [ADMIN_HOST.toLowerCase()]: { port: adminPort },
  [PLAZA_HOST.toLowerCase()]: { port: plazaPort, rewriteHost: true },
  [OPS_HOST.toLowerCase()]: { port: opsPort, rewriteHost: true },
};

function routeFor(req) {
  const hostname = String(req.headers.host ?? '').split(':')[0].toLowerCase();
  return ROUTES[hostname] ? { hostname, ...ROUTES[hostname] } : null;
}

function proxyRequest(clientReq, clientRes) {
  const route = routeFor(clientReq);
  if (!route) {
    clientRes.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
    clientRes.end(`Unknown host: ${clientReq.headers.host ?? '(missing)'}`);
    return;
  }

  const headers = { ...clientReq.headers };
  if (route.rewriteHost) {
    headers.host = `${route.hostname}:${route.port}`;
  }

  const proxyReq = http.request(
    {
      hostname: targetHost,
      port: route.port,
      method: clientReq.method,
      path: clientReq.url,
      headers,
    },
    (proxyRes) => {
      clientRes.writeHead(proxyRes.statusCode ?? 502, proxyRes.headers);
      proxyRes.pipe(clientRes);
    },
  );

  proxyReq.on('error', (err) => {
    if (!clientRes.headersSent) {
      clientRes.writeHead(502, { 'Content-Type': 'text/plain; charset=utf-8' });
    }
    clientRes.end(
      `Upstream unavailable (${route.hostname} -> ${targetHost}:${route.port}): ${err.message}`,
    );
  });

  clientReq.pipe(proxyReq);
}

function proxyUpgrade(clientReq, clientSocket, head) {
  const route = routeFor(clientReq);
  if (!route) {
    clientSocket.destroy();
    return;
  }

  const headers = { ...clientReq.headers };
  if (route.rewriteHost) {
    headers.host = `${route.hostname}:${route.port}`;
  }

  const proxyReq = http.request({
    hostname: targetHost,
    port: route.port,
    method: clientReq.method,
    path: clientReq.url,
    headers,
  });

  proxyReq.on('upgrade', (proxyRes, proxySocket, proxyHead) => {
    clientSocket.write(
      `HTTP/1.1 ${proxyRes.statusCode ?? 101} ${proxyRes.statusMessage ?? 'Switching Protocols'}\r\n` +
        Object.entries(proxyRes.headers)
          .filter(([, value]) => value != null)
          .map(([key, value]) => `${key}: ${value}`)
          .join('\r\n') +
        '\r\n\r\n',
    );
    if (proxyHead.length) proxySocket.write(proxyHead);
    proxySocket.pipe(clientSocket);
    clientSocket.pipe(proxySocket);
  });

  proxyReq.on('error', () => clientSocket.destroy());
  proxyReq.end(head);
}

function bindErrorHandler(server) {
  server.on('error', (err) => {
    if (err.code === 'EACCES') {
      console.error(`无法监听 ${PUBLIC_PORT} 端口。若使用 443 需 sudo；默认 8443 无需 root。`);
      process.exit(1);
    }
    if (err.code === 'EADDRINUSE') {
      console.error(`端口 ${PUBLIC_PORT} 已被占用。`);
      process.exit(1);
    }
    throw err;
  });
}

let server;
if (useHttps) {
  const { keyPath, certPath } = ensureLocalTestTls();
  server = https.createServer(
    {
      key: fs.readFileSync(keyPath),
      cert: fs.readFileSync(certPath),
    },
    proxyRequest,
  );
} else {
  server = http.createServer(proxyRequest);
}

server.on('upgrade', proxyUpgrade);
bindErrorHandler(server);

server.listen(PUBLIC_PORT, listenHost, () => {
  const scheme = useHttps ? 'https' : 'http';
  console.log(`Local test proxy (${scheme}:${PUBLIC_PORT})`);
  console.log(`  ${publicUrl(H5_HOST)}           ->  :${vitePort}  (H5)`);
  console.log(`  ${publicUrl(ADMIN_HOST)}        ->  :${adminPort}  (memind_adm)`);
  console.log(`  ${publicUrl(PLAZA_HOST)}/plaza  ->  :${plazaPort}  (Plaza)`);
  console.log(`  ${publicUrl(OPS_HOST)}/ops/     ->  :${opsPort}  (Ops)`);
});

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