import assert from "node:assert/strict";

import { createEmptyGraph, serializeGraph } from "../graph/graph.js";
import {
  applyAuthorityCheckpointToStore,
  buildAuthorityCheckpointImportSnapshot,
  buildAuthorityConsistencyAudit,
  buildAuthorityConsistencyRepairPlan,
  isAuthorityReplicaSyncRepairAction,
} from "../maintenance/authority-consistency.js";

const graph = createEmptyGraph();
graph.chatId = "chat-a";
graph.meta = { ...(graph.meta || {}), chatId: "chat-a", revision: 7 };
graph.nodes.push({
  id: "node-a",
  type: "memory",
  seq: 1,
  seqRange: [1, 1],
  fields: { title: "Node A" },
  updatedAt: Date.now(),
});

const checkpoint = {
  chatId: "chat-a",
  revision: 7,
  integrity: "integrity-a",
  persistedAt: "2026-04-28T08:00:00.000Z",
  serializedGraph: serializeGraph(graph),
};

const prepared = buildAuthorityCheckpointImportSnapshot(checkpoint, {
  path: "user/files/checkpoint.json",
  source: "authority-blob-restore",
});
assert.equal(prepared.ok, true);
assert.equal(prepared.snapshot.meta.chatId, "chat-a");
assert.equal(prepared.snapshot.meta.revision, 7);
assert.equal(prepared.snapshot.meta.authorityCheckpointPath, "user/files/checkpoint.json");
assert.equal(prepared.snapshot.nodes.length, 1);

const missingSerialized = buildAuthorityCheckpointImportSnapshot({
  chatId: "chat-a",
  revision: 7,
});
assert.equal(missingSerialized.ok, false);
assert.equal(missingSerialized.reason, "checkpoint-serialized-graph-missing");

const imported = [];
const restoreResult = await applyAuthorityCheckpointToStore(
  {
    async open() {
      return true;
    },
    async importSnapshot(snapshot, options) {
      imported.push({ snapshot, options });
      return { revision: snapshot.meta.revision, imported: { nodes: snapshot.nodes.length } };
    },
  },
  checkpoint,
  { markSyncDirty: false },
);
assert.equal(restoreResult.ok, true);
assert.equal(restoreResult.restored, true);
assert.equal(imported.length, 1);
assert.equal(imported[0].options.mode, "replace");
assert.equal(imported[0].options.markSyncDirty, false);

const auditAligned = buildAuthorityConsistencyAudit({
  updatedAt: "2026-04-28T08:20:00.000Z",
  chatId: "chat-a",
  collectionId: "st-bme::chat-a",
  capability: {
    blobReady: true,
  },
  runtimeGraph: {
    meta: { revision: 7 },
    nodes: [{ id: "node-a" }],
    edges: [],
    vectorIndexState: {
      collectionId: "st-bme::chat-a",
      dirty: false,
    },
  },
  graphPersistenceState: {
    chatId: "chat-a",
    revision: 7,
    authorityBlobCheckpointPath: "user/files/checkpoint.json",
  },
  sqlSnapshot: {
    meta: { revision: 7, nodeCount: 1, edgeCount: 0, tombstoneCount: 0 },
    nodes: [],
    edges: [],
    tombstones: [],
  },
  triviumStat: {
    revision: 7,
    itemCount: 1,
    linkCount: 0,
    namespace: "st-bme::chat-a",
  },
  blobResult: {
    ok: true,
    exists: true,
    path: "user/files/checkpoint.json",
    checkpoint,
  },
});
assert.equal(auditAligned.summary.level, "success");
assert.equal(auditAligned.issues.length, 0);
assert.equal(auditAligned.drift.checkpointRestorable, true);
assert.equal(auditAligned.actions.includes("restore-from-authority-blob-checkpoint"), false);
assert.equal(auditAligned.summary.dataSafety, "saved");
const alignedRepairPlan = buildAuthorityConsistencyRepairPlan(auditAligned);
assert.equal(alignedRepairPlan.ok, false);
assert.equal(alignedRepairPlan.stepCount, 0);

const auditDrift = buildAuthorityConsistencyAudit({
  chatId: "chat-a",
  collectionId: "st-bme::chat-a",
  capability: {
    blobReady: true,
  },
  runtimeGraph: {
    meta: { revision: 9 },
    nodes: [],
    edges: [],
    vectorIndexState: {
      collectionId: "st-bme::chat-a",
      dirty: true,
    },
  },
  graphPersistenceState: {
    chatId: "chat-a",
    revision: 9,
  },
  sqlSnapshot: {
    meta: { revision: 8, nodeCount: 1, edgeCount: 0, tombstoneCount: 0 },
  },
  triviumStat: {
    revision: 7,
    namespace: "st-bme::chat-a",
  },
  blobResult: {
    ok: true,
    exists: false,
    path: "user/files/checkpoint.json",
  },
});
assert.equal(auditDrift.summary.level, "warning");
assert.ok(auditDrift.issues.some((issue) => issue.code === "sql-runtime-revision-drift"));
assert.ok(auditDrift.issues.some((issue) => issue.code === "vector-dirty"));
assert.ok(auditDrift.actions.includes("rebuild-authority-trivium"));
assert.ok(auditDrift.actions.includes("write-authority-checkpoint"));
const driftRepairPlan = buildAuthorityConsistencyRepairPlan(auditDrift);
assert.equal(driftRepairPlan.ok, true);
assert.equal(driftRepairPlan.requiresConfirmation, false);
assert.deepEqual(
  driftRepairPlan.steps.map((step) => step.action),
  [
    "write-authority-checkpoint",
    "rebuild-authority-trivium",
  ],
);

