/* eslint-disable */
const fs = require('fs');
const http = require('http');
const path = require('path');
const express = require('express');
const cors = require('cors');
const morgan = require('morgan');
const WebSocket = require('ws');

// --- Load config ---
const CONFIG_PATH = path.join(__dirname, 'room.config');
function loadConfig() {
  const raw = fs.readFileSync(CONFIG_PATH, 'utf8');
  return JSON.parse(raw);
}
let cfg = loadConfig();

// --- Express HTTP server ---
const app = express();
app.use(express.json({ limit: '256kb' }));
app.use(cors({ origin: true }));
app.use(morgan('dev'));

// Serve three.js from node_modules (import-map)
app.use('/vendor/three', express.static(path.join(__dirname, 'node_modules/three')));

// Serve static viewer from ./public
const publicDir = path.join(__dirname, 'public');
app.use('/', express.static(publicDir, { etag: false, lastModified: false, cacheControl: false }));

// Expose render-relevant config to the browser
app.get('/config', (req, res) => {
  const { sensor, pose } = cfg;
  res.json({ sensor, pose });
});

// Optional: pose updates
app.post('/pose', (req, res) => {
  const { position, rotationEulerDeg, eulerOrder } = req.body || {};
  if (Array.isArray(position) && position.length === 3) cfg.pose.position = position.map(Number);
  if (Array.isArray(rotationEulerDeg) && rotationEulerDeg.length === 3) cfg.pose.rotationEulerDeg = rotationEulerDeg.map(Number);
  if (typeof eulerOrder === 'string') cfg.pose.eulerOrder = eulerOrder;
  broadcastState();
  res.json({ ok: true, pose: cfg.pose });
});

const server = http.createServer(app);
server.on('connection', (socket) => socket.setNoDelay(true));

// --- WS servers for browsers ---
const wssFrames = new WebSocket.Server({ noServer: true });
const wssState = new WebSocket.Server({ noServer: true });

function upgradePath(req) {
  try {
    const u = new URL(req.url, `http://${req.headers.host}`);
    return u.pathname;
  } catch (_) {
    return req.url;
  }
}

server.on('upgrade', (req, socket, head) => {
  const pathname = upgradePath(req);
  console.log('[HTTP] upgrade request', pathname, 'from', req.socket.remoteAddress);
  if (pathname === '/ws/frames') {
    wssFrames.handleUpgrade(req, socket, head, (ws) => {
      ws._isViewer = true;
      ws._name = `viewer:${Math.random().toString(16).slice(2, 8)}`;
      ws._drops = 0;
      ws._socket?.setNoDelay?.(true);
      console.log('[WS frames] connected', ws._name);
      ws.on('close', (code, reason) => {
        console.log('[WS frames] closed', ws._name, code, reason?.toString());
      });
      wssFrames.emit('connection', ws, req);
    });
  } else if (pathname === '/ws/state') {
    wssState.handleUpgrade(req, socket, head, (ws) => {
      ws._name = `state:${Math.random().toString(16).slice(2, 8)}`;
      ws._socket?.setNoDelay?.(true);
      console.log('[WS state] connected', ws._name);
      ws.on('close', (code, reason) => {
        console.log('[WS state] closed', ws._name, code, reason?.toString());
      });
      wssState.emit('connection', ws, req);
      try { ws.send(JSON.stringify({ type: 'pose', pose: cfg.pose })); } catch (_) {}
    });
  } else {
    console.warn('[HTTP] upgrade unknown path:', pathname);
    socket.destroy();
  }
});

wssFrames.on('connection', (ws) => { /* frames -> viewers */ });
wssState.on('connection', (ws) => { ws.on('message', () => {/*reserved*/}); });

function broadcastState() {
  const msg = JSON.stringify({ type: 'pose', pose: cfg.pose });
  for (const client of wssState.clients) {
    if (client.readyState === WebSocket.OPEN) {
      try { client.send(msg); } catch (_) {}
    }
  }
}

// --- Bridge: WS client to ESP32 ---
let espWS = null;
let reconnectTimer = null;

