import assert from "node:assert/strict";

import {
  BME_SYNC_DEVICE_ID_KEY,
  BME_SYNC_UPLOAD_DEBOUNCE_MS,
  __testOnlyDecodeBase64Utf8,
  autoSyncOnChatChange,
  autoSyncOnVisibility,
  backupToServer,
  buildRestoreSafetyChatId,
  deleteRemoteSyncFile,
  deleteServerBackup,
  getRestoreSafetySnapshotStatus,
  getOrCreateDeviceId,
  getRemoteStatus,
  download,
  listServerBackups,
  mergeSnapshots,
  rollbackFromRestoreSafetySnapshot,
  restoreFromServer,
  scheduleUpload,
  syncNow,
  upload,
} from "../sync/bme-sync.js";
import { MANUAL_BACKUP_BATCH_JOURNAL_COVERAGE_KEY } from "../runtime/runtime-state.js";

const PREFIX = "[ST-BME][indexeddb-sync]";

class MemoryStorage {
  constructor() {
    this.map = new Map();
  }

  getItem(key) {
    return this.map.has(key) ? this.map.get(key) : null;
  }

  setItem(key, value) {
    this.map.set(String(key), String(value));
  }

  removeItem(key) {
    this.map.delete(String(key));
  }
}

class FakeDb {
  constructor(chatId, snapshot = null) {
    this.chatId = chatId;
    this.snapshot = snapshot || {
      meta: {
        schemaVersion: 1,
        chatId,
        deviceId: "",
        revision: 0,
        lastModified: Date.now(),
        nodeCount: 0,
        edgeCount: 0,
        tombstoneCount: 0,
      },
      nodes: [],
      edges: [],
      tombstones: [],
      state: {
        lastProcessedFloor: -1,
        extractionCount: 0,
      },
    };
    this.meta = new Map([
      ["syncDirty", false],
      ["syncDirtyReason", ""],
      ["lastSyncedRevision", 0],
      ["deviceId", ""],
    ]);
    this.lastImportPayload = null;
    this.lastImportOptions = null;
  }

  async exportSnapshot() {
    return JSON.parse(JSON.stringify(this.snapshot));
  }

  async importSnapshot(snapshot, options = {}) {
    this.lastImportPayload = JSON.parse(JSON.stringify(snapshot));
    this.lastImportOptions = { ...options };
    this.snapshot = JSON.parse(JSON.stringify(snapshot));
    return {
      mode: options.mode || "replace",
      revision: snapshot?.meta?.revision || 0,
      imported: {
        nodes: Array.isArray(snapshot?.nodes) ? snapshot.nodes.length : 0,
        edges: Array.isArray(snapshot?.edges) ? snapshot.edges.length : 0,
        tombstones: Array.isArray(snapshot?.tombstones) ? snapshot.tombstones.length : 0,
      },
    };
  }

  async getMeta(key, fallback = null) {
    return this.meta.has(key) ? this.meta.get(key) : fallback;
  }

  async patchMeta(record = {}) {
    for (const [key, value] of Object.entries(record)) {
      this.meta.set(key, value);
    }
  }

  async setMeta(key, value) {
    this.meta.set(key, value);
  }
}

function createJsonResponse(status, body) {
  return {
    ok: status >= 200 && status < 300,
    status,
    statusText: String(status),
    async json() {
      return JSON.parse(JSON.stringify(body));
    },
    async text() {
      return typeof body === "string" ? body : JSON.stringify(body);
    },
  };
}

