/**
 * #2798 — SCHEMA_FINGERPRINT, the derived half of the incremental reuse gate.
 *
 * The replaced `INCREMENTAL_SCHEMA_VERSION` was hand-picked and had to PREDICT whether an
 * on-disk database was created from this build's DDL. It clashed exactly
 * with a concurrently-merged branch twice, and the gate was a strict `===`, so
 * such an index read as current while its tables physically could not hold the
 * edges the build emitted. The fingerprint derives that fact instead, and is now
 * the only schema gate.
 *
 * These tests pin the three properties the gate depends on:
 *   1. it covers the DDL that is actually executed (input-set pinning);
 *   2. it is a function of CODE, never of the environment;
 *   3. it moves when any covered DDL string moves.
 */

import { describe, it, expect } from 'vitest';
import { createHash } from 'node:crypto';
import {
  NODE_SCHEMA_QUERIES,
  REL_SCHEMA_QUERIES,
  SCHEMA_FINGERPRINT,
  SCHEMA_QUERIES,
  EMBEDDING_SCHEMA,
} from '../../src/core/lbug/schema.js';

const digest = (input: string): string =>
  createHash('sha256').update(input).digest('hex').slice(0, 12);

describe('SCHEMA_FINGERPRINT (#2798)', () => {
  it('is a 12-char lowercase hex digest, matching the taintModelVersion shape', () => {
    expect(SCHEMA_FINGERPRINT).toMatch(/^[0-9a-f]{12}$/);
  });

  it('covers exactly the node + relation DDL this build creates', () => {
    // Recomputed from the exported lists rather than hardcoded, so the
    // assertion pins the INPUT SET, not a literal. Adding a table to
    // NODE_SCHEMA_QUERIES (or a FROM/TO pair to RELATION_SCHEMA) without the
    // fingerprint moving becomes impossible.
    expect(SCHEMA_FINGERPRINT).toBe(
      digest([...NODE_SCHEMA_QUERIES, ...REL_SCHEMA_QUERIES].join('\n')),
    );
  });

  it('covers every DDL statement init executes, bar the one documented exclusion', () => {
    // Ties the fingerprint's input to SCHEMA_QUERIES — the array
    // `runSchemaCreationQueries` iterates, i.e. the DDL that actually reaches
    // the database. Without this a FOURTH member could join SCHEMA_QUERIES, go
    // unfingerprinted, and be skipped as "already exists" against an index the
    // gate waved through: a wrong graph, no error.
    //
    // EMBEDDING_SCHEMA is the one intentional exclusion — its FLOAT[N] width
    // comes from GITNEXUS_EMBEDDING_DIMS at module load, so folding it in would
    // make the digest a function of the environment. Do not "fix" that by
    // adding it to the fingerprint; the second assertion keeps the exclusion
    // honest rather than vacuous.
    const fingerprintInput = [...NODE_SCHEMA_QUERIES, ...REL_SCHEMA_QUERIES];
    expect(new Set(SCHEMA_QUERIES)).toEqual(new Set([...fingerprintInput, EMBEDDING_SCHEMA]));
    expect(fingerprintInput).not.toContain(EMBEDDING_SCHEMA);
  });

  it('does not fold in EMBEDDING_SCHEMA, whose width is environment-derived', () => {
    // EMBEDDING_SCHEMA carries FLOAT[GITNEXUS_EMBEDDING_DIMS]. Including it
    // would make the fingerprint a function of the environment: the same build
    // under two dims values would disagree and thrash full rebuilds. Proven by
    // construction — appending it changes the digest, so its absence from
    // SCHEMA_FINGERPRINT is load-bearing rather than incidental.
    const withEmbedding = digest(
      [...NODE_SCHEMA_QUERIES, ...REL_SCHEMA_QUERIES, EMBEDDING_SCHEMA].join('\n'),
    );
    expect(withEmbedding).not.toBe(SCHEMA_FINGERPRINT);
  });

  it('moves when a relation FROM/TO pair is added', () => {
    // The #2798 failure shape: v32, v33 and #2781 each added pairs, and #2793
    // regenerated the whole block. Any such change must alter the digest even
    // when the version integer does not.
    const withExtraPair = digest(
      [
        ...NODE_SCHEMA_QUERIES,
        ...REL_SCHEMA_QUERIES.map((ddl) =>
          ddl.replace('  type STRING,', '  FROM `Record` TO `Tool`,\n  type STRING,'),
        ),
      ].join('\n'),
    );
    expect(withExtraPair).not.toBe(SCHEMA_FINGERPRINT);
  });

  it('moves when a node table gains a column', () => {
    const withExtraColumn = digest(
      [
        ...NODE_SCHEMA_QUERIES.map((ddl) =>
          ddl.replace('  id STRING,', '  id STRING,\n  probe STRING,'),
        ),
        ...REL_SCHEMA_QUERIES,
      ].join('\n'),
    );
    expect(withExtraColumn).not.toBe(SCHEMA_FINGERPRINT);
  });
});