// Counters
let espFramesTotal = 0;
let espFrames10s = 0;
let sent10s = 0;
let dropped10s = 0;

function connectESP() {
  if (espWS && (espWS.readyState === WebSocket.OPEN || espWS.readyState === WebSocket.CONNECTING)) return;
  const url = cfg.esp.wsUrl; // e.g. "ws://10.0.0.55:81/frames"
  console.log(`[ESP] Connecting to ${url} ...`);
  espWS = new WebSocket(url, { perMessageDeflate: false, handshakeTimeout: 4000 });

  espWS.on('open', () => {
    console.log('[ESP] connected');
    try { espWS.send('csv'); } catch (_) {}
  });

  espWS.on('message', (data) => {
    const frame = (typeof data === 'string') ? data : data.toString('utf8');
    espFramesTotal++;
    espFrames10s++;
    broadcastFrame(frame);
  });

  espWS.on('close', (code, reason) => {
    console.log(`[ESP] closed ${code} ${reason}`);
    scheduleReconnect();
  });

  espWS.on('error', (err) => {
    console.warn('[ESP] error', err.message);
  });
}

function scheduleReconnect() {
  if (reconnectTimer) return;
  reconnectTimer = setTimeout(() => { reconnectTimer = null; connectESP(); }, cfg.reconnectMs || 1500);
}

function broadcastFrame(frame) {
  let sentThisFrame = 0;
  for (const client of wssFrames.clients) {
    if (client.readyState !== WebSocket.OPEN) continue;
    const threshold = (cfg.forwarding && cfg.forwarding.clientBufferedThreshold) || 128 * 1024;
    if (client.bufferedAmount > threshold) { client._drops = (client._drops || 0) + 1; continue; }
    try { client.send(frame, { binary: false }); sentThisFrame++; } catch (_) {}
  }
  if (sentThisFrame > 0) {
    sent10s += sentThisFrame; // sum over all viewers for this frame
  } else {
    dropped10s++;
  }
}

// === TESTGENERATOR via Config ===
if (cfg.testGenerator?.enabled) {
  console.log("[TESTGEN] Aktiv – künstliche Testdaten werden erzeugt!");

  const W = cfg.sensor.gridW;
  const H = cfg.sensor.gridH;

  const fps = cfg.testGenerator.fps || 10;
  const minD = cfg.testGenerator.minDistMm || 2000;
  const maxD = cfg.testGenerator.maxDistMm || 2500;
  const noise = cfg.testGenerator.noiseMm || 20;

  let ts = 0;
  const intervalMs = 1000 / fps;

  setInterval(() => {
    const distances = [];

    for (let i = 0; i < W * H; i++) {
      let mm = minD + Math.random() * (maxD - minD);
      mm += (Math.random() - 0.5) * noise * 2;
      distances.push(Math.round(mm));
    }

    const csv = [ts++, W, H, ...distances].join(",");
    broadcastFrame(csv);   // <- exakt gleiche Pipeline wie echte ESP‑Daten
  }, intervalMs);
}



// ---- 10s stats ----
setInterval(() => {
  console.log(`[STAT 10s] fromESP:${espFrames10s} ->toViewers:${sent10s} dropped:${dropped10s} viewers:${wssFrames.clients.size}`);
  espFrames10s = 0;
  sent10s = 0;
  dropped10s = 0;
}, 10_000);

const PORT = Number(process.env.PORT || cfg.server.port || 8060);
const HOST = cfg.server.host || '0.0.0.0';
server.listen(PORT, HOST, () => {
  console.log(`[HTTP] listening on http://${HOST}:${PORT}`);

  if (cfg.testGenerator?.enabled) {
    console.log("[TESTGEN] ESP-Verbindung deaktiviert (Testmodus aktiv).");
  } else {
    connectESP();   // Nur verbinden, wenn Testgenerator ausgeschaltet ist
  }
});
process.on('SIGHUP', () => {
  try { cfg = loadConfig(); console.log('[CFG] reloaded'); } catch (e) { console.warn('[CFG] reload failed', e.message); }
});