const auditSqlAheadReplicasBehind = buildAuthorityConsistencyAudit({
  chatId: "chat-a",
  collectionId: "st-bme::chat-a",
  capability: {
    blobReady: true,
  },
  runtimeGraph: {
    meta: { revision: 2 },
    nodes: [{ id: "node-a" }],
    edges: [],
    vectorIndexState: {
      collectionId: "st-bme::chat-a",
      dirty: false,
    },
  },
  graphPersistenceState: {
    chatId: "chat-a",
    revision: 2,
    authorityBlobCheckpointPath: "user/files/checkpoint.json",
    authorityBlobCheckpointRevision: 0,
  },
  sqlSnapshot: {
    meta: { revision: 2, nodeCount: 1, edgeCount: 0, tombstoneCount: 0 },
  },
  triviumStat: {
    revision: 0,
    namespace: "st-bme::chat-a",
  },
  blobResult: {
    ok: true,
    exists: true,
    path: "user/files/checkpoint.json",
    checkpoint: {
      chatId: "chat-a",
      revision: 0,
      serializedGraph: serializeGraph(createEmptyGraph()),
    },
  },
});
assert.equal(auditSqlAheadReplicasBehind.summary.level, "warning");
assert.equal(auditSqlAheadReplicasBehind.summary.label, "副本待同步");
assert.equal(auditSqlAheadReplicasBehind.summary.dataSafety, "saved-replicas-behind");
assert.equal(auditSqlAheadReplicasBehind.summary.backupRedundancy, "degraded");
assert.equal(auditSqlAheadReplicasBehind.summary.searchQuality, "degraded");
assert.ok(auditSqlAheadReplicasBehind.issues.some((issue) => issue.code === "blob-checkpoint-behind"));
assert.ok(auditSqlAheadReplicasBehind.issues.some((issue) => issue.code === "trivium-replica-behind"));
assert.ok(auditSqlAheadReplicasBehind.actions.includes("write-authority-checkpoint"));
assert.ok(auditSqlAheadReplicasBehind.actions.includes("rebuild-authority-trivium"));
assert.equal(auditSqlAheadReplicasBehind.actions.includes("restore-from-authority-blob-checkpoint"), false);
assert.equal(auditSqlAheadReplicasBehind.drift.checkpointRestorable, false);
const sqlAheadRepairPlan = buildAuthorityConsistencyRepairPlan(auditSqlAheadReplicasBehind);
assert.equal(sqlAheadRepairPlan.ok, true);
assert.equal(sqlAheadRepairPlan.requiresConfirmation, false);
assert.deepEqual(
  sqlAheadRepairPlan.steps.map((step) => step.action),
  [
    "write-authority-checkpoint",
    "rebuild-authority-trivium",
  ],
);

const restoreRepairPlan = buildAuthorityConsistencyRepairPlan({
  issues: [
    {
      severity: "warning",
      code: "sql-runtime-revision-drift",
      message: "SQL revision drift",
    },
  ],
  actions: ["restore-from-authority-blob-checkpoint"],
  summary: {
    level: "warning",
    detail: "runtime / SQL drift",
  },
});
assert.equal(restoreRepairPlan.ok, true);
assert.equal(restoreRepairPlan.requiresConfirmation, true);
assert.deepEqual(
  restoreRepairPlan.steps.map((step) => step.action),
  ["restore-from-authority-blob-checkpoint"],
);

const auditRuntimeAheadOfSql = buildAuthorityConsistencyAudit({
  chatId: "chat-a",
  collectionId: "st-bme::chat-a",
  runtimeGraph: {
    meta: { revision: 4 },
    nodes: [{ id: "node-a" }],
    edges: [],
    vectorIndexState: { collectionId: "st-bme::chat-a", dirty: false },
  },
  graphPersistenceState: {
    chatId: "chat-a",
    revision: 4,
  },
  sqlSnapshot: {
    meta: { revision: 3, nodeCount: 1, edgeCount: 0, tombstoneCount: 0 },
  },
  triviumStat: {
    revision: 3,
    namespace: "st-bme::chat-a",
  },
});
assert.equal(auditRuntimeAheadOfSql.summary.level, "warning");
assert.equal(auditRuntimeAheadOfSql.summary.dataSafety, "runtime-ahead-of-sql");
assert.equal(auditRuntimeAheadOfSql.actions.includes("restore-from-authority-blob-checkpoint"), false);

const auditVectorDirtyOnly = buildAuthorityConsistencyAudit({
  chatId: "chat-a",
  collectionId: "st-bme::chat-a",
  runtimeGraph: {
    meta: { revision: 5 },
    nodes: [{ id: "node-a" }],
    edges: [],
    vectorIndexState: { collectionId: "st-bme::chat-a", dirty: true },
  },
  graphPersistenceState: {
    chatId: "chat-a",
    revision: 5,
  },
  sqlSnapshot: {
    meta: { revision: 5, nodeCount: 1, edgeCount: 0, tombstoneCount: 0 },
  },
  triviumStat: {
    revision: 5,
    namespace: "st-bme::chat-a",
  },
  blobResult: {
    ok: true,
    exists: true,
    path: "user/files/checkpoint.json",
    checkpoint: {
      chatId: "chat-a",
      revision: 5,
      serializedGraph: serializeGraph(graph),
    },
  },
});
assert.equal(auditVectorDirtyOnly.summary.backupRedundancy, "ok");
assert.equal(auditVectorDirtyOnly.summary.searchQuality, "degraded");
assert.equal(isAuthorityReplicaSyncRepairAction("write-authority-checkpoint"), true);
assert.equal(isAuthorityReplicaSyncRepairAction("rebuild-authority-trivium"), true);
assert.equal(isAuthorityReplicaSyncRepairAction("restore-from-authority-blob-checkpoint"), false);

console.log("authority-consistency tests passed");
