'use strict';

const express = require('express');
const { LIVE_SIZES } = require('./liveSizes');

// ── Reine Logik (kein Express, kein fs) – Jest-testbar ───────────────────────

// Erlaubte Encode-Modi (Live). copybsf = MJPEG-Copy (Default), mjpeg = Re-Encode,
// h264 = GPU-H.264 (MSE). UI bietet nur copybsf/h264 an, mjpeg bleibt per API/Env.
const ENCODES = ['copybsf', 'mjpeg', 'h264'];

// Validiert den POST-Body. reqCameras: [{ id, liveSize?, stream?, encode? }].
// Liefert { ok, errors[] }. Ein einziger Fehler kippt das Ergebnis → kein
// Teil-Apply (der Aufrufer wendet nur bei ok:true etwas an).
function validateConfig(reqCameras, knownIds, liveSizes = LIVE_SIZES) {
  if (!Array.isArray(reqCameras)) {
    return { ok: false, errors: ['"cameras" muss ein Array sein'] };
  }
  const known = new Set(knownIds);
  const errors = [];
  for (const c of reqCameras) {
    if (!c || typeof c.id !== 'string') {
      errors.push(`Eintrag ohne gültige id: ${JSON.stringify(c)}`);
      continue;
    }
    if (!known.has(c.id)) {
      errors.push(`Unbekannte Kamera: ${c.id}`);
      continue;
    }
    if ('stream' in c && typeof c.stream !== 'boolean') {
      errors.push(`${c.id}: "stream" muss boolean sein`);
    }
    // liveSize darf bei "Aus" fehlen; wenn gesetzt, muss sie erlaubt sein.
    if (c.liveSize != null && !liveSizes.includes(c.liveSize)) {
      errors.push(`${c.id}: ungültige Auflösung "${c.liveSize}" (erlaubt: ${liveSizes.join(', ')})`);
    }
    if (c.encode != null && !ENCODES.includes(c.encode)) {
      errors.push(`${c.id}: ungültiger Encode "${c.encode}" (erlaubt: ${ENCODES.join(', ')})`);
    }
  }
  return { ok: errors.length === 0, errors };
}

// Liefert ein NEUES camerasJson-Objekt: patcht nur liveSize/stream der genannten
// Kameras, lässt alle übrigen Felder (device, name, hiresSize, note …) und nicht
// genannte Kameras unangetastet. Mutiert das Original nicht.
function mergeConfig(camerasJson, reqCameras) {
  const patchById = new Map(reqCameras.map((c) => [c.id, c]));
  return {
    ...camerasJson,
    cameras: camerasJson.cameras.map((cam) => {
      const p = patchById.get(cam.id);
      if (!p) return cam;
      const next = { ...cam };
      if (p.liveSize != null) next.liveSize = p.liveSize;
      if ('stream' in p) next.stream = p.stream;
      if (p.encode != null) next.encode = p.encode;
      return next;
    }),
  };
}

// Aktueller Ist-Zustand für GET /api/config und die POST-Antwort.
// liveSize aus dem Switch (Laufzeit-Wahrheit), name/stream aus camsMeta.
function currentConfig(switches, camsMeta) {
  return {
    liveSizes: LIVE_SIZES,
    encodes: ENCODES,
    cameras: camsMeta.map((m) => ({
      id: m.id,
      name: m.name,
      liveSize: switches[m.id]?.liveSize ?? null,
      stream: m.stream,
      encode: switches[m.id]?.encode ?? m.encode ?? 'copybsf',
    })),
  };
}

// ── Express-Router ───────────────────────────────────────────────────────────
//   GET  /api/config  → { liveSizes, cameras:[{id,name,liveSize,stream}] }
//   POST /api/config  → Body { cameras:[{id,liveSize?,stream}] }
//                       validiert → cameras.json atomar schreiben → Hot-Reload
function createConfigRouter({ switches, camsMeta, getCamerasJson, setCamerasJson, persist, mseCodec = null }) {
  const router = express.Router();

  router.get('/', (_req, res) => {
    res.json(currentConfig(switches, camsMeta));
  });

  router.post('/', async (req, res) => {
    const reqCameras = req.body && req.body.cameras;
    const v = validateConfig(reqCameras, camsMeta.map((c) => c.id));
    if (!v.ok) return res.status(400).json({ error: v.errors.join('; ') });

    // 1. cameras.json in-memory patchen + atomar persistieren (übrige Felder bleiben).
    const merged = mergeConfig(getCamerasJson(), reqCameras);
    try {
      persist(merged);
    } catch (e) {
      return res.status(500).json({ error: `Persistieren fehlgeschlagen: ${e.message}` });
    }
    setCamerasJson(merged);

    // 2. camsMeta nachziehen (Viewer respektiert das beim nächsten Laden).
    for (const c of reqCameras) {
      const meta = camsMeta.find((m) => m.id === c.id);
      if (!meta) continue;
      if ('stream' in c) meta.stream = c.stream;
      if (c.encode != null) {
        meta.encode = c.encode;
        meta.mseCodec = c.encode === 'h264' ? mseCodec : null;
      }
    }

    // 3. Hot-Reload pro genannter Kamera (Auflösung/Encoder sofort aktiv, Aus/An n. Reload).
    await Promise.allSettled(reqCameras.map((c) => {
      const sw = switches[c.id];
      return sw ? sw.reconfigure({ liveSize: c.liveSize, stream: c.stream, encode: c.encode }) : Promise.resolve();
    }));

    res.json(currentConfig(switches, camsMeta));
  });

  return router;
}

module.exports = { validateConfig, mergeConfig, currentConfig, createConfigRouter };