function createMockFetchEnvironment() {
  const remoteFiles = new Map();
  const logs = {
    sanitizeCalls: 0,
    getCalls: 0,
    uploadCalls: 0,
    uploadChunkCalls: 0,
    deleteCalls: 0,
    uploadedPayloads: [],
    uploadedChunkPayloads: [],
  };

  const fetch = async (url, options = {}) => {
    const method = String(options?.method || "GET").toUpperCase();

    if (url === "/api/files/sanitize-filename" && method === "POST") {
      logs.sanitizeCalls += 1;
      const body = JSON.parse(String(options.body || "{}"));
      const sanitized = String(body.fileName || "")
        .replace(/[<>:"/\\|?*\x00-\x1F]/g, "_")
        .replace(/\s+/g, "_");
      return createJsonResponse(200, { fileName: sanitized });
    }

    if (url === "/api/files/upload" && method === "POST") {
      const body = JSON.parse(String(options.body || "{}"));
      if (!/^[A-Za-z0-9._~-]+$/.test(String(body.name || ""))) {
        return createJsonResponse(
          400,
          "Illegal character in filename; only alphanumeric, '-', '_', '.', '~' are accepted.",
        );
      }
      const decoded = __testOnlyDecodeBase64Utf8(body.data);
      const payload = JSON.parse(decoded);
      remoteFiles.set(body.name, payload);
      const targetLog = String(body.name || "").includes(".__")
        ? "uploadedChunkPayloads"
        : "uploadedPayloads";
      if (targetLog === "uploadedChunkPayloads") {
        logs.uploadChunkCalls += 1;
      } else {
        logs.uploadCalls += 1;
      }
      logs[targetLog].push({
        name: body.name,
        decoded,
        payload,
      });
      return createJsonResponse(200, { path: `/user/files/${body.name}` });
    }

    if (url === "/api/files/delete" && method === "POST") {
      logs.deleteCalls += 1;
      const body = JSON.parse(String(options.body || "{}"));
      const name = String(body.path || "").replace("/user/files/", "");
      if (!remoteFiles.has(name)) return createJsonResponse(404, "not found");
      remoteFiles.delete(name);
      return createJsonResponse(200, {});
    }

    if (String(url).startsWith("/user/files/") && method === "GET") {
      logs.getCalls += 1;
      const withoutQuery = String(url).split("?")[0];
      const fileName = decodeURIComponent(withoutQuery.slice("/user/files/".length));
      if (!remoteFiles.has(fileName)) {
        return createJsonResponse(404, "not found");
      }
      return createJsonResponse(200, remoteFiles.get(fileName));
    }

    return createJsonResponse(404, "unsupported route");
  };

  return {
    fetch,
    remoteFiles,
    logs,
  };
}

function buildRuntimeOptions({ dbByChatId, fetch }) {
  return {
    fetch,
    getDb: async (chatId) => {
      const db = dbByChatId.get(chatId);
      if (!db) throw new Error(`missing db: ${chatId}`);
      return db;
    },
    getRequestHeaders: () => ({
      "X-Test": "1",
    }),
    disableRemoteSanitize: false,
  };
}

async function sleep(ms) {
  await new Promise((resolve) => setTimeout(resolve, ms));
}

function createVisibilityMockDocument(initialVisibilityState = "visible") {
  const listeners = new Map();
  const document = {
    visibilityState: initialVisibilityState,
    addEventListener(eventName, handler) {
      listeners.set(String(eventName), handler);
    },
  };

  return {
    document,
    emitVisibilityChange(nextVisibilityState) {
      document.visibilityState = nextVisibilityState;
      const handler = listeners.get("visibilitychange");
      if (typeof handler === "function") {
        handler();
      }
    },
    getListener(eventName) {
      return listeners.get(String(eventName));
    },
  };
}

async function testDeviceId() {
  const storage = new MemoryStorage();
  globalThis.localStorage = storage;

  const first = getOrCreateDeviceId();
  const second = getOrCreateDeviceId();

  assert.ok(first);
  assert.equal(first, second);
  assert.equal(storage.getItem(BME_SYNC_DEVICE_ID_KEY), first);
}

async function testRemoteStatusMissing() {
  const { fetch } = createMockFetchEnvironment();
  const status = await getRemoteStatus("chat-a", {
    fetch,
    getRequestHeaders: () => ({}),
  });

  assert.equal(status.exists, false);
  assert.equal(status.status, "not-found");
}

async function testUploadPayloadMetaFirstAndDebounce() {
  const { fetch, logs } = createMockFetchEnvironment();
  const dbByChatId = new Map();
  dbByChatId.set(
    "chat-upload",
    new FakeDb("chat-upload", {
      meta: {
        schemaVersion: 1,
        chatId: "chat-upload",
        deviceId: "",
        revision: 9,
        lastModified: Date.now(),
        nodeCount: 1,
        edgeCount: 0,
        tombstoneCount: 0,
      },
      nodes: [{ id: "n1", updatedAt: 100 }],
      edges: [],
      tombstones: [],
      state: { lastProcessedFloor: 7, extractionCount: 4 },
    }),
  );

  const runtime = buildRuntimeOptions({ dbByChatId, fetch });
  const uploadResult = await upload("chat-upload", runtime);
  assert.equal(uploadResult.uploaded, true);
  assert.equal(logs.uploadCalls, 1);
  assert.equal(logs.uploadChunkCalls > 0, true);
  assert.equal(Number.isFinite(uploadResult.timings?.exportMs), true);
  assert.equal(Number.isFinite(uploadResult.timings?.chunkUploadMs), true);
  assert.equal(Number.isFinite(uploadResult.timings?.manifestUploadMs), true);
  assert.equal(Number.isFinite(uploadResult.timings?.metaPatchMs), true);

  const uploadedPayload = logs.uploadedPayloads[0].payload;
  assert.equal(uploadedPayload.formatVersion, 2);
  assert.equal(uploadedPayload.meta.revision, 9);
  assert.equal(Array.isArray(uploadedPayload.chunks), true);
  assert.equal(uploadedPayload.chunks.length > 0, true);

  scheduleUpload("chat-upload", {
    ...runtime,
    debounceMs: 20,
  });
  await sleep(50);
  assert.equal(logs.uploadCalls, 2);
}

async function testUploadSanitizesIllegalChatIdFilename() {
  const { fetch, logs } = createMockFetchEnvironment();
  const dbByChatId = new Map();
  const chatId = "世界书 测试(chat)#1";
  dbByChatId.set(chatId, new FakeDb(chatId));

  const runtime = buildRuntimeOptions({ dbByChatId, fetch });
  const uploadResult = await upload(chatId, runtime);

  assert.equal(uploadResult.uploaded, true);
  assert.equal(logs.uploadCalls, 1);
  assert.match(uploadResult.filename, /^ST-BME_sync_[A-Za-z0-9._~-]+\.json$/);
  assert.match(logs.uploadedPayloads[0].name, /^[A-Za-z0-9._~-]+$/);
}

async function testDownloadImport() {
  const { fetch, remoteFiles } = createMockFetchEnvironment();
  const dbByChatId = new Map();
  const db = new FakeDb("chat-download");
  dbByChatId.set("chat-download", db);

  remoteFiles.set("ST-BME_sync_chat-download.json", {
    meta: {
      schemaVersion: 1,
      chatId: "chat-download",
      revision: 12,
      deviceId: "remote-device",
      lastModified: 500,
      nodeCount: 1,
      edgeCount: 0,
      tombstoneCount: 0,
      runtimeVectorIndexState: {
        mode: "backend",
        collectionId: "st-bme::chat-download",
        source: "openai",
        hashToNodeId: {
          "hash-remote-node": "remote-node",
        },
        nodeToHash: {
          "remote-node": "hash-remote-node",
        },
        lastStats: {
          total: 1,
          indexed: 1,
          stale: 0,
          pending: 0,
        },
      },
    },
    nodes: [{ id: "remote-node", updatedAt: 400 }],
    edges: [],
    tombstones: [],
    state: {
      lastProcessedFloor: 10,
      extractionCount: 2,
    },
  });

  const runtime = buildRuntimeOptions({ dbByChatId, fetch });
  const result = await download("chat-download", runtime);

  assert.equal(result.downloaded, true);
  assert.equal(Number.isFinite(result.timings?.networkMs), true);
  assert.equal(Number.isFinite(result.timings?.importMs), true);
  assert.equal(Number.isFinite(result.timings?.metaPatchMs), true);
  assert.equal(Number.isFinite(result.timings?.hookMs), true);
  assert.equal(db.lastImportPayload.meta.revision, 12);
  assert.equal(db.lastImportPayload.nodes[0].id, "remote-node");
  assert.equal(db.lastImportPayload.meta.runtimeVectorIndexState.dirty, true);
  assert.equal(
    db.lastImportPayload.meta.runtimeVectorIndexState.dirtyReason,
    "backend-sync-download-unverified",
  );
  assert.deepEqual(db.lastImportPayload.meta.runtimeVectorIndexState.hashToNodeId, {});
  assert.deepEqual(db.lastImportPayload.meta.runtimeVectorIndexState.nodeToHash, {});
  assert.equal(
    db.lastImportPayload.meta.runtimeVectorIndexState.pendingRepairFromFloor,
    0,
  );
}

async function testLegacyRemoteFilenameFallbackAndReuse() {
  const { fetch, remoteFiles, logs } = createMockFetchEnvironment();
  const dbByChatId = new Map();
  const chatId = "chat~legacy name";
  const db = new FakeDb(chatId);
  dbByChatId.set(chatId, db);

  remoteFiles.set("ST-BME_sync_chat~legacy_name.json", {
    meta: {
      schemaVersion: 1,
      chatId,
      revision: 4,
      deviceId: "remote-device",
      lastModified: 400,
      nodeCount: 1,
      edgeCount: 0,
      tombstoneCount: 0,
    },
    nodes: [{ id: "legacy-node", updatedAt: 300 }],
    edges: [],
    tombstones: [],
    state: {
      lastProcessedFloor: 3,
      extractionCount: 2,
    },
  });

  const runtime = buildRuntimeOptions({ dbByChatId, fetch });
  const status = await getRemoteStatus(chatId, runtime);
  assert.equal(status.exists, true);
  assert.equal(status.filename, "ST-BME_sync_chat~legacy_name.json");

  const downloadResult = await download(chatId, runtime);
  assert.equal(downloadResult.downloaded, true);
  assert.equal(downloadResult.filename, "ST-BME_sync_chat~legacy_name.json");
  assert.equal(db.lastImportPayload.nodes[0].id, "legacy-node");

  const uploadResult = await upload(chatId, runtime);
  assert.equal(uploadResult.uploaded, true);
  assert.equal(uploadResult.filename, "ST-BME_sync_chat~legacy_name.json");
  assert.equal(logs.uploadedPayloads.at(-1)?.name, "ST-BME_sync_chat~legacy_name.json");
}

async function testMergeRules() {
  const local = {
    meta: {
      chatId: "chat-merge",
      revision: 7,
      lastModified: 100,
      deviceId: "local-device",
      schemaVersion: 1,
    },
    nodes: [{ id: "node-a", updatedAt: 100, value: "old" }],
    edges: [{ id: "edge-a", updatedAt: 100, fromId: "a", toId: "b" }],
    tombstones: [],
    state: {
      lastProcessedFloor: 5,
      extractionCount: 3,
    },
  };

  const remote = {
    meta: {
      chatId: "chat-merge",
      revision: 10,
      lastModified: 200,
      deviceId: "remote-device",
      schemaVersion: 1,
    },
    nodes: [{ id: "node-a", updatedAt: 200, value: "new" }],
    edges: [{ id: "edge-a", updatedAt: 200, fromId: "a", toId: "b" }],
    tombstones: [
      {
        id: "node:node-a",
        kind: "node",
        targetId: "node-a",
        deletedAt: 250,
        sourceDeviceId: "remote-device",
      },
    ],
    state: {
      lastProcessedFloor: 8,
      extractionCount: 2,
    },
  };

  const merged = mergeSnapshots(local, remote, { chatId: "chat-merge" });

  assert.equal(merged.meta.revision, 11);
  assert.equal(merged.nodes.length, 0, "tombstone 必须覆盖复活");
  assert.equal(merged.state.lastProcessedFloor, 8);
  assert.equal(merged.state.extractionCount, 3);
}

async function testMergeRuntimeMetaPolicies() {
  const local = {
    meta: {
      chatId: "chat-merge-meta",
      revision: 7,
      lastModified: 200,
      deviceId: "local-device",
      schemaVersion: 1,
      runtimeHistoryState: {
        chatId: "chat-merge-meta",
        lastProcessedAssistantFloor: 6,
        extractionCount: 6,
        processedMessageHashes: {
          1: "h1",
          2: "h2",
          3: "h3",
          4: "local-h4",
          6: "h6",
        },
      },
      runtimeVectorIndexState: {
        hashToNodeId: {
          "hash-local-a": "node-a",
          "hash-shared-b": "node-b",
        },
        nodeToHash: {
          "node-a": "hash-local-a",
          "node-b": "hash-shared-b",
        },
      },
      runtimeBatchJournal: [
        { id: "journal-shared", processedRange: [0, 2], createdAt: 100 },
        { id: "journal-drop-local", processedRange: [4, 5], createdAt: 110 },
      ],
      runtimeLastRecallResult: { nodes: ["local-only"] },
      runtimeSummaryState: { updatedAt: 500, frontier: ["local-summary"] },
      maintenanceJournal: [{ id: "maintenance-local", updatedAt: 600 }],
      knowledgeState: { updatedAt: 700, activeOwnerKey: "local-owner" },
      regionState: { updatedAt: 800, activeRegion: "local-region" },
      timelineState: { updatedAt: 900, activeSegmentId: "local-segment" },
      runtimeLastProcessedSeq: 2,
      runtimeGraphVersion: 10,
    },
    nodes: [
      { id: "node-a", updatedAt: 100 },
      { id: "node-b", updatedAt: 100 },
    ],
    edges: [],
    tombstones: [],
    state: {
      lastProcessedFloor: 6,
      extractionCount: 3,
    },
  };

  const remote = {
    meta: {
      chatId: "chat-merge-meta",
      revision: 10,
      lastModified: 200,
      deviceId: "remote-device",
      schemaVersion: 1,
      runtimeHistoryState: {
        chatId: "chat-merge-meta",
        lastProcessedAssistantFloor: 5,
        extractionCount: 7,
        processedMessageHashes: {
          1: "h1",
          2: "h2",
          3: "h3",
          4: "remote-h4",
          5: "h5",
        },
      },
      runtimeVectorIndexState: {
        hashToNodeId: {
          "hash-remote-a": "node-a",
          "hash-shared-b": "node-b",
        },
        nodeToHash: {
          "node-a": "hash-remote-a",
          "node-b": "hash-shared-b",
        },
      },
      runtimeBatchJournal: [
        { id: "journal-shared", processedRange: [0, 3], createdAt: 210 },
        { id: "journal-drop-remote", processedRange: [3, 4], createdAt: 220 },
      ],
      runtimeLastRecallResult: { nodes: ["remote-only"] },
      runtimeSummaryState: { updatedAt: 1500, frontier: ["remote-summary"] },
      maintenanceJournal: [{ id: "maintenance-remote", updatedAt: 1600 }],
      knowledgeState: { updatedAt: 1700, activeOwnerKey: "remote-owner" },
      regionState: { updatedAt: 1800, activeRegion: "remote-region" },
      timelineState: { updatedAt: 1900, activeSegmentId: "remote-segment" },
      runtimeLastProcessedSeq: 9,
      runtimeGraphVersion: 7,
    },
    nodes: [
      { id: "node-a", updatedAt: 200 },
      { id: "node-b", updatedAt: 200 },
    ],
    edges: [],
    tombstones: [],
    state: {
      lastProcessedFloor: 5,
      extractionCount: 2,
    },
  };

  const merged = mergeSnapshots(local, remote, { chatId: "chat-merge-meta" });

  assert.equal(merged.state.lastProcessedFloor, 3, "冲突哈希楼层应触发保守回退");
  assert.equal(merged.state.extractionCount, 7);
  assert.deepEqual(Object.keys(merged.meta.runtimeHistoryState.processedMessageHashes), ["1", "2", "3"]);
  assert.equal(merged.meta.runtimeHistoryState.historyDirtyFrom, 4);
  assert.ok(String(merged.meta.runtimeHistoryState.lastMutationReason).includes("processed-hash-conflict@4"));
  assert.equal(merged.meta.runtimeVectorIndexState.nodeToHash["node-a"], undefined);
  assert.equal(merged.meta.runtimeVectorIndexState.nodeToHash["node-b"], "hash-shared-b");
  assert.equal(merged.meta.runtimeVectorIndexState.hashToNodeId["hash-local-a"], undefined);
  assert.equal(merged.meta.runtimeVectorIndexState.hashToNodeId["hash-remote-a"], undefined);
  assert.equal(merged.meta.runtimeVectorIndexState.hashToNodeId["hash-shared-b"], "node-b");
  assert.equal(merged.meta.runtimeVectorIndexState.dirty, true);
  assert.ok(merged.meta.runtimeVectorIndexState.replayRequiredNodeIds.includes("node-a"));
  assert.equal(merged.meta.runtimeVectorIndexState.pendingRepairFromFloor, 3);
  assert.equal(merged.meta.runtimeBatchJournal.length, 1);
  assert.equal(merged.meta.runtimeBatchJournal[0].id, "journal-shared");
  assert.deepEqual(merged.meta.runtimeBatchJournal[0].processedRange, [0, 3]);
  assert.equal(merged.meta.runtimeLastRecallResult, null);
  assert.equal(merged.meta.runtimeSummaryState.frontier[0], "remote-summary");
  assert.equal(merged.meta.maintenanceJournal[0].id, "maintenance-remote");
  assert.equal(merged.meta.knowledgeState.activeOwnerKey, "remote-owner");
  assert.equal(merged.meta.regionState.activeRegion, "remote-region");
  assert.equal(merged.meta.timelineState.activeSegmentId, "remote-segment");
  assert.equal(merged.meta.runtimeLastProcessedSeq, 9);
  assert.equal(merged.meta.runtimeGraphVersion, 11);
}

async function testManualCloudModeGuards() {
  const { fetch, logs } = createMockFetchEnvironment();
  const dbByChatId = new Map();
  dbByChatId.set("chat-manual", new FakeDb("chat-manual"));

  const runtime = {
    ...buildRuntimeOptions({ dbByChatId, fetch }),
    cloudStorageMode: "manual",
  };

  const scheduleResult = scheduleUpload("chat-manual", runtime);
  assert.equal(scheduleResult.scheduled, false);
  assert.equal(scheduleResult.reason, "manual-cloud-mode");

  const syncResult = await syncNow("chat-manual", runtime);
  assert.equal(syncResult.action, "manual-probe");
  assert.equal(logs.uploadCalls, 0);

  const chatChangeResult = await autoSyncOnChatChange("chat-manual", runtime);
  assert.equal(chatChangeResult.action, "manual-probe");
  assert.equal(chatChangeResult.remoteStatus, null);
  assert.equal(logs.getCalls, 0);
  assert.equal(logs.uploadCalls, 0);
}

async function testManualBackupAndRestoreFlow() {
  const { fetch, remoteFiles, logs } = createMockFetchEnvironment();
  const dbByChatId = new Map();
  const db = new FakeDb("chat-backup-flow", {
    meta: {
      schemaVersion: 1,
      chatId: "chat-backup-flow",
      revision: 8,
      lastModified: 80,
      deviceId: "",
      nodeCount: 1,
      edgeCount: 0,
      tombstoneCount: 0,
      runtimeHistoryState: {
        chatId: "chat-backup-flow",
        lastProcessedAssistantFloor: 4,
        extractionCount: 2,
        processedMessageHashVersion: 2,
        processedMessageHashes: {
          0: "hash-0",
          1: "hash-1",
          2: "hash-2",
          3: "hash-3",
          4: "hash-4",
        },
        processedMessageHashesNeedRefresh: false,
        historyDirtyFrom: 2,
        lastMutationReason: "hash-recheck",
        lastMutationSource: "event:message-received",
        lastRecoveryResult: {
          status: "pending",
          fromFloor: 2,
        },
      },
      runtimeBatchJournal: [
        { id: "journal-1", processedRange: [0, 0], createdAt: 11 },
        { id: "journal-2", processedRange: [1, 1], createdAt: 22 },
        { id: "journal-3", processedRange: [2, 2], createdAt: 33 },
        { id: "journal-4", processedRange: [3, 3], createdAt: 44 },
        { id: "journal-5", processedRange: [4, 4], createdAt: 55 },
        { id: "journal-6", processedRange: [5, 5], createdAt: 66 },
      ],
      runtimeVectorIndexState: {
        mode: "backend",
        collectionId: "st-bme::chat-backup-flow",
        source: "openai",
        hashToNodeId: {
          "hash-local-node": "local-node",
        },
        nodeToHash: {
          "local-node": "hash-local-node",
        },
        lastStats: {
          total: 1,
          indexed: 1,
          stale: 0,
          pending: 0,
        },
      },
      maintenanceJournal: [
        { id: "maintenance-a", updatedAt: 70 },
        { id: "maintenance-b", updatedAt: 80 },
      ],
    },
    nodes: [{ id: "local-node", updatedAt: 80 }],
    edges: [],
    tombstones: [],
    state: {
      lastProcessedFloor: 4,
      extractionCount: 2,
    },
  });
  db.meta.set("syncDirty", true);
  dbByChatId.set("chat-backup-flow", db);

  const safetyDb = new FakeDb("__restore_safety__chat-backup-flow");
  const hookCalls = [];
  const runtime = {
    ...buildRuntimeOptions({ dbByChatId, fetch }),
    getSafetyDb: async () => safetyDb,
    onSyncApplied: async (payload) => hookCalls.push({ ...payload }),
  };

  const backupResult = await backupToServer("chat-backup-flow", runtime);
  assert.equal(backupResult.backedUp, true);
  assert.equal(Number.isFinite(backupResult.timings?.exportMs), true);
  assert.equal(Number.isFinite(backupResult.timings?.uploadMs), true);
  assert.equal(Number.isFinite(backupResult.timings?.manifestWriteMs), true);
  assert.equal(Number.isFinite(backupResult.timings?.metaPatchMs), true);
  assert.equal(db.meta.get("syncDirty"), false);
  assert.ok(Number(db.meta.get("lastBackupUploadedAt")) > 0);
  assert.ok(String(db.meta.get("lastBackupFilename") || "").startsWith("ST-BME_backup_"));
  const backupPayload = remoteFiles.get(backupResult.filename);
  assert.ok(backupPayload, "manual backup should be written to remote files");
  assert.equal(backupPayload.snapshot.meta.runtimeBatchJournal.length, 4);
  assert.deepEqual(
    backupPayload.snapshot.meta.runtimeBatchJournal.map((entry) => entry.id),
    ["journal-3", "journal-4", "journal-5", "journal-6"],
  );
  assert.equal(backupPayload.snapshot.meta.maintenanceJournal.length, 0);
  assert.deepEqual(
    backupPayload.snapshot.meta.runtimeHistoryState[MANUAL_BACKUP_BATCH_JOURNAL_COVERAGE_KEY],
    {
      truncated: true,
      earliestRetainedFloor: 2,
      retainedCount: 4,
    },
  );
  assert.deepEqual(
    backupPayload.snapshot.meta.runtimeHistoryState.processedMessageHashes,
    {
      0: "hash-0",
      1: "hash-1",
      2: "hash-2",
      3: "hash-3",
      4: "hash-4",
    },
  );
  assert.equal(
    backupPayload.snapshot.meta.runtimeHistoryState.processedMessageHashesNeedRefresh,
    false,
  );
  const backupUploadLog = logs.uploadedPayloads.find(
    (entry) => entry.name === backupResult.filename,
  );
  assert.ok(backupUploadLog);
  assert.equal(backupUploadLog.decoded.includes("\n"), false);

  const manifestResult = await listServerBackups(runtime);
  assert.equal(manifestResult.entries.length, 1);
  assert.equal(manifestResult.entries[0].chatId, "chat-backup-flow");
  const manifestUploadLog = logs.uploadedPayloads.find(
    (entry) => entry.name === "ST-BME_BackupManifest.json",
  );
  assert.ok(manifestUploadLog);
  assert.equal(manifestUploadLog.decoded.includes("\n"), false);

  db.snapshot = {
    meta: {
      schemaVersion: 1,
      chatId: "chat-backup-flow",
      revision: 1,
      lastModified: 10,
      deviceId: "",
      nodeCount: 0,
      edgeCount: 0,
      tombstoneCount: 0,
    },
    nodes: [],
    edges: [],
    tombstones: [],
    state: {
      lastProcessedFloor: -1,
      extractionCount: 0,
    },
  };

  const restoreResult = await restoreFromServer("chat-backup-flow", runtime);
  assert.equal(restoreResult.restored, true);
  assert.equal(Number.isFinite(restoreResult.timings?.downloadMs), true);
  assert.equal(Number.isFinite(restoreResult.timings?.localExportMs), true);
  assert.equal(Number.isFinite(restoreResult.timings?.safetySnapshotMs), true);
  assert.equal(Number.isFinite(restoreResult.timings?.importMs), true);
  assert.equal(Number.isFinite(restoreResult.timings?.metaPatchMs), true);
  assert.equal(Number.isFinite(restoreResult.timings?.hookMs), true);
  assert.equal(db.snapshot.nodes[0].id, "local-node");
  assert.equal(db.snapshot.meta.runtimeBatchJournal.length, 4);
  assert.equal(db.snapshot.meta.maintenanceJournal.length, 0);
  assert.deepEqual(
    db.snapshot.meta.runtimeHistoryState[MANUAL_BACKUP_BATCH_JOURNAL_COVERAGE_KEY],
    {
      truncated: true,
      earliestRetainedFloor: 2,
      retainedCount: 4,
    },
  );
  assert.deepEqual(db.snapshot.meta.runtimeHistoryState.processedMessageHashes, {});
  assert.equal(
    db.snapshot.meta.runtimeHistoryState.processedMessageHashesNeedRefresh,
    true,
  );
  assert.equal(
    db.snapshot.meta.runtimeHistoryState.lastProcessedAssistantFloor,
    4,
  );
  assert.equal(db.snapshot.meta.runtimeHistoryState.historyDirtyFrom, null);
  assert.equal(db.snapshot.meta.runtimeHistoryState.lastMutationReason, "");
  assert.equal(db.snapshot.meta.runtimeHistoryState.lastMutationSource, "");
  assert.equal(db.snapshot.meta.runtimeHistoryState.lastRecoveryResult, null);
  assert.equal(db.snapshot.meta.runtimeVectorIndexState.dirty, true);
  assert.equal(
    db.snapshot.meta.runtimeVectorIndexState.dirtyReason,
    "backend-backup-restore-unverified",
  );
  assert.deepEqual(db.snapshot.meta.runtimeVectorIndexState.hashToNodeId, {});
  assert.deepEqual(db.snapshot.meta.runtimeVectorIndexState.nodeToHash, {});
  assert.ok(Number(db.meta.get("lastBackupRestoredAt")) > 0);
  const safetyStatus = await getRestoreSafetySnapshotStatus(
    "chat-backup-flow",
    runtime,
  );
  assert.equal(safetyStatus.exists, true);
  assert.equal(safetyDb.lastImportPayload.meta.revision, 1);
  assert.deepEqual(
    hookCalls.map((item) => item.action),
    ["restore-backup"],
  );

  db.snapshot = {
    meta: {
      schemaVersion: 1,
      chatId: "chat-backup-flow",
      revision: 99,
      lastModified: 999,
      deviceId: "",
      nodeCount: 1,
      edgeCount: 0,
      tombstoneCount: 0,
    },
    nodes: [{ id: "broken-node", updatedAt: 999 }],
    edges: [],
    tombstones: [],
    state: {
      lastProcessedFloor: 88,
      extractionCount: 9,
    },
  };

  const rollbackResult = await rollbackFromRestoreSafetySnapshot(
    "chat-backup-flow",
    runtime,
  );
  assert.equal(rollbackResult.restored, true);
  assert.equal(db.snapshot.meta.revision, 1);
  assert.equal(db.snapshot.nodes.length, 0);
  assert.equal(db.meta.get("syncDirty"), true);
  assert.ok(Number(db.meta.get("lastBackupRollbackAt")) > 0);

  const deleteResult = await deleteServerBackup("chat-backup-flow", runtime);
  assert.equal(deleteResult.deleted, true);
  assert.equal(deleteResult.localMetaUpdated, true);
  const manifestAfterDelete = await listServerBackups(runtime);
  assert.equal(manifestAfterDelete.entries.length, 0);
  assert.equal(
    Array.from(remoteFiles.keys()).some((key) => key.startsWith("ST-BME_backup_")),
    false,
  );
  assert.equal(db.meta.get("lastBackupUploadedAt"), 0);
  assert.equal(db.meta.get("lastBackupFilename"), "");
}

async function testBackupManifestReadFailureDoesNotOverwriteManifest() {
  const { fetch, remoteFiles } = createMockFetchEnvironment();
  const dbByChatId = new Map();
  const db = new FakeDb("chat-manifest-guard", {
    meta: {
      schemaVersion: 1,
      chatId: "chat-manifest-guard",
      revision: 3,
      lastModified: 30,
      deviceId: "",
      nodeCount: 1,
      edgeCount: 0,
      tombstoneCount: 0,
    },
    nodes: [{ id: "node-manifest", updatedAt: 30 }],
    edges: [],
    tombstones: [],
    state: {
      lastProcessedFloor: 2,
      extractionCount: 1,
    },
  });
  dbByChatId.set("chat-manifest-guard", db);

  remoteFiles.set("ST-BME_BackupManifest.json", [
    {
      filename: "ST-BME_backup_existing-a.json",
      serverPath: "user/files/ST-BME_backup_existing-a.json",
      chatId: "chat-a",
      revision: 1,
      lastModified: 10,
      backupTime: 10,
      size: 100,
      schemaVersion: 1,
    },
  ]);

  let failManifestRead = true;
  const guardedFetch = async (url, options = {}) => {
    if (
      failManifestRead
      && String(options?.method || "GET").toUpperCase() === "GET"
      && String(url).startsWith("/user/files/ST-BME_BackupManifest.json")
    ) {
      return createJsonResponse(500, "manifest read failed");
    }
    return await fetch(url, options);
  };

  const runtime = buildRuntimeOptions({ dbByChatId, fetch: guardedFetch });
  const backupResult = await backupToServer("chat-manifest-guard", runtime);
  assert.equal(backupResult.backedUp, false);
  assert.equal(backupResult.reason, "backup-manifest-error");
  assert.equal(backupResult.backupUploaded, true);

  failManifestRead = false;
  const manifestResult = await listServerBackups(runtime);
  assert.equal(manifestResult.entries.length, 1);
  assert.equal(manifestResult.entries[0].chatId, "chat-a");
}

async function testRestoreValidationDoesNotCreateSafetySnapshot() {
  const { fetch } = createMockFetchEnvironment();
  const dbByChatId = new Map();
  const db = new FakeDb("chat-no-backup");
  const safetyDb = new FakeDb(buildRestoreSafetyChatId("chat-no-backup"));
  dbByChatId.set("chat-no-backup", db);

  const runtime = {
    ...buildRuntimeOptions({ dbByChatId, fetch }),
    getSafetyDb: async () => safetyDb,
  };

  const restoreResult = await restoreFromServer("chat-no-backup", runtime);
  assert.equal(restoreResult.restored, false);
  assert.equal(restoreResult.reason, "not-found");
  assert.equal(Number.isFinite(restoreResult.timings?.downloadMs), true);

  const safetyStatus = await getRestoreSafetySnapshotStatus(
    "chat-no-backup",
    runtime,
  );
  assert.equal(safetyStatus.exists, false);
}

async function testRestoreUsesManifestFilenameWhenCurrentFilenameDrifts() {
  const { fetch, remoteFiles } = createMockFetchEnvironment();
  const dbByChatId = new Map();
  const db = new FakeDb("chat-filename-drift");
  const safetyDb = new FakeDb(buildRestoreSafetyChatId("chat-filename-drift"));
  dbByChatId.set("chat-filename-drift", db);

  const legacyFilename = "ST-BME_backup_chat-filename-drift-legacy.json";
  remoteFiles.set(legacyFilename, {
    kind: "st-bme-backup",
    version: 1,
    chatId: "chat-filename-drift",
    createdAt: 123,
    sourceDeviceId: "remote-device",
    snapshot: {
      meta: {
        schemaVersion: 1,
        chatId: "chat-filename-drift",
        revision: 7,
        lastModified: 70,
        deviceId: "remote-device",
        nodeCount: 1,
        edgeCount: 0,
        tombstoneCount: 0,
      },
      nodes: [{ id: "restored-from-drift", updatedAt: 70 }],
      edges: [],
      tombstones: [],
      state: {
        lastProcessedFloor: 5,
        extractionCount: 2,
      },
    },
  });
  remoteFiles.set("ST-BME_BackupManifest.json", [
    {
      filename: legacyFilename,
      serverPath: `user/files/${legacyFilename}`,
      chatId: "chat-filename-drift",
      revision: 7,
      lastModified: 70,
      backupTime: 123,
      size: 256,
      schemaVersion: 1,
    },
  ]);

  const runtime = {
    ...buildRuntimeOptions({ dbByChatId, fetch }),
    getSafetyDb: async () => safetyDb,
  };

  const restoreResult = await restoreFromServer("chat-filename-drift", runtime);
  assert.equal(restoreResult.restored, true);
  assert.equal(restoreResult.filename, legacyFilename);
  assert.equal(db.snapshot.nodes[0].id, "restored-from-drift");
}

async function testDeleteUsesExplicitManifestFilenameAndClearsLocalBackupMeta() {
  const { fetch, remoteFiles } = createMockFetchEnvironment();
  const dbByChatId = new Map();
  const db = new FakeDb("chat-delete-drift");
  db.meta.set("lastBackupUploadedAt", 999);
  db.meta.set("lastBackupFilename", "ST-BME_backup_chat-delete-drift-stale.json");
  dbByChatId.set("chat-delete-drift", db);

  const driftFilename = "ST-BME_backup_chat-delete-drift-legacy.json";
  remoteFiles.set(driftFilename, {
    kind: "st-bme-backup",
    version: 1,
    chatId: "chat-delete-drift",
    createdAt: 321,
    sourceDeviceId: "remote-device",
    snapshot: {
      meta: {
        schemaVersion: 1,
        chatId: "chat-delete-drift",
        revision: 3,
        lastModified: 30,
        deviceId: "remote-device",
        nodeCount: 0,
        edgeCount: 0,
        tombstoneCount: 0,
      },
      nodes: [],
      edges: [],
      tombstones: [],
      state: {
        lastProcessedFloor: -1,
        extractionCount: 0,
      },
    },
  });
  remoteFiles.set("ST-BME_BackupManifest.json", [
    {
      filename: driftFilename,
      serverPath: `user/files/${driftFilename}`,
      chatId: "chat-delete-drift",
      revision: 3,
      lastModified: 30,
      backupTime: 321,
      size: 128,
      schemaVersion: 1,
    },
  ]);

  const runtime = buildRuntimeOptions({ dbByChatId, fetch });
  const deleteResult = await deleteServerBackup("chat-delete-drift", {
    ...runtime,
    filename: driftFilename,
    serverPath: `user/files/${driftFilename}`,
  });

  assert.equal(deleteResult.deleted, true);
  assert.equal(deleteResult.filename, driftFilename);
  assert.equal(deleteResult.localMetaUpdated, true);
  assert.equal(remoteFiles.has(driftFilename), false);
  assert.equal(db.meta.get("lastBackupUploadedAt"), 0);
  assert.equal(db.meta.get("lastBackupFilename"), "");

  const manifestResult = await listServerBackups(runtime);
  assert.equal(manifestResult.entries.length, 0);
}

async function testSyncNowLockAndAutoSync() {
  const { fetch, remoteFiles, logs } = createMockFetchEnvironment();
  const dbByChatId = new Map();
  const db = new FakeDb("chat-lock", {
    meta: {
      schemaVersion: 1,
      chatId: "chat-lock",
      revision: 1,
      lastModified: 10,
      deviceId: "",
      nodeCount: 0,
      edgeCount: 0,
      tombstoneCount: 0,
    },
    nodes: [],
    edges: [],
    tombstones: [],
    state: {
      lastProcessedFloor: -1,
      extractionCount: 0,
    },
  });
  dbByChatId.set("chat-lock", db);

  const runtime = buildRuntimeOptions({ dbByChatId, fetch });

  const [r1, r2] = await Promise.all([
    syncNow("chat-lock", runtime),
    syncNow("chat-lock", runtime),
  ]);

  assert.equal(r1.action, "upload");
  assert.equal(r2.action, "upload");
  assert.equal(logs.uploadCalls, 1, "同 chatId 并发 sync 应串行去重");

  remoteFiles.set("ST-BME_sync_chat-lock.json", {
    meta: {
      schemaVersion: 1,
      chatId: "chat-lock",
      revision: 3,
      lastModified: 99,
      deviceId: "remote-device",
      nodeCount: 1,
      edgeCount: 0,
      tombstoneCount: 0,
    },
    nodes: [{ id: "remote-new", updatedAt: 99 }],
    edges: [],
    tombstones: [],
    state: {
      lastProcessedFloor: 2,
      extractionCount: 1,
    },
  });

  db.meta.set("syncDirty", false);
  const autoResult = await autoSyncOnChatChange("chat-lock", runtime);
  assert.equal(autoResult.action, "download");
  assert.equal(db.lastImportPayload.nodes[0].id, "remote-new");
}

async function testDeleteRemoteSyncFile() {
  const { fetch, logs } = createMockFetchEnvironment();
  const dbByChatId = new Map();
  dbByChatId.set("chat-delete", new FakeDb("chat-delete"));
  const runtime = buildRuntimeOptions({ dbByChatId, fetch });

  await upload("chat-delete", runtime);
  assert.equal(logs.uploadCalls, 1);

  const deleteResult = await deleteRemoteSyncFile("chat-delete", runtime);
  assert.equal(deleteResult.deleted, true);
  assert.equal(deleteResult.chatId, "chat-delete");
  assert.equal(logs.deleteCalls >= 1, true);
  const deleteCallsAfterFirstDelete = logs.deleteCalls;

  const deleteMissingResult = await deleteRemoteSyncFile("chat-delete", runtime);
  assert.equal(deleteMissingResult.deleted, false);
  assert.equal(deleteMissingResult.reason, "not-found");
  assert.equal(logs.deleteCalls > deleteCallsAfterFirstDelete, true);
}

async function testDeleteRemoteSyncFileFallsBackToLegacyFilename() {
  const { fetch, remoteFiles, logs } = createMockFetchEnvironment();
  const dbByChatId = new Map();
  const chatId = "chat~legacy delete";
  dbByChatId.set(chatId, new FakeDb(chatId));
  remoteFiles.set("ST-BME_sync_chat~legacy_delete.json", {
    meta: {
      schemaVersion: 1,
      chatId,
      revision: 1,
      lastModified: 10,
      deviceId: "remote-device",
      nodeCount: 0,
      edgeCount: 0,
      tombstoneCount: 0,
    },
    nodes: [],
    edges: [],
    tombstones: [],
    state: {
      lastProcessedFloor: -1,
      extractionCount: 0,
    },
  });

  const runtime = buildRuntimeOptions({ dbByChatId, fetch });
  const deleteResult = await deleteRemoteSyncFile(chatId, runtime);
  assert.equal(deleteResult.deleted, true);
  assert.equal(deleteResult.filename, "ST-BME_sync_chat~legacy_delete.json");
  assert.equal(logs.deleteCalls, 2, "应先尝试新文件名，再回退删除 legacy 文件名");
}

async function testAutoSyncOnVisibility() {
  const { fetch, logs } = createMockFetchEnvironment();
  const dbByChatId = new Map();
  dbByChatId.set(
    "chat-visibility",
    new FakeDb("chat-visibility", {
      meta: {
        schemaVersion: 1,
        chatId: "chat-visibility",
        revision: 2,
        lastModified: 12,
        deviceId: "",
        nodeCount: 0,
        edgeCount: 0,
        tombstoneCount: 0,
      },
      nodes: [],
      edges: [],
      tombstones: [],
      state: { lastProcessedFloor: -1, extractionCount: 0 },
    }),
  );

  const runtime = buildRuntimeOptions({ dbByChatId, fetch });
  runtime.getCurrentChatId = () => "chat-visibility";

  const originalDocument = globalThis.document;
  const visibilityDocument = createVisibilityMockDocument("hidden");
  globalThis.document = visibilityDocument.document;

  try {
    const installResult = autoSyncOnVisibility(runtime);
    assert.equal(installResult.installed, true);
    assert.ok(
      typeof visibilityDocument.getListener("visibilitychange") === "function",
    );

    visibilityDocument.emitVisibilityChange("visible");
    await sleep(30);
    assert.equal(logs.uploadCalls, 1, "visibility visible 应触发一次自动同步");

    const secondInstallResult = autoSyncOnVisibility(runtime);
    assert.equal(secondInstallResult.installed, true);
  } finally {
    globalThis.document = originalDocument;
  }
}

async function testSyncNowRemoteReadErrorPath() {
  const base = createMockFetchEnvironment();
  const fetch = async (url, options = {}) => {
    if (String(url).startsWith("/user/files/")) {
      return createJsonResponse(500, "server-error");
    }
    return await base.fetch(url, options);
  };

  const dbByChatId = new Map();
  dbByChatId.set("chat-remote-error", new FakeDb("chat-remote-error"));
  const runtime = buildRuntimeOptions({ dbByChatId, fetch });

  const result = await syncNow("chat-remote-error", runtime);
  assert.equal(result.synced, false);
  assert.equal(result.reason, "http-error");
}

async function testSyncAppliedHook() {
  const { fetch, remoteFiles } = createMockFetchEnvironment();
  const dbByChatId = new Map();
  const hookCalls = [];

  dbByChatId.set(
    "chat-hook-download",
    new FakeDb("chat-hook-download", {
      meta: {
        schemaVersion: 1,
        chatId: "chat-hook-download",
        revision: 1,
        lastModified: 10,
        deviceId: "",
        nodeCount: 0,
        edgeCount: 0,
        tombstoneCount: 0,
      },
      nodes: [],
      edges: [],
      tombstones: [],
      state: { lastProcessedFloor: -1, extractionCount: 0 },
    }),
  );

  dbByChatId.set(
    "chat-hook-merge",
    new FakeDb("chat-hook-merge", {
      meta: {
        schemaVersion: 1,
        chatId: "chat-hook-merge",
        revision: 4,
        lastModified: 20,
        deviceId: "",
        nodeCount: 1,
        edgeCount: 0,
        tombstoneCount: 0,
        runtimeVectorIndexState: {
          mode: "backend",
          collectionId: "st-bme::chat-hook-merge",
          source: "openai",
          hashToNodeId: {
            "hash-local-merge": "local-merge",
          },
          nodeToHash: {
            "local-merge": "hash-local-merge",
          },
          lastStats: {
            total: 1,
            indexed: 1,
            stale: 0,
            pending: 0,
          },
        },
      },
      nodes: [{ id: "local-merge", updatedAt: 20 }],
      edges: [],
      tombstones: [],
      state: { lastProcessedFloor: 1, extractionCount: 1 },
    }),
  );

  remoteFiles.set("ST-BME_sync_chat-hook-download.json", {
    meta: { schemaVersion: 1, chatId: "chat-hook-download", revision: 3, lastModified: 30, deviceId: "remote", nodeCount: 1, edgeCount: 0, tombstoneCount: 0 },
    nodes: [{ id: "remote-download", updatedAt: 30 }],
    edges: [],
    tombstones: [],
    state: { lastProcessedFloor: 2, extractionCount: 1 },
  });
  remoteFiles.set("ST-BME_sync_chat-hook-merge.json", {
    meta: {
      schemaVersion: 1,
      chatId: "chat-hook-merge",
      revision: 4,
      lastModified: 25,
      deviceId: "remote",
      nodeCount: 1,
      edgeCount: 0,
      tombstoneCount: 0,
      runtimeVectorIndexState: {
        mode: "backend",
        collectionId: "st-bme::chat-hook-merge",
        source: "openai",
        hashToNodeId: {
          "hash-remote-merge": "remote-merge",
        },
        nodeToHash: {
          "remote-merge": "hash-remote-merge",
        },
        lastStats: {
          total: 1,
          indexed: 1,
          stale: 0,
          pending: 0,
        },
      },
    },
    nodes: [{ id: "remote-merge", updatedAt: 25 }],
    edges: [],
    tombstones: [],
    state: { lastProcessedFloor: 3, extractionCount: 2 },
  });

  const runtime = {
    ...buildRuntimeOptions({ dbByChatId, fetch }),
    onSyncApplied: async (payload) => hookCalls.push({ ...payload }),
  };

  const downloadResult = await syncNow("chat-hook-download", runtime);
  assert.equal(downloadResult.action, "download");

  dbByChatId.get("chat-hook-merge").meta.set("syncDirty", true);
  const mergeResult = await syncNow("chat-hook-merge", runtime);
  assert.equal(mergeResult.action, "merge");

  assert.equal(downloadResult.revision, 3);
  assert.equal(mergeResult.revision, 5);
  assert.equal(
    dbByChatId.get("chat-hook-merge").lastImportPayload.meta.runtimeVectorIndexState.dirty,
    true,
  );
  assert.equal(
    dbByChatId.get("chat-hook-merge").lastImportPayload.meta.runtimeVectorIndexState.dirtyReason,
    "backend-sync-merge-unverified",
  );
  assert.deepEqual(
    dbByChatId.get("chat-hook-merge").lastImportPayload.meta.runtimeVectorIndexState.hashToNodeId,
    {},
  );
  assert.deepEqual(
    dbByChatId.get("chat-hook-merge").lastImportPayload.meta.runtimeVectorIndexState.nodeToHash,
    {},
  );

  assert.deepEqual(hookCalls.map((item) => item.action), ["download", "merge"]);
  assert.deepEqual(hookCalls.map((item) => item.chatId), ["chat-hook-download", "chat-hook-merge"]);
  assert.deepEqual(hookCalls.map((item) => item.revision), [3, 5]);
}

async function main() {
  console.log(`${PREFIX} debounce=${BME_SYNC_UPLOAD_DEBOUNCE_MS}`);
  await testDeviceId();
  await testRemoteStatusMissing();
  await testUploadPayloadMetaFirstAndDebounce();
  await testUploadSanitizesIllegalChatIdFilename();
  await testDownloadImport();
  await testLegacyRemoteFilenameFallbackAndReuse();
  await testMergeRules();
  await testMergeRuntimeMetaPolicies();
  await testManualCloudModeGuards();
  await testManualBackupAndRestoreFlow();
  await testBackupManifestReadFailureDoesNotOverwriteManifest();
  await testRestoreValidationDoesNotCreateSafetySnapshot();
  await testRestoreUsesManifestFilenameWhenCurrentFilenameDrifts();
  await testDeleteUsesExplicitManifestFilenameAndClearsLocalBackupMeta();
  await testSyncNowLockAndAutoSync();
  await testDeleteRemoteSyncFile();
  await testDeleteRemoteSyncFileFallsBackToLegacyFilename();
  await testAutoSyncOnVisibility();
  await testSyncNowRemoteReadErrorPath();
  await testSyncAppliedHook();
  console.log("indexeddb-sync tests passed");
}

await main();
