"use strict";
// Rutas propias de la rama experimental (modulo Karvan: salas efimeras en RAM,
// mensajes temporales y senalizacion WebRTC).
//
// Viven fuera de backend.js a proposito: ese fichero concentra las 577 rutas de
// upstream en una sola cadena fluida y se mueve +905/-298 lineas por release, asi
// que cualquier ruta anadida en medio entra en conflicto en cada integracion.
// Aqui se montan en un router propio que se inserta en la cadena de middleware
// antes del router de upstream; lo que no casa cae a next() y sigue su curso.
//
// Las dependencias se inyectan en lugar de reconstruirse: cooler abre la conexion
// al sbot y duplicarla abriria un segundo cliente muxrpc.

const koaRouter = require("../server/node_modules/@koa/router");
const { koaBody } = require("../server/node_modules/koa-body");
const { stripDangerousTags } = require("./sanitizeHtml");
const { buildIceServers } = require("./turnCredentials");
const karvanModel = require('../models/karvan_model')({}); // FORK_IA_UX: salas efímeras en RAM (mensajes temporales + señalización WebRTC)
const { karvanView, karvanRoomView } = require("../views/karvan_view");

module.exports = ({ cooler, pull, pmModel, checkMod, getViewerId,
                    getConfig, saveConfig, legacyModel, onboardingModel,
                    isLoopbackRequest, sendErrorPage }) => {
// FORK_IA_UX Fase 2 (cross-device): relay de MENSAJES de Karvan por privados SSB. Funciona vía un pub
// (el privado se replica al otro móvil); los SIGNAL de WebRTC NO se relayan (serían demasiados/lentos por el log).
// No toca el arranque del SSB. Ingesta por stream EN VIVO (no polling) → eficiente.
const karvanSeenRelay = new Set();     // mids ya inyectados (evita duplicados/bucles)
let karvanRelaySub = false;            // ¿suscriptor del log en vivo montado?
async function karvanPublishRelay(roomId, mid, from, text) {
  try {
    const feeds = karvanModel.getRemoteFeeds(roomId);
    if (!feeds.length) return;
    const ssbClient = await cooler.open();
    const recps = [...new Set([ssbClient.id, ...feeds])].slice(0, 8);
    await new Promise((res, rej) => ssbClient.private.publish(
      { type: 'karvan-relay', roomId, mid: String(mid || '').slice(0, 60), from: String(from || '?').slice(0, 80), text: String(text || '').slice(0, 2000), private: true },
      recps, (e, m) => e ? rej(e) : res(m)));
  } catch (e) {}
}
async function karvanStartRelay() {
  if (karvanRelaySub) return;
  karvanRelaySub = true;
  try {
    const ssbClient = await cooler.open();
    const me = ssbClient.id;
    pull(
      ssbClient.createLogStream({ old: false, live: true }),
      pull.drain((m) => {
        try {
          if (!m || !m.value) return;
          let dec; try { dec = ssbClient.private.unbox({ key: m.key, value: m.value, timestamp: m.timestamp }); } catch (_) { return; }
          const c = dec && dec.value && dec.value.content;
          if (!c || c.type !== 'karvan-relay' || !c.roomId || !c.mid) return;
          if (dec.value.author === me) return;               // no reinyectar lo propio (evita bucle A→B→A)
          if (karvanSeenRelay.has(c.mid)) return;
          if (!karvanModel.getRoom(c.roomId)) return;        // solo si tenemos esa sala localmente
          karvanSeenRelay.add(c.mid);
          if (karvanSeenRelay.size > 5000) karvanSeenRelay.clear();
          karvanModel.postMessage(c.roomId, { from: c.from, text: c.text, mid: c.mid });
        } catch (e) {}
      })
    );
  } catch (e) { karvanRelaySub = false; }
}
// ¿quién me invitó a esta sala? (para registrar su feed en el espejo local) — reutiliza el lector de privados
async function karvanInviteAuthor(roomId) {
  try {
    const msgs = await pmModel.listAllPrivate();
    for (const m of (msgs || [])) {
      const c = m && m.value && m.value.content;
      if (c && c.subject === 'KARVAN_INVITE' && typeof c.text === 'string' && c.text.indexOf('/karvan/' + roomId) !== -1) return m.value.author;
    }
  } catch (e) {}
  return null;
}

  const forkRouter = new koaRouter();
  forkRouter
  // Configuracion ICE para las llamadas. Solo loopback: la credencial TURN va
  // firmada con el secreto del nodo y no debe salir de la maquina.
  .get('/karvan/ice', async (ctx) => {
    if (!checkMod(ctx, 'karvanMod')) { ctx.status = 403; ctx.body = { error: 'off' }; return; }
    if (!isLoopbackRequest(ctx)) { ctx.status = 403; ctx.body = { error: 'forbidden' }; return; }
    let who = ''; try { who = getViewerId() || ''; } catch (e) {}
    let rtc = null; try { rtc = (getConfig() || {}).rtc || null; } catch (e) {}
    ctx.body = buildIceServers(rtc, who);
  })
  // ===== FORK_IA_UX: módulo KARVAN (mensajes temporales RAM + WebRTC data-channel) =====
  .get('/karvan', async (ctx) => {
    if (!checkMod(ctx, 'karvanMod')) { ctx.redirect('/modules'); return; }
    let selfId = ''; try { selfId = getViewerId(); } catch (e) {}
    ctx.body = karvanView(karvanModel.listRooms(), { selfId });
  })
  .post('/karvan/create', koaBody(), async (ctx) => {
    if (!checkMod(ctx, 'karvanMod')) { ctx.redirect('/modules'); return; }
    const b = ctx.request.body || {};
    const room = karvanModel.createRoom({
      title: stripDangerousTags(String(b.title || '').trim()).slice(0, 80),
      ttlMinutes: b.ttl,
    });
    ctx.redirect('/karvan/' + room.id);
  })
  .get('/karvan/:id', async (ctx) => {
    if (!checkMod(ctx, 'karvanMod')) { ctx.redirect('/modules'); return; }
    let room = karvanModel.getRoom(ctx.params.id);
    // unirse por enlace/invitación crea un espejo local, pero SOLO en una navegación real (documento):
    // un <img>/subrecurso de una web maliciosa NO debe poder crear/expulsar salas por GET (CSRF). El navegador
    // controla sec-fetch-dest/accept, así que son señales fiables.
    let justAdopted = false;
    if (!room) {
      const isNav = ctx.get('sec-fetch-dest') === 'document' || (ctx.get('accept') || '').includes('text/html');
      if (isNav) { room = karvanModel.adoptRoom({ id: ctx.params.id }); justAdopted = !!room; }
    }
    if (!room) { ctx.redirect('/karvan'); return; }
    if (justAdopted) { // al unirte por enlace, registra el feed de quien te invitó → relay cross-device
      const author = await karvanInviteAuthor(ctx.params.id);
      if (author) { karvanModel.addRemoteFeed(ctx.params.id, author); karvanStartRelay(); }
    }
    let selfId = ''; try { selfId = getViewerId(); } catch (e) {}
    const msgs = karvanModel.listMessages(room.id, 0) || [];
    ctx.body = karvanRoomView(room, msgs, { selfId, invite: ctx.query.invite || '' });
  })
  .post('/karvan/:id/invite', koaBody(), async (ctx) => {
    if (!checkMod(ctx, 'karvanMod')) { ctx.status = 403; ctx.body = { error: 'off' }; return; }
    const id = ctx.params.id;
    const room = karvanModel.getRoom(id);
    if (!room) { ctx.redirect('/karvan'); return; }
    const to = String((ctx.request.body || {}).to || '').trim();
    if (!/^@[A-Za-z0-9+/=]{20,}\.ed25519$/.test(to)) { ctx.redirect('/karvan/' + id + '?invite=bad'); return; }
    try {
      // reutiliza el patrón de invitación por mensaje privado de Oasis (como el módulo industry):
      // le llega al INBOX del contacto con un enlace a la sala. Sin código SSB nuevo.
      await pmModel.sendMessage([to], 'KARVAN_INVITE',
        'You have been invited to an ephemeral Karvan room' + (room.title ? ' ("' + room.title + '")' : '') + ' -> /karvan/' + id);
      karvanModel.addRemoteFeed(id, to); karvanStartRelay(); // relay cross-device hacia el invitado
      ctx.redirect('/karvan/' + id + '?invite=ok');
    } catch (e) { ctx.redirect('/karvan/' + id + '?invite=fail'); }
  })
  .post('/karvan/:id/msg', koaBody(), async (ctx) => {
    if (!checkMod(ctx, 'karvanMod')) { ctx.status = 403; ctx.body = { error: 'off' }; return; }
    const b = ctx.request.body || {};
    let selfId = ''; try { selfId = getViewerId(); } catch (e) {}
    const msg = karvanModel.postMessage(ctx.params.id, {
      from: String(b.from || selfId || '?').slice(0, 80),
      text: stripDangerousTags(String(b.text || '')).slice(0, 2000),
      mid: b.mid,
    });
    if (!msg) { ctx.status = 404; ctx.body = { error: 'no room' }; return; }
    karvanPublishRelay(ctx.params.id, msg.mid || (msg.from + '-' + msg.ts + '-' + msg.seq), msg.from, msg.text); // → feeds remotos (si hay)
    const wantsJson = (ctx.get('accept') || '').indexOf('application/json') !== -1
      || (ctx.get('content-type') || '').indexOf('application/json') !== -1;
    if (wantsJson) ctx.body = { ok: true, seq: msg.seq };
    else ctx.redirect('/karvan/' + ctx.params.id);
  })
  .get('/karvan/:id/msgs', async (ctx) => {
    if (!checkMod(ctx, 'karvanMod')) { ctx.status = 403; ctx.body = { error: 'off' }; return; }
    const msgs = karvanModel.listMessages(ctx.params.id, ctx.query.since || 0);
    ctx.body = { messages: msgs || [] };
  })
  .post('/karvan/:id/signal', koaBody(), async (ctx) => {
    if (!checkMod(ctx, 'karvanMod')) { ctx.status = 403; ctx.body = { error: 'off' }; return; }
    const b = ctx.request.body || {};
    karvanModel.postSignal(ctx.params.id, { from: b.from, to: b.to, payload: b.payload });
    ctx.body = { ok: true };
  })
  .get('/karvan/:id/signal', async (ctx) => {
    if (!checkMod(ctx, 'karvanMod')) { ctx.status = 403; ctx.body = { error: 'off' }; return; }
    const r = karvanModel.getSignals(ctx.params.id, { forId: ctx.query.for || '', since: ctx.query.since || 0 });
    ctx.body = r || { signals: [], members: [] };
  })
  .get("/settings/bottombar", async (ctx) => {
    ctx.body = require("../views/main_views").bottomBarPickerView();
  })
  .post("/settings/bottombar", koaBody(), async (ctx) => {
    const cfg = getConfig();
    const MAX = 4;
    const body = ctx.request.body || {};
    const action = String(body.action || '').trim();
    const key = String(body.key || '').trim();
    const catalog = require("../views/main_views").PINNABLE_MODULES || [];
    let pins = Array.isArray(cfg.bottomBarPins) ? cfg.bottomBarPins.slice() : [];
    if (action === 'add') {
      if (key && catalog.some(m => m.key === key) && !pins.includes(key) && pins.length < MAX) pins.push(key);
    } else if (action === 'remove') {
      pins = pins.filter(k => k !== key);
    } else if (action === 'clear') {
      pins = [];
    }
    cfg.bottomBarPins = pins.slice(0, MAX);
    saveConfig(cfg);
    ctx.redirect('/settings/bottombar');
  })
  .post("/settings/identity/use", koaBody(), async (ctx) => {
    // solo anota cual sera la del proximo arranque: cambiarla en caliente dejaria
    // media aplicacion hablando con el sbot anterior
    const accounts = require('./accounts.js');
    const name = String((ctx.request.body || {}).account || '').trim();
    if (!accounts.isValid(name) || !accounts.list().includes(name)) {
      sendErrorPage(ctx, require('../views/main_views').i18n.identitiesInvalidName, { status: 400 });
      return;
    }
    accounts.setActive(name);
    ctx.redirect('/settings');
  })
  .post("/settings/identity/create", koaBody(), async (ctx) => {
    const accounts = require('./accounts.js');
    const name = String((ctx.request.body || {}).account || '').trim();
    if (!accounts.isValid(name)) {
      sendErrorPage(ctx, require('../views/main_views').i18n.identitiesInvalidName, { status: 400 });
      return;
    }
    // no se genera el secret aqui: lo crea ssb-config al arrancar con esa cuenta,
    // que es quien sabe como hacerlo. Basta con anotarla y reiniciar.
    accounts.setActive(name);
    ctx.redirect('/settings');
  })
  .get('/legacy/export', async (ctx) => {
    if (!isLoopbackRequest(ctx)) { ctx.status = 403; ctx.body = ''; return; }
    const pw = ctx.query && ctx.query.password;
    if (!pw || pw.length < 32) return ctx.redirect('/legacy');
    try {
      const { filename, data } = await legacyModel.exportData({ password: pw });
      ctx.set('Content-Type', 'application/octet-stream');
      ctx.set('Content-Disposition', `attachment; filename="${filename}"`);
      ctx.set('Content-Length', String(data.length));
      ctx.body = data;
      try { onboardingModel.markStep('backup'); } catch (_) {}
    } catch (error) { sendErrorPage(ctx, error.message); }
  });

  return forkRouter.routes();
};
