/*
 * Copyright The OpenTelemetry Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import * as assert from 'assert';
import * as sinon from 'sinon';
import { getGlobal } from '../../../src/internal/global-utils';
import { _globalThis } from '../../../src/platform';
import { NoopContextManager } from '../../../src/context/NoopContextManager';
import { DiagLogLevel } from '../../../src/diag/types';

const api1 = require('../../../src') as typeof import('../../../src');

// clear cache and load a second instance of the api
for (const key of Object.keys(require.cache)) {
  delete require.cache[key];
}
const api2 = require('../../../src') as typeof import('../../../src');

// This will need to be changed manually on major version changes.
// It is intentionally not autogenerated to ensure the author of the change is aware of what they are doing.
const GLOBAL_API_SYMBOL_KEY = 'opentelemetry.js.api.1';

const getMockLogger = () => ({
  verbose: sinon.spy(),
  debug: sinon.spy(),
  info: sinon.spy(),
  warn: sinon.spy(),
  error: sinon.spy(),
});

describe('Global Utils', function () {
  // prove they are separate instances
  assert.notEqual(api1, api2);
  // that return separate noop instances to start
  assert.notStrictEqual(
    api1.context['_getContextManager'](),
    api2.context['_getContextManager']()
  );

  beforeEach(() => {
    api1.context.disable();
    api1.propagation.disable();
    api1.trace.disable();
    api1.diag.disable();
    // @ts-expect-error we are modifying internals for testing purposes here
    delete _globalThis[Symbol.for(GLOBAL_API_SYMBOL_KEY)];
  });

  it('should change the global context manager', function () {
    const original = api1.context['_getContextManager']();
    const newContextManager = new NoopContextManager();
    api1.context.setGlobalContextManager(newContextManager);
    assert.notStrictEqual(api1.context['_getContextManager'](), original);
    assert.strictEqual(api1.context['_getContextManager'](), newContextManager);
  });

  it('should load an instance from one which was set in the other', function () {
    api1.context.setGlobalContextManager(new NoopContextManager());
    assert.strictEqual(
      api1.context['_getContextManager'](),
      api2.context['_getContextManager']()
    );
  });

  it('should disable both if one is disabled', function () {
    const manager = new NoopContextManager();
    api1.context.setGlobalContextManager(manager);

    assert.strictEqual(manager, api1.context['_getContextManager']());
    api2.context.disable();
    assert.notStrictEqual(manager, api1.context['_getContextManager']());
  });

  it('should not register if the version is a mismatch', function () {
    const logger1 = getMockLogger();
    const logger2 = getMockLogger();

    api1.diag.setLogger(logger1);
    const globalInstance = getGlobal('diag');
    assert.ok(globalInstance);
    // @ts-expect-error we are modifying internals for testing purposes here
    _globalThis[Symbol.for(GLOBAL_API_SYMBOL_KEY)].version = '0.0.1';

    assert.equal(false, api1.diag.setLogger(logger2)); // won't happen

    api1.diag.info('message');
    sinon.assert.notCalled(logger2.error);
    sinon.assert.notCalled(logger2.info);
    sinon.assert.notCalled(logger2.warn);
  });

  it('should debug log registrations', function () {
    const logger = getMockLogger();
    api1.diag.setLogger(logger, DiagLogLevel.DEBUG);

    const newContextManager = new NoopContextManager();
    api1.context.setGlobalContextManager(newContextManager);

    sinon.assert.calledWith(logger.debug, sinon.match(/global for context/));
    sinon.assert.calledWith(logger.debug, sinon.match(/global for diag/));
    sinon.assert.calledTwice(logger.debug);
  });

  it('should log an error if there is a duplicate registration', function () {
    const logger = getMockLogger();
    api1.diag.setLogger(logger);

    api1.context.setGlobalContextManager(new NoopContextManager());
    api1.context.setGlobalContextManager(new NoopContextManager());

    sinon.assert.calledOnce(logger.error);
    assert.strictEqual(logger.error.firstCall.args.length, 1);
    assert.ok(
      logger.error.firstCall.args[0].startsWith(
        'Error: @opentelemetry/api: Attempted duplicate registration of API: context'
      )
    );
  });

  it('should allow duplicate registration of the diag logger', function () {
    const logger1 = getMockLogger();
    const logger2 = getMockLogger();

    api1.diag.setLogger(logger1);
    api1.diag.setLogger(logger2);

    const MSG = '__log message__';
    api1.diag.info(MSG);

    sinon.assert.notCalled(logger1.error);
    sinon.assert.notCalled(logger1.info);
    sinon.assert.calledOnce(logger1.warn);
    sinon.assert.calledWith(logger1.warn, sinon.match(/will be overwritten/i));

    sinon.assert.notCalled(logger2.error);
    sinon.assert.calledOnce(logger2.warn);
    sinon.assert.calledWith(logger2.warn, sinon.match(/will overwrite/i));
    sinon.assert.calledOnce(logger2.info);
    sinon.assert.calledWith(logger2.info, MSG);
  });
});
