// Library export/import — bulk JSON dump + restore of sessions, folder
// meta, and subscriptions. Only routes; no module state. Pulls history
// data from history.js helpers; reads/writes the subscriptions file
// directly because library import predates the subscriptions module
// having a public 'merge' helper (and the merge logic is library-
// specific anyway).
//
// As of 0.2.77 (multi-tenant): all reads/writes are scoped to the
// requesting user via scopeForRequest(req). In single mode the scope
// is always "owner" — preserved single-user behavior. In multi mode
// each tenant exports/imports their own library; the operator's
// admin status doesn't grant cross-user export (a separate operator-
// only "export everyone's library" endpoint can be added later if
// the operator ever needs it).
//
// Subscriptions remain global (one /data/history/subscriptions.json
// per install) for now. Per-user subscriptions are a Phase 1D/2 task.

import fs from "fs/promises";
import path from "path";
import express from "express";
import {
  getHistoryDir,
  getScopeHistoryDir,
  loadMeta,
  saveMeta,
  scopeForRequest,
  ROOT_SIDECARS,
} from "./history.js";

// ── Routes ──────────────────────────────────────────────────────────────────
// Both routes are gated by the Pro 'library' entitlement (the gate runs
// upstream in license-middleware.js). They assume initHistory() has
// already been called.
export function setupLibraryRoutes(app) {
  function requireScope(req, res) {
    try {
      return scopeForRequest(req);
    } catch {
      res.status(401).json({ error: "auth_required" });
      return null;
    }
  }

  // Bulk export everything: meta, sessions, subscriptions.
  app.get("/api/library/export", async (req, res) => {
    const scope = requireScope(req, res);
    if (!scope) return;
    try {
      const scopeDir = getScopeHistoryDir(scope);
      const meta = await loadMeta(scope);
      let files = [];
      try {
        files = await fs.readdir(scopeDir);
      } catch {
        files = [];
      }
      const sessions = {};
      for (const file of files) {
        // Skip non-sessions: meta + the subscription sidecar files that now
        // live inside the scope dir (else they'd export as phantom sessions).
        if (!file.endsWith(".json") || ROOT_SIDECARS.has(file)) continue;
        try {
          const raw = await fs.readFile(path.join(scopeDir, file), "utf-8");
          const id = file.replace(".json", "");
          sessions[id] = JSON.parse(raw);
        } catch {}
      }
      // Subscriptions live at the install-wide history root and are
      // operator-owned (single global store). Only the operator exports
      // them — in single mode (the operator owns the box) or, in multi
      // mode, the admin. A non-admin tenant's export must NOT leak the
      // operator's subscription list. Future per-user subscriptions move
      // this into the scope dir.
      let subscriptions = [];
      const ownsSubscriptions =
        req.recapMode !== "multi" || !!(req.user && req.user.is_admin);
      if (ownsSubscriptions) {
        try {
          subscriptions =
            JSON.parse(
              await fs.readFile(
                path.join(getHistoryDir(), "subscriptions.json"),
                "utf-8",
              ),
            ).subscriptions || [];
        } catch {}
      }

      const exportData = {
        version: 1,
        exportedAt: new Date().toISOString(),
        meta,
        sessions,
        subscriptions,
      };
      res.setHeader("Content-Type", "application/json");
      res.setHeader(
        "Content-Disposition",
        'attachment; filename="recap-library.json"',
      );
      res.json(exportData);
    } catch (err) {
      res.status(500).json({ error: err.message });
    }
  });

  // Bulk import: skip sessions that already exist (don't overwrite),
  // merge meta folders, add net-new subscriptions.
  app.post(
    "/api/library/import",
    express.json({ limit: "200mb" }),
    async (req, res) => {
      const scope = requireScope(req, res);
      if (!scope) return;
      try {
        const data = req.body;
        if (!data || !data.sessions) {
          return res
            .status(400)
            .json({ error: "Invalid library file — missing sessions data" });
        }

        const scopeDir = getScopeHistoryDir(scope);
        await fs.mkdir(scopeDir, { recursive: true }).catch(() => {});

        let imported = 0;
        let skipped = 0;

        // Sessions — skip if already present.
        for (const [id, session] of Object.entries(data.sessions)) {
          const filePath = path.join(scopeDir, `${id}.json`);
          try {
            await fs.access(filePath);
            skipped++;
            continue;
          } catch {}
          await fs.writeFile(filePath, JSON.stringify(session));
          imported++;
        }

        // Meta — merge folders + add new uncategorized at the top.
        if (data.meta) {
          const existingMeta = await loadMeta(scope);
          const allExistingIds = new Set([
            ...existingMeta.uncategorized,
            ...existingMeta.folders.flatMap((f) => f.items),
          ]);

          if (data.meta.folders) {
            for (const folder of data.meta.folders) {
              const existingFolder = existingMeta.folders.find(
                (f) => f.id === folder.id,
              );
              if (!existingFolder) {
                existingMeta.folders.push(folder);
                folder.items.forEach((id) => allExistingIds.add(id));
              }
            }
          }

          if (data.meta.uncategorized) {
            for (const id of data.meta.uncategorized) {
              if (!allExistingIds.has(id)) {
                existingMeta.uncategorized.unshift(id);
              }
            }
          }

          await saveMeta(scope, existingMeta);
        }

        // Subscriptions — install-wide + operator-owned. Only the operator
        // (single mode, or multi-mode admin) may import them; otherwise a
        // tenant's import would inject into the operator's global list.
        const ownsSubscriptions =
          req.recapMode !== "multi" || !!(req.user && req.user.is_admin);
        if (ownsSubscriptions && data.subscriptions && data.subscriptions.length > 0) {
          const subsPath = path.join(getHistoryDir(), "subscriptions.json");
          let existingSubs = [];
          try {
            existingSubs =
              JSON.parse(await fs.readFile(subsPath, "utf-8"))
                .subscriptions || [];
          } catch {}
          const existingUrls = new Set(existingSubs.map((s) => s.url));
          for (const sub of data.subscriptions) {
            if (!existingUrls.has(sub.url)) {
              existingSubs.push(sub);
            }
          }
          await fs.writeFile(
            subsPath,
            JSON.stringify({ subscriptions: existingSubs }),
          );
        }

        res.json({
          ok: true,
          imported,
          skipped,
          total: Object.keys(data.sessions).length,
        });
      } catch (err) {
        res.status(500).json({ error: err.message });
      }
    },
  );
}
