import {
  getActiveBlocklistDomains,
  getUserCustomDomains,
} from "../../db/domains";
import { getProfile } from "../../db/profile";
import { getPlanLimits } from "../../utils/plan-features";
import { buildHashListBinary, etagFor } from "../../utils/domainHash";

/**
 * GET /api/url-filter/blocklist.bin
 *
 * Liefert die Blocklist als sortierte Binary-Hash-Liste (8 Bytes pro Hash,
 * big-endian, sorted ascending). Die iOS-Extension memory-mapped diese Datei
 * und macht binary-search bei jedem Browser-Flow.
 *
 * Privacy:
 *   - Server schickt NUR Hashes, keine Klartext-Domains an die App
 *   - On-Disk in App-Group: nur Hash-Bytes, kein Klartext (forensik-resistent)
 *
 * Plan-aware:
 *   - free:      KEIN global-blocklist, NUR Custom-Domains gehasht
 *   - pro/legend: global HaGeZi-List + Custom-Domains beide gehasht
 *
 * Caching:
 *   - ETag = sha256 des Binary-Inhalts (16 hex chars)
 *   - Cache-Control: private, max-age=300
 *   - Bei If-None-Match: 304 Not Modified
 */
export default defineEventHandler(async (event) => {
  const user = await requireUser(event);

  const [profile, customDomains, globalDomains] = await Promise.all([
    getProfile(user.id),
    getUserCustomDomains(user.id),
    // Global nur lazy laden wenn Plan es erlaubt — spart RAM bei Free-Usern
    Promise.resolve(null),
  ]);

  const limits = getPlanLimits(profile?.plan ?? "free");

  // Grace-Period: wenn globalBlocklistGraceUntil noch in der Zukunft liegt,
  // behandeln wir den User als 'full' auch wenn sein Plan 'curated' sagt.
  const inGrace =
    profile?.globalBlocklistGraceUntil != null &&
    new Date(profile.globalBlocklistGraceUntil) > new Date();
  const useFullBlocklist = limits.globalBlocklist === "full" || inGrace;

  // Global Domains nur für Pro/Legend (oder während Grace-Period)
  const global = useFullBlocklist ? await getActiveBlocklistDomains() : [];

  // Beide Listen ohne Salt hashen — vereinfachte Architektur:
  // Server kennt die Klartext-Domains eh (via DB), darum bringt User-Salt
  // praktisch keinen Privacy-Vorteil. Vorteil: Extension kann mit einer
  // Hash-Funktion alle Hosts prüfen.
  const customBuf = buildHashListBinary(customDomains.map((d) => d.domain));
  const globalBuf = buildHashListBinary(global.map((d) => d.domain));

  // Merged sorted binary (Extension macht eine binary-search über alles).
  // Wichtig: doppelt sortieren weil Custom + Global zwei separate sorted
  // arrays sind — wir mergen sie zu einem sorted array.
  const combined = mergeSortedHashBuffers(customBuf, globalBuf);

  const etag = etagFor(combined);

  // Conditional GET — bei If-None-Match returns 304
  const ifNoneMatch = getHeader(event, "if-none-match");
  if (ifNoneMatch === etag) {
    setResponseStatus(event, 304);
    setResponseHeader(event, "etag", etag);
    return null;
  }

  setResponseHeader(event, "content-type", "application/octet-stream");
  setResponseHeader(event, "etag", etag);
  setResponseHeader(event, "cache-control", "private, max-age=300");
  setResponseHeader(event, "x-rebreak-count", String(combined.length / 8));
  setResponseHeader(event, "x-rebreak-plan", profile?.plan ?? "free");

  return combined;
});

/**
 * Merged zwei sortierte 8-byte-hash-Buffer in ein sortiertes Output.
 * In-place merge sort.
 */
function mergeSortedHashBuffers(a: Buffer, b: Buffer): Buffer {
  const result = Buffer.alloc(a.length + b.length);
  let ai = 0;
  let bi = 0;
  let ri = 0;

  while (ai < a.length && bi < b.length) {
    const av = a.readBigUInt64BE(ai);
    const bv = b.readBigUInt64BE(bi);
    if (av <= bv) {
      result.writeBigUInt64BE(av, ri);
      ai += 8;
    } else {
      result.writeBigUInt64BE(bv, ri);
      bi += 8;
    }
    ri += 8;
  }
  while (ai < a.length) {
    result.writeBigUInt64BE(a.readBigUInt64BE(ai), ri);
    ai += 8;
    ri += 8;
  }
  while (bi < b.length) {
    result.writeBigUInt64BE(b.readBigUInt64BE(bi), ri);
    bi += 8;
    ri += 8;
  }

  return result.subarray(0, ri);
}
