/**
 * Tests for src/utilities/updateNotifier.js — the CLI banner wrapper.
 *
 * The helper has four kill-switch paths and two normal paths. We
 * test each so a regression doesn't either spam users or silently
 * stop checking.
 */

import { describe, test, expect, jest } from '@jest/globals';
import {
  runUpdateCheck,
  isUpdateCheckDisabled,
  loadPackageJson,
} from '../updateNotifier.js';

describe('isUpdateCheckDisabled', () => {
  test('returns false on an empty env', () => {
    expect(isUpdateCheckDisabled({})).toBe(false);
  });

  test('honors LOXIA_DISABLE_UPDATE_CHECK', () => {
    expect(isUpdateCheckDisabled({ LOXIA_DISABLE_UPDATE_CHECK: '1' })).toBe(true);
    expect(isUpdateCheckDisabled({ LOXIA_DISABLE_UPDATE_CHECK: 'true' })).toBe(true);
  });

  test('honors NO_UPDATE_NOTIFIER (package convention)', () => {
    expect(isUpdateCheckDisabled({ NO_UPDATE_NOTIFIER: '1' })).toBe(true);
  });

  test('honors CI=true (avoids banner in pipelines)', () => {
    expect(isUpdateCheckDisabled({ CI: 'true' })).toBe(true);
  });

  test('treats "0" and "false" as not-disabled (escape hatch for misuse)', () => {
    expect(isUpdateCheckDisabled({ LOXIA_DISABLE_UPDATE_CHECK: '0' })).toBe(false);
    expect(isUpdateCheckDisabled({ LOXIA_DISABLE_UPDATE_CHECK: 'false' })).toBe(false);
  });
});

describe('loadPackageJson', () => {
  test('finds the CLI package.json by walking up from a starting dir', () => {
    // The helper looks for a pkg.json with `bin` OR `@loxia-labs/` name —
    // both true of this repo's root pkg.json.
    const pkg = loadPackageJson();
    expect(pkg).not.toBeNull();
    expect(pkg.name).toMatch(/loxia/i);
    expect(typeof pkg.version).toBe('string');
  });

  test('returns null when no qualifying package.json exists in the chain', () => {
    // Inject a stub that always returns ENOENT.
    const fs = {
      readFileSync: () => { throw Object.assign(new Error('nope'), { code: 'ENOENT' }); },
    };
    expect(loadPackageJson({ startDir: '/tmp', fs })).toBeNull();
  });
});

describe('runUpdateCheck — kill-switches', () => {
  test('returns null when disabled by env', async () => {
    const importImpl = jest.fn();
    const r = await runUpdateCheck({
      env: { LOXIA_DISABLE_UPDATE_CHECK: '1' },
      pkg: { name: 'x', version: '1.0.0' },
      importImpl,
    });
    expect(r).toBeNull();
    expect(importImpl).not.toHaveBeenCalled();
  });

  test('returns null when pkg.json could not be loaded', async () => {
    const importImpl = jest.fn();
    const r = await runUpdateCheck({
      env: {},
      pkg: null,
      importImpl,
    });
    expect(r).toBeNull();
    expect(importImpl).not.toHaveBeenCalled();
  });
});

describe('runUpdateCheck — dependency tolerance', () => {
  test('returns null when update-notifier is not installed', async () => {
    const importImpl = jest.fn().mockRejectedValue(new Error('Cannot find module'));
    const r = await runUpdateCheck({
      env: {},
      pkg: { name: 'x', version: '1.0.0' },
      importImpl,
    });
    expect(r).toBeNull();
  });

  test('returns null when the module exists but has the wrong shape', async () => {
    const importImpl = jest.fn().mockResolvedValue({ /* no default, no callable */ });
    const r = await runUpdateCheck({
      env: {},
      pkg: { name: 'x', version: '1.0.0' },
      importImpl,
    });
    expect(r).toBeNull();
  });
});

describe('runUpdateCheck — happy path', () => {
  test('invokes update-notifier with the supplied pkg and calls notify()', async () => {
    const notifyMock = jest.fn();
    const updateNotifierFn = jest.fn().mockReturnValue({ notify: notifyMock });
    const importImpl = jest.fn().mockResolvedValue({ default: updateNotifierFn });

    const pkg = { name: '@loxia-labs/loxia-autopilot-one', version: '4.6.4' };
    const result = await runUpdateCheck({ env: {}, pkg, importImpl });

    expect(result).toEqual({ notify: notifyMock });
    expect(updateNotifierFn).toHaveBeenCalledTimes(1);
    const call = updateNotifierFn.mock.calls[0][0];
    expect(call.pkg).toBe(pkg);
    expect(typeof call.updateCheckInterval).toBe('number');
    expect(notifyMock).toHaveBeenCalledTimes(1);

    const notifyOpts = notifyMock.mock.calls[0][0];
    expect(notifyOpts.isGlobal).toBe(true);
    expect(notifyOpts.message).toContain('{packageName}');
  });

  test('a throwing notifier does not surface — returns null', async () => {
    const updateNotifierFn = jest.fn().mockImplementation(() => { throw new Error('boom'); });
    const importImpl = jest.fn().mockResolvedValue({ default: updateNotifierFn });
    const logger = { debug: jest.fn() };
    const r = await runUpdateCheck({
      env: {},
      pkg: { name: 'x', version: '1.0.0' },
      importImpl,
      logger,
    });
    expect(r).toBeNull();
    expect(logger.debug).toHaveBeenCalled();
  });
});
