import { describe, it, expect, vi, afterEach } from 'vitest';

/**
 * Tests for the #307 pnpm-strict / `pnpm dlx` fix: a synchronous in-thread ESM
 * resolution hook (`module.registerHooks`) that redirects @huggingface/
 * transformers' phantom `onnxruntime-common` import to gitnexus' own copy.
 *
 * Each test mocks `node:module` with a chosen `registerHooks` (a spy, or
 * `undefined` to simulate Node < 22.15) so we can assert one-shot installation,
 * graceful degradation, and the redirect/passthrough/rethrow logic of the
 * resolve closure — without mutating the real process loader.
 */

const RESOLVER = '../../src/core/embeddings/onnxruntime-common-resolver.js';

/**
 * (Re)load the resolver with a chosen `registerHooks` mocked into node:module.
 * `vi.resetModules()` + the fresh `import()` re-initialises the module-level
 * one-shot guard, so each test gets a pristine resolver with no shared state.
 */
async function loadResolver(registerHooks: unknown) {
  vi.resetModules();
  vi.doMock('node:module', async (importOriginal) => {
    const orig = await importOriginal<typeof import('node:module')>();
    return { ...orig, registerHooks };
  });
  return import(RESOLVER);
}

const ctx = { conditions: [], importAttributes: {} } as never;
const moduleNotFound = (): Error => {
  const e = new Error("Cannot find package 'onnxruntime-common'") as Error & { code: string };
  e.code = 'ERR_MODULE_NOT_FOUND';
  return e;
};

afterEach(() => {
  vi.doUnmock('node:module');
});

describe('ensureOnnxRuntimeCommonResolvable — installation', () => {
  it('installs the resolve hook exactly once (idempotent)', async () => {
    const spy = vi.fn();
    const mod = await loadResolver(spy);

    mod.ensureOnnxRuntimeCommonResolvable();
    mod.ensureOnnxRuntimeCommonResolvable(); // second call is a no-op

    expect(spy).toHaveBeenCalledTimes(1);
    expect(typeof spy.mock.calls[0][0].resolve).toBe('function');
  });

  it('no-ops gracefully when registerHooks is unavailable (Node < 22.15)', async () => {
    const mod = await loadResolver(undefined);
    // Must not throw even though there is no synchronous-hooks API to call.
    expect(() => mod.ensureOnnxRuntimeCommonResolvable()).not.toThrow();
  });

  it('is best-effort: swallows a registerHooks() failure instead of throwing into the embedder', async () => {
    const mod = await loadResolver(
      vi.fn(() => {
        throw new Error('hook-install-failed');
      }),
    );
    // The call site (initEmbedder) does not guard the return; a throw here would
    // break `analyze --embeddings`. The outer try/catch must absorb it.
    expect(() => mod.ensureOnnxRuntimeCommonResolvable()).not.toThrow();
  });
});

describe('ensureOnnxRuntimeCommonResolvable — resolve hook behaviour', () => {
  /** Install the fallback and return the resolve closure handed to registerHooks. */
  async function captureResolve() {
    const spy = vi.fn();
    const mod = await loadResolver(spy);
    mod.ensureOnnxRuntimeCommonResolvable();
    return spy.mock.calls[0][0].resolve as (
      s: string,
      c: never,
      n: (s: string, c: never) => unknown,
    ) => unknown;
  }

  it('passes a successful default resolution through unchanged (no-op on hoisted layouts)', async () => {
    const resolve = await captureResolve();
    const real = { url: 'file:///real/onnxruntime-common/index.js', shortCircuit: true };
    const next = vi.fn(() => real);

    const res = resolve('onnxruntime-common', ctx, next);

    expect(next).toHaveBeenCalledTimes(1);
    expect(res).toBe(real); // the real resolution, NOT a redirect
  });

  it('redirects onnxruntime-common to the gitnexus copy when default resolution fails', async () => {
    const resolve = await captureResolve();
    const next = vi.fn(() => {
      throw moduleNotFound();
    });

    const res = resolve('onnxruntime-common', ctx, next) as { url: string; shortCircuit: boolean };

    expect(res.shortCircuit).toBe(true);
    // The real resolved onnxruntime-common in node_modules (require.resolve runs
    // for real here) — not just any path containing the substring.
    expect(res.url).toMatch(/^file:\/\/.*\/node_modules\/onnxruntime-common\/.*\.js$/);
  });

  it('never masks an unrelated resolution failure (other specifiers rethrow)', async () => {
    const resolve = await captureResolve();
    const err = moduleNotFound();
    const next = vi.fn(() => {
      throw err;
    });

    expect(() => resolve('some-other-package', ctx, next)).toThrow(err);
  });

  it('rethrows when onnxruntime-common fails for a non-absence reason', async () => {
    const resolve = await captureResolve();
    // A present-but-otherwise-broken resolution (not a missing package) must
    // surface, not be silently papered over with gitnexus' copy.
    const err = Object.assign(new Error('bad specifier'), {
      code: 'ERR_INVALID_MODULE_SPECIFIER',
    });
    const next = vi.fn(() => {
      throw err;
    });

    expect(() => resolve('onnxruntime-common', ctx, next)).toThrow(err);
  });
});
