import assert from "node:assert/strict";

import {
  BME_LEGACY_RETENTION_MS,
  BmeDatabase,
  buildBmeDbName,
  ensureDexieLoaded,
} from "../sync/bme-db.js";
import { createEmptyGraph } from "../graph/graph.js";

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

const chatIdsForCleanup = new Set([
  "chat-migration-a",
  "chat-migration-b",
  "chat-migration-c",
]);

async function setupIndexedDbTestEnv() {
  try {
    await import("fake-indexeddb/auto");
  } catch (error) {
    console.warn(
      `${PREFIX} fake-indexeddb 未安装，回退到当前运行时 indexedDB:`,
      error?.message || error,
    );
  }

  if (!globalThis.Dexie) {
    try {
      const imported = await import("dexie");
      globalThis.Dexie = imported?.default || imported?.Dexie || imported;
    } catch {
      await import("../lib/dexie.min.js");
    }
  }

  await ensureDexieLoaded();
  assert.equal(typeof globalThis.Dexie, "function", "Dexie 构造函数必须可用");
}

async function cleanupDatabases() {
  if (typeof globalThis.Dexie?.delete !== "function") return;

  for (const chatId of chatIdsForCleanup) {
    try {
      await globalThis.Dexie.delete(buildBmeDbName(chatId));
    } catch {
      // ignore
    }
  }
}

function createLegacyGraph(chatId, suffix = "legacy") {
  const graph = createEmptyGraph();
  graph.historyState.chatId = chatId;
  graph.historyState.lastProcessedAssistantFloor = 8;
  graph.historyState.extractionCount = 3;
  graph.lastProcessedSeq = 8;
  graph.nodes.push(
    {
      id: `node-${suffix}-a`,
      type: "event",
      seq: 5,
      seqRange: [4, 5],
      archived: false,
      fields: {
        title: "第一条",
      },
    },
    {
      id: `node-${suffix}-b`,
      type: "event",
      seq: 8,
      archived: false,
      fields: {
        title: "第二条",
      },
    },
  );
  graph.edges.push({
    id: `edge-${suffix}-ab`,
    fromId: `node-${suffix}-a`,
    toId: `node-${suffix}-b`,
    relation: "related",
    seqRange: [5, 8],
  });
  graph.__stBmePersistence = {
    revision: 6,
    reason: "legacy-seed",
    updatedAt: new Date().toISOString(),
  };
  return graph;
}

async function testMigrationSuccessAndMeta() {
  const db = new BmeDatabase("chat-migration-a", { dexieClass: globalThis.Dexie });
  await db.open();

  const before = await db.isEmpty();
  assert.equal(before.empty, true);

  const nowMs = 1735689600000;
  const result = await db.importLegacyGraph(createLegacyGraph("chat-migration-a"), {
    nowMs,
    source: "chat_metadata",
    revision: 6,
  });

  assert.equal(result.migrated, true);
  assert.ok(result.revision >= 1);

  const snapshot = await db.exportSnapshot();
  assert.equal(snapshot.nodes.length, 2);
  assert.equal(snapshot.edges.length, 1);

  const migratedNodeA = snapshot.nodes.find((item) => item.id === "node-legacy-a");
  const migratedNodeB = snapshot.nodes.find((item) => item.id === "node-legacy-b");
  const migratedEdge = snapshot.edges.find((item) => item.id === "edge-legacy-ab");

  assert.ok(migratedNodeA);
  assert.ok(migratedNodeB);
  assert.ok(migratedEdge);

  assert.equal(migratedNodeA.sourceFloor, 5, "node sourceFloor 应优先取 seqRange[1]");
  assert.equal(migratedNodeB.sourceFloor, 8, "node sourceFloor 应回退到 seq");
  assert.equal(migratedEdge.sourceFloor, 8, "edge sourceFloor 应优先取 seqRange[1]");

  assert.equal(snapshot.meta.migrationSource, "chat_metadata");
  assert.equal(snapshot.meta.migrationCompletedAt, nowMs);
  assert.equal(snapshot.meta.legacyRetentionUntil, nowMs + BME_LEGACY_RETENTION_MS);
  assert.equal(snapshot.state.lastProcessedFloor, 8);
  assert.equal(snapshot.state.extractionCount, 3);

  await db.close();
}

async function testMigrationIdempotent() {
  const db = new BmeDatabase("chat-migration-a", { dexieClass: globalThis.Dexie });
  await db.open();

  const beforeSnapshot = await db.exportSnapshot();
  const result = await db.importLegacyGraph(createLegacyGraph("chat-migration-a"), {
    nowMs: beforeSnapshot.meta.migrationCompletedAt + 1000,
    source: "chat_metadata",
    revision: 12,
  });

  assert.equal(result.migrated, false);
  assert.equal(result.reason, "migration-already-completed");

  const afterSnapshot = await db.exportSnapshot();
  assert.equal(afterSnapshot.meta.revision, beforeSnapshot.meta.revision);
  assert.equal(afterSnapshot.nodes.length, beforeSnapshot.nodes.length);

  await db.close();
}

async function testMigrationSkippedWhenNotEmpty() {
  const db = new BmeDatabase("chat-migration-b", { dexieClass: globalThis.Dexie });
  await db.open();

  await db.bulkUpsertNodes([
    {
      id: "existing-node",
      type: "event",
      sourceFloor: 1,
      updatedAt: Date.now(),
    },
  ]);

  const result = await db.importLegacyGraph(createLegacyGraph("chat-migration-b"), {
    nowMs: Date.now(),
    source: "chat_metadata",
    revision: 5,
  });

  assert.equal(result.migrated, false);
  assert.equal(result.reason, "indexeddb-not-empty");

  const migrationCompletedAt = await db.getMeta("migrationCompletedAt", 0);
  assert.equal(migrationCompletedAt, 0);

  await db.close();
}

async function testIsEmptyWithTombstonesOption() {
  const db = new BmeDatabase("chat-migration-c", { dexieClass: globalThis.Dexie });
  await db.open();

  await db.bulkUpsertTombstones([
    {
      id: "tomb-only",
      kind: "node",
      targetId: "legacy-node",
      deletedAt: Date.now(),
      sourceDeviceId: "device-a",
    },
  ]);

  const defaultEmpty = await db.isEmpty();
  const strictEmpty = await db.isEmpty({ includeTombstones: true });

  assert.equal(defaultEmpty.empty, true);
  assert.equal(strictEmpty.empty, false);

  await db.close();
}

async function main() {
  await setupIndexedDbTestEnv();
  await cleanupDatabases();

  await testMigrationSuccessAndMeta();
  await testMigrationIdempotent();
  await testMigrationSkippedWhenNotEmpty();
  await testIsEmptyWithTombstonesOption();

  await cleanupDatabases();

  console.log("indexeddb-migration tests passed");
}

await main();
