import { describe, test } from 'node:test';
import assert from 'node:assert/strict';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { CondensedGraph } from '../../src/core/CondensedGraph.js';
import { ShardedSnapshotBuilder } from '../../src/core/shards/ShardedSnapshotBuilder.js';
import { ShardedSnapshot } from '../../src/core/shards/ShardedSnapshot.js';
import { FileShardStorage } from '../../src/core/shards/FileShardStorage.js';
import { DeltaShardBinary } from '../../src/core/shards/DeltaShardBinary.js';

function buildSnapshot(bucketSize = 4) {
  const graph = new CondensedGraph();

  const user0 = graph._ensureNode('user:0');
  const user1 = graph._ensureNode('user:1');
  const doc0 = graph._ensureNode('doc:0');
  const doc1 = graph._ensureNode('doc:1');

  graph.addEdge(user0, 'owner', doc0);
  graph.addEdge(user1, 'owner', doc0);

  graph.finalizePerfectHash();
  graph.finalizeWaveletAdjacency({ dropAdjacencyList: true });

  const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'sharded-delta-'));
  const builder = new ShardedSnapshotBuilder({ bucketSize, includeDirections: ['out', 'in'] });
  const manifest = builder.build(graph, dir);
  const storage = new FileShardStorage(dir);
  const snapshot = new ShardedSnapshot(manifest, storage, { cacheLimit: 8 });
  snapshot.initializeSync();

  return { graph, snapshot, dir, ids: { user0, user1, doc0, doc1 } };
}

function writeDeltaLayer(snapshot, dir, name, entries) {
  fs.mkdirSync(dir, { recursive: true });
  const merged = new Map();

  for (const entry of entries) {
    const shardMeta = snapshot._selectShardMeta(entry.relationId, entry.direction, entry.srcId);
    assert.ok(shardMeta, 'Missing shard meta for delta entry');
    const localSource = snapshot._localSource(entry.srcId, shardMeta);
    const key = shardMeta.cacheKey;
    let bucket = merged.get(key);
    if (!bucket) {
      bucket = { shardMeta, additions: [], removals: [] };
      merged.set(key, bucket);
    }
    for (const add of entry.additions) {
      bucket.additions.push({
        srcLocal: localSource,
        otherId: add.dstId,
        possBits: add.possBits,
        relBits: add.relBits
      });
    }
    for (const rem of entry.removals) {
      bucket.removals.push({
        srcLocal: localSource,
        otherId: rem.dstId
      });
    }
  }

  const shards = [];
  for (const bucket of merged.values()) {
    const shardMeta = bucket.shardMeta;
    const buffer = DeltaShardBinary.serialize({
      relationId: shardMeta.relationId,
      direction: shardMeta.direction,
      rangeStart: shardMeta.rangeStart,
      rangeEnd: shardMeta.rangeEnd,
      nodeCount: snapshot.nodeCount,
      additions: bucket.additions,
      removals: bucket.removals
    });

    const shardKey = `delta-${name}-${shardMeta.key}`;
    fs.writeFileSync(path.join(dir, shardKey), new Uint8Array(buffer));
    shards.push({
      key: shardKey,
      relationId: shardMeta.relationId,
      direction: shardMeta.direction,
      rangeStart: shardMeta.rangeStart,
      rangeEnd: shardMeta.rangeEnd,
      cacheKey: shardMeta.cacheKey
    });
  }

  return { shards };
}

// ADR-003: sharded snapshots are stubs in src/core/shards/ — tests are the spec for when the subsystem is implemented.
describe.skip('Sharded snapshot delta overlay', () => {
  test('adds and removes edges in overlay reads', () => {
    const { graph, snapshot, dir, ids } = buildSnapshot(4);
    const relId = graph.getRelationId('owner');

    const baseEdge = snapshot.findEdgeSync(ids.user0, relId, ids.doc0);
    assert.ok(baseEdge, 'Expected base edge');

    const deltaDir = path.join(dir, 'delta');
    const deltaManifest = writeDeltaLayer(snapshot, deltaDir, 'l1', [
      {
        relationId: relId,
        direction: 'out',
        srcId: ids.user0,
        additions: [],
        removals: [{ dstId: ids.doc0 }]
      },
      {
        relationId: relId,
        direction: 'out',
        srcId: ids.user1,
        additions: [{ dstId: ids.doc1, possBits: 65535, relBits: 65535 }],
        removals: []
      }
    ]);

    snapshot.setDeltaLayers([{ shards: deltaManifest.shards, storage: new FileShardStorage(deltaDir) }]);

    const removedEdge = snapshot.findEdgeSync(ids.user0, relId, ids.doc0);
    assert.equal(removedEdge, null);

    const addedEdge = snapshot.findEdgeSync(ids.user1, relId, ids.doc1);
    assert.ok(addedEdge, 'Expected added edge');

    const user0Edges = snapshot.getOutEdgesSync(ids.user0, relId);
    assert.equal(user0Edges.length, 0);

    const user1Edges = snapshot.getOutEdgesSync(ids.user1, relId);
    assert.equal(user1Edges.length, 2);

    fs.rmSync(dir, { recursive: true, force: true });
  });

  test('later delta layers override earlier ones', () => {
    const { graph, snapshot, dir, ids } = buildSnapshot(4);
    const relId = graph.getRelationId('owner');

    const layer1Dir = path.join(dir, 'delta-1');
    const layer2Dir = path.join(dir, 'delta-2');

    const layer1 = writeDeltaLayer(snapshot, layer1Dir, 'l1', [
      {
        relationId: relId,
        direction: 'out',
        srcId: ids.user0,
        additions: [{ dstId: ids.doc1, possBits: 65535, relBits: 65535 }],
        removals: []
      }
    ]);

    const layer2 = writeDeltaLayer(snapshot, layer2Dir, 'l2', [
      {
        relationId: relId,
        direction: 'out',
        srcId: ids.user0,
        additions: [],
        removals: [{ dstId: ids.doc1 }]
      }
    ]);

    snapshot.setDeltaLayers([
      { shards: layer1.shards, storage: new FileShardStorage(layer1Dir) },
      { shards: layer2.shards, storage: new FileShardStorage(layer2Dir) }
    ]);

    const edge = snapshot.findEdgeSync(ids.user0, relId, ids.doc1);
    assert.equal(edge, null);

    fs.rmSync(dir, { recursive: true, force: true });
  });
});
