/**
 * Full build parity test.
 *
 * Build the same fixture project with both WASM and native engines,
 * then compare the resulting nodes/edges in SQLite.
 *
 * IMPORTANT: Every feature MUST be implemented for BOTH engines (WASM and native).
 * This test is a hard gate — if it fails, the feature is incomplete. Do not weaken,
 * skip, or filter this test to work around missing engine parity. Fix the code instead.
 *
 * Skipped when the native engine is not installed.
 */

import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import Database from 'better-sqlite3';
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
import { buildGraph } from '../../src/domain/graph/builder.js';
import { isNativeAvailable } from '../../src/infrastructure/native.js';

const FIXTURE_DIR = path.join(import.meta.dirname, '..', 'fixtures', 'sample-project');

const hasNative = isNativeAvailable();
// In the dedicated parity CI job (CODEGRAPH_PARITY=1), never silently skip —
// fail hard so a missing native addon is immediately visible.
const requireParity = !!process.env.CODEGRAPH_PARITY;
const describeOrSkip = requireParity || hasNative ? describe : describe.skip;

function copyDirSync(src, dest) {
  fs.mkdirSync(dest, { recursive: true });
  for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
    const s = path.join(src, entry.name);
    const d = path.join(dest, entry.name);
    if (entry.isDirectory()) copyDirSync(s, d);
    else fs.copyFileSync(s, d);
  }
}

function readGraph(dbPath) {
  const db = new Database(dbPath, { readonly: true });
  const nodes = db
    .prepare('SELECT name, kind, file, line FROM nodes ORDER BY name, kind, file, line')
    .all();
  const edges = db
    .prepare(`
    SELECT n1.name AS source_name, n2.name AS target_name, e.kind
    FROM edges e
    JOIN nodes n1 ON e.source_id = n1.id
    JOIN nodes n2 ON e.target_id = n2.id
    ORDER BY n1.name, n2.name, e.kind
  `)
    .all();
  const roles = db
    .prepare('SELECT name, role FROM nodes WHERE role IS NOT NULL ORDER BY name, role')
    .all();

  // ast_nodes may not exist on older schemas — read if available
  let astNodes: unknown[] = [];
  try {
    astNodes = db
      .prepare('SELECT file, line, kind, name FROM ast_nodes ORDER BY file, line, kind, name')
      .all();
  } catch {
    /* table may not exist */
  }

  db.close();
  return { nodes, edges, roles, astNodes };
}

describeOrSkip('Build parity: native vs WASM', () => {
  let wasmDir: string;
  let nativeDir: string;

  beforeAll(async () => {
    // Create two temp copies of the fixture
    const tmpBase = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-parity-'));
    wasmDir = path.join(tmpBase, 'wasm');
    nativeDir = path.join(tmpBase, 'native');
    copyDirSync(FIXTURE_DIR, wasmDir);
    copyDirSync(FIXTURE_DIR, nativeDir);

    // Build with WASM
    await buildGraph(wasmDir, { engine: 'wasm', incremental: false, skipRegistry: true });
    // Build with native
    await buildGraph(nativeDir, { engine: 'native', incremental: false, skipRegistry: true });
  }, 60_000);

  afterAll(() => {
    // Cleanup
    try {
      if (wasmDir) fs.rmSync(path.dirname(wasmDir), { recursive: true, force: true });
    } catch {
      /* ignore */
    }
  });

  it('produces identical nodes', () => {
    const wasmGraph = readGraph(path.join(wasmDir, '.codegraph', 'graph.db'));
    const nativeGraph = readGraph(path.join(nativeDir, '.codegraph', 'graph.db'));
    expect(nativeGraph.nodes).toEqual(wasmGraph.nodes);
  });

  it('produces identical edges', () => {
    const wasmGraph = readGraph(path.join(wasmDir, '.codegraph', 'graph.db'));
    const nativeGraph = readGraph(path.join(nativeDir, '.codegraph', 'graph.db'));
    expect(nativeGraph.edges).toEqual(wasmGraph.edges);
  });

  it('produces identical roles', () => {
    const wasmGraph = readGraph(path.join(wasmDir, '.codegraph', 'graph.db'));
    const nativeGraph = readGraph(path.join(nativeDir, '.codegraph', 'graph.db'));
    expect(nativeGraph.roles).toEqual(wasmGraph.roles);
  });

  it('produces identical ast_nodes', () => {
    const wasmGraph = readGraph(path.join(wasmDir, '.codegraph', 'graph.db'));
    const nativeGraph = readGraph(path.join(nativeDir, '.codegraph', 'graph.db'));
    expect(nativeGraph.astNodes).toEqual(wasmGraph.astNodes);
  });
});
