/**
 * combined-model BASELINE (pre-catalog-injection) — curated e2e.
 *
 * The first end-to-end curated/private-CG VM publish on the devnet, via the
 * PRODUCT path (write -> promote -> publishFromFinalizedAssertion). Uses the
 * pre-staked genesis core nodes for the ACK quorum (lowered to 1), so the
 * curated `nodeExists` signer check passes.
 *
 * Baseline invariant: a private CG publishing N data entities yields a manifest
 * with exactly N data roots. The public DCAT catalog floor is still committed in
 * the KC Merkle root and served from `_catalog`, but it is not a user KA root.
 */
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import { mkdtempSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { makeTestKaNumberAllocator } from './_helpers/ka-allocator.js';
import { DKGAgent } from '../src/index.js';
import {
  spawnHardhatEnv, killHardhat, setMinimumRequiredSignatures, HARDHAT_KEYS, type HardhatContext,
} from '../../chain/test/hardhat-harness.js';

let ctx: HardhatContext;
const agents: DKGAgent[] = [];

function chainConfig(opKey: string, adminKey: string) {
  return { rpcUrl: ctx.rpcUrl, adminPrivateKey: adminKey, operationalKeys: [opKey], hubAddress: ctx.hubAddress, chainId: 'evm:31337' };
}

// A disk-backed dataDir is REQUIRED for SWM host mode — without it a core cannot
// store the curated ciphertext chunks and declines the ACK (MISSING_CIPHERTEXT_CHUNKS).
const mkDataDir = (name: string) => mkdtempSync(join(tmpdir(), `rfc49-${name}-`));

describe('baseline — curated CG product-path publish (pre-catalog)', () => {
  let publisher: DKGAgent;
  let curator: string;

  beforeAll(async () => {
    ctx = await spawnHardhatEnv(8553);
    await setMinimumRequiredSignatures(ctx.provider, ctx.hubAddress, HARDHAT_KEYS.DEPLOYER, 1);

    // Genesis core nodes are pre-staked. Publisher = CORE_OP (curator/member);
    // the connected REC1 core holds the curated ciphertext and supplies the ACK.
    publisher = await DKGAgent.create({
      kaNumberAllocator: makeTestKaNumberAllocator(), name: 'CatPublisher', nodeRole: 'core', listenPort: 0, skills: [],
      dataDir: mkDataDir('pub'),
      chainConfig: chainConfig(HARDHAT_KEYS.CORE_OP, HARDHAT_KEYS.CORE_ADMIN),
    });
    const ackCore = await DKGAgent.create({
      kaNumberAllocator: makeTestKaNumberAllocator(), name: 'CatAckCore', nodeRole: 'core', listenPort: 0, skills: [],
      dataDir: mkDataDir('ack'),
      chainConfig: chainConfig(HARDHAT_KEYS.REC1_OP, HARDHAT_KEYS.REC1_ADMIN),
    });
    agents.push(publisher, ackCore);
    await publisher.start(); await ackCore.start();
    await ackCore.connectTo(publisher.multiaddrs[0]);
    await new Promise((r) => setTimeout(r, 2000));
    curator = publisher.defaultAgentAddress ?? publisher.peerId;
  }, 180_000);

  afterAll(async () => {
    for (const a of agents) { try { await a.stop(); } catch {} }
    killHardhat(ctx);
  });

  it('a private CG publishes via the product path and yields exactly N data KAs', async () => {
    const CG = 'rfc49-baseline-private';
    await publisher.createContextGraph({ id: CG, name: 'RFC49 Baseline Private', accessPolicy: 1, callerAgentAddress: curator });
    await publisher.registerContextGraph(CG, { callerAgentAddress: curator });

    const name = 'shipment';
    await publisher.assertion.create(CG, name);
    await publisher.assertion.write(CG, name, [
      { subject: 'urn:acme:shipment/SH-42', predicate: 'urn:acme:product', object: '"P-9"' },
    ]);
    // Explicit finalize = the daemon product path (POST /vm/publish does
    // assertion.finalize then publishFromFinalizedAssertion). The catalog
    // injection lives in assertionFinalize and is re-injected as generated
    // catalog material at publish time, without becoming a manifest root.
    await publisher.assertion.finalize(CG, name);
    await publisher.assertion.promote(CG, name);

    const pub: any = await publisher.publishFromFinalizedAssertion(CG, name);

    expect(pub.status).toBe('confirmed');
    // combined model: the private publish carries only data roots in the KA
    // manifest. The generated catalog floor is committed in the CG's Merkle root
    // and public _catalog graph, but not sealed as a Rule-4-colliding root.
    expect(pub.kaManifest.length).toBe(1);
    const roots = pub.kaManifest.map((m: any) => String(m.rootEntity ?? m.entity ?? m.subject ?? ''));
    expect(roots).toEqual(['urn:acme:shipment/SH-42']);

    // The catalog entry persists PLAINTEXT in the public _catalog graph — a
    // queryable, standards-compliant DCAT dataset record (NOT encrypted, unlike
    // the private data which only exists as ciphertext chunks).
    const cgUal = `did:dkg:context-graph:${CG}`;
    const catalogGraph = `${cgUal}/_catalog`;
    const res: any = await (publisher as any).store.query(
      `SELECT ?p ?o WHERE { GRAPH <${catalogGraph}> { <${cgUal}> ?p ?o } }`,
    );
    expect(res.type).toBe('bindings');
    const triples = res.bindings.map((b: any) => ({ p: b.p, o: b.o }));
    const types = triples.filter((t: any) => t.p === 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type').map((t: any) => t.o);
    expect(types).toContain('http://www.w3.org/ns/dcat#Dataset');             // standards-compliant DCAT
    expect(types).toContain('https://dkg.network/ontology#PrivateContextGraph'); // dual-typed
    const accessRights = triples.find((t: any) => t.p === 'http://purl.org/dc/terms/accessRights');
    expect(accessRights?.o).toBe('http://publications.europa.eu/resource/authority/access-right/RESTRICTED');

    const secondName = 'shipment-2';
    await publisher.assertion.create(CG, secondName);
    await publisher.assertion.write(CG, secondName, [
      { subject: 'urn:acme:shipment/SH-43', predicate: 'urn:acme:product', object: '"P-10"' },
    ]);
    await publisher.assertion.finalize(CG, secondName);
    await publisher.assertion.promote(CG, secondName);

    const secondPub: any = await publisher.publishFromFinalizedAssertion(CG, secondName);
    expect(secondPub.status).toBe('confirmed');
    expect(secondPub.kaManifest.length).toBe(1);
    const secondRoots = secondPub.kaManifest.map((m: any) => String(m.rootEntity ?? m.entity ?? m.subject ?? ''));
    expect(secondRoots).toEqual(['urn:acme:shipment/SH-43']);
    expect(secondRoots).not.toContain(cgUal);

    const postSecondCatalog: any = await (publisher as any).store.query(
      `SELECT ?p ?o WHERE { GRAPH <${catalogGraph}> { <${cgUal}> ?p ?o } }`,
    );
    expect(postSecondCatalog.type).toBe('bindings');
    const postSecondTriples = postSecondCatalog.bindings.map((b: any) => ({ p: b.p, o: b.o }));
    expect(postSecondTriples.filter((t: any) => t.p === 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type').map((t: any) => t.o))
      .toEqual(expect.arrayContaining([
        'http://www.w3.org/ns/dcat#Dataset',
        'https://dkg.network/ontology#PrivateContextGraph',
      ]));
    expect(postSecondTriples.find((t: any) => t.p === 'http://purl.org/dc/terms/accessRights')?.o)
      .toBe('http://publications.europa.eu/resource/authority/access-right/RESTRICTED');
  });
});
