/**
 * E2E tests for the context graph publishing flow (2 nodes, shared chain):
 *
 * 1. Create a context graph on-chain
 * 2. Write data to workspace → replicate via GossipSub
 * 3. Enshrine from workspace with contextGraphId
 * 4. Finalization message propagates → peer verifies on-chain → promotes to context graph URIs
 * 5. Verify data lives in context graph data/meta graphs (not contextGraph data graph)
 *
 * Uses a shared EVMChainAdapter so both nodes see the same on-chain events,
 * allowing B to verify A's publish transaction during finalization.
 */
import { describe, it, expect, afterAll, beforeAll } from 'vitest';
import { DKGAgent } from '../src/index.js';
import { createEVMAdapter, getSharedContext, createProvider, takeSnapshot, revertSnapshot, HARDHAT_KEYS } from '../../chain/test/evm-test-context.js';
import { mintTokens } from '../../chain/test/hardhat-harness.js';
import { ethers } from 'ethers';

const CONTEXT_GRAPH = 'context-graph-e2e';
const ENTITY_CTX_1 = 'urn:ctxgraph:entity:1';
const ENTITY_CTX_2 = 'urn:ctxgraph:entity:2';

function sleep(ms: number) { return new Promise(r => setTimeout(r, ms)); }

let _fileSnapshot: string;
beforeAll(async () => {
  _fileSnapshot = await takeSnapshot();
  const { hubAddress } = getSharedContext();
  const provider = createProvider();
  const coreOp = new ethers.Wallet(HARDHAT_KEYS.CORE_OP);
  await mintTokens(provider, hubAddress, HARDHAT_KEYS.DEPLOYER, coreOp.address, ethers.parseEther('50000000'));
});
afterAll(async () => {
  await revertSnapshot(_fileSnapshot);
});

describe('E2E: context graph publish + finalization (shared chain)', () => {
  const sharedChain = createEVMAdapter(HARDHAT_KEYS.CORE_OP);
  let nodeA: DKGAgent;
  let nodeB: DKGAgent;
  let contextGraphId: string;

  afterAll(async () => {
    try { await nodeA?.stop(); } catch {}
    try { await nodeB?.stop(); } catch {}
  });

  it('bootstraps two agents with shared chain, connects, subscribes', async () => {
    nodeA = await DKGAgent.create({
      name: 'CtxA',
      listenPort: 0,
      skills: [],
      chainAdapter: sharedChain,
      nodeRole: 'core',
    });
    nodeB = await DKGAgent.create({
      name: 'CtxB',
      listenPort: 0,
      skills: [],
      chainAdapter: sharedChain,
      nodeRole: 'core',
    });

    await nodeA.start();
    await nodeB.start();
    await sleep(800);

    const addrA = nodeA.multiaddrs.find(a => a.includes('/tcp/') && !a.includes('/p2p-circuit'))!;
    await nodeB.connectTo(addrA);
    await sleep(2000);

    expect(nodeA.node.libp2p.getPeers().length).toBeGreaterThanOrEqual(1);
    expect(nodeB.node.libp2p.getPeers().length).toBeGreaterThanOrEqual(1);

    await nodeA.createContextGraph({ id: CONTEXT_GRAPH, name: 'Context Graph E2E', description: '' });
    nodeA.subscribeToContextGraph(CONTEXT_GRAPH);
    nodeB.subscribeToContextGraph(CONTEXT_GRAPH);
    await sleep(1500);
  }, 15_000);

  it('creates a context graph on the shared chain', async () => {
    const result = await nodeA.registerContextGraphOnChain({
      participantIdentityIds: [1n, 2n],
      requiredSignatures: 1,
    });

    contextGraphId = result.contextGraphId;
    expect(contextGraphId).toBeDefined();
    expect(Number(contextGraphId)).toBeGreaterThan(0);
  }, 10_000);

  it('A writes to workspace; B receives via GossipSub', async () => {
    const quads = [
      { subject: ENTITY_CTX_1, predicate: 'http://schema.org/name', object: '"Context Graph Entity"', graph: '' },
      { subject: ENTITY_CTX_1, predicate: 'http://schema.org/version', object: '"1"', graph: '' },
    ];

    const wsResult = await nodeA.share(CONTEXT_GRAPH, quads);
    expect(wsResult.shareOperationId).toBeDefined();

    const deadline = Date.now() + 15_000;
    let bWorkspace: any;
    while (Date.now() < deadline) {
      bWorkspace = await nodeB.query(
        `SELECT ?name WHERE { <${ENTITY_CTX_1}> <http://schema.org/name> ?name }`,
        { contextGraphId: CONTEXT_GRAPH, graphSuffix: '_shared_memory' },
      );
      if (bWorkspace.bindings.length > 0) break;
      await sleep(500);
    }
    expect(bWorkspace.bindings.length).toBe(1);
    expect(bWorkspace.bindings[0]['name']).toBe('"Context Graph Entity"');
  }, 25_000);

  it('A enshrines to context graph; A has data in context graph URI', async () => {
    const result = await nodeA.publishFromSharedMemory(
      CONTEXT_GRAPH,
      { rootEntities: [ENTITY_CTX_1] },
      { subContextGraphId: contextGraphId },
    );

    expect(result.status).toBe('confirmed');
    expect(result.ual).toBeDefined();

    const ctxDataGraph = `did:dkg:context-graph:${CONTEXT_GRAPH}/context/${contextGraphId}`;

    const aData = await nodeA.query(
      `SELECT ?name WHERE { GRAPH <${ctxDataGraph}> { <${ENTITY_CTX_1}> <http://schema.org/name> ?name } }`,
    );
    expect(aData.bindings.length).toBe(1);
    expect(aData.bindings[0]['name']).toBe('"Context Graph Entity"');

    // NOT in contextGraph data graph
    const aContextGraphData = await nodeA.query(
      `SELECT ?name WHERE { <${ENTITY_CTX_1}> <http://schema.org/name> ?name }`,
      CONTEXT_GRAPH,
    );
    expect(aContextGraphData.bindings.length).toBe(0);
  }, 30_000);

  it('B receives finalization and promotes to context graph', async () => {
    const ctxDataGraph = `did:dkg:context-graph:${CONTEXT_GRAPH}/context/${contextGraphId}`;

    const deadline = Date.now() + 20_000;
    let bData: any;
    while (Date.now() < deadline) {
      bData = await nodeB.query(
        `SELECT ?name WHERE { GRAPH <${ctxDataGraph}> { <${ENTITY_CTX_1}> <http://schema.org/name> ?name } }`,
      );
      if (bData.bindings.length > 0) break;
      await sleep(500);
    }

    expect(bData.bindings.length).toBe(1);
    expect(bData.bindings[0]['name']).toBe('"Context Graph Entity"');
  }, 30_000);

  it('B has confirmed metadata in context graph meta', async () => {
    const ctxMetaGraph = `did:dkg:context-graph:${CONTEXT_GRAPH}/context/${contextGraphId}/_meta`;

    const metaResult = await nodeB.query(
      `SELECT ?status WHERE { GRAPH <${ctxMetaGraph}> { ?kc <http://dkg.io/ontology/status> ?status } }`,
    );

    const statuses = metaResult.bindings.map((b: any) => String(b['status']));
    expect(statuses.some(s => s === '"confirmed"')).toBe(true);
  }, 10_000);

  it('B contextGraph data graph does NOT contain context graph data', async () => {
    const contextGraphData = await nodeB.query(
      `SELECT ?name WHERE { <${ENTITY_CTX_1}> <http://schema.org/name> ?name }`,
      CONTEXT_GRAPH,
    );
    expect(contextGraphData.bindings.length).toBe(0);
  }, 5_000);

  it('B workspace is cleaned up after promotion', async () => {
    const wsResult = await nodeB.query(
      `SELECT ?name WHERE { <${ENTITY_CTX_1}> <http://schema.org/name> ?name }`,
      { contextGraphId: CONTEXT_GRAPH, graphSuffix: '_shared_memory' },
    );
    expect(wsResult.bindings.length).toBe(0);
  }, 5_000);

  it('second enshrine to same context graph accumulates data', async () => {
    await nodeA.share(CONTEXT_GRAPH, [
      { subject: ENTITY_CTX_2, predicate: 'http://schema.org/name', object: '"Second Context Entity"', graph: '' },
    ]);

    // Wait for workspace replication
    const wsDeadline = Date.now() + 10_000;
    while (Date.now() < wsDeadline) {
      const ws = await nodeB.query(
        `SELECT ?name WHERE { <${ENTITY_CTX_2}> <http://schema.org/name> ?name }`,
        { contextGraphId: CONTEXT_GRAPH, graphSuffix: '_shared_memory' },
      );
      if (ws.bindings.length > 0) break;
      await sleep(500);
    }

    const result = await nodeA.publishFromSharedMemory(
      CONTEXT_GRAPH,
      { rootEntities: [ENTITY_CTX_2] },
      { subContextGraphId: contextGraphId, clearSharedMemoryAfter: true },
    );
    expect(result.status).toBe('confirmed');

    const ctxDataGraph = `did:dkg:context-graph:${CONTEXT_GRAPH}/context/${contextGraphId}`;

    // Both entities in context graph on A
    const data = await nodeA.query(
      `SELECT ?s ?name WHERE { GRAPH <${ctxDataGraph}> { ?s <http://schema.org/name> ?name } }`,
    );
    const names = data.bindings.map((b: any) => String(b['name']));
    expect(names.some((n: string) => n.includes('Context Graph Entity'))).toBe(true);
    expect(names.some((n: string) => n.includes('Second Context Entity'))).toBe(true);

    // A's workspace cleaned
    const ws = await nodeA.query(
      `SELECT ?name WHERE { <${ENTITY_CTX_2}> <http://schema.org/name> ?name }`,
      { contextGraphId: CONTEXT_GRAPH, graphSuffix: '_shared_memory' },
    );
    expect(ws.bindings.length).toBe(0);

    // Poll until B promotes
    const deadline = Date.now() + 20_000;
    let bData: any;
    while (Date.now() < deadline) {
      bData = await nodeB.query(
        `SELECT ?s ?name WHERE { GRAPH <${ctxDataGraph}> { ?s <http://schema.org/name> ?name } }`,
      );
      if (bData.bindings.length >= 2) break;
      await sleep(500);
    }
    const bNames = bData.bindings.map((b: any) => String(b['name']));
    expect(bNames.some((n: string) => n.includes('Context Graph Entity'))).toBe(true);
    expect(bNames.some((n: string) => n.includes('Second Context Entity'))).toBe(true);
  }, 60_000);
});
