/* eslint-disable @typescript-eslint/no-explicit-any */
/*
 * Copyright (C) 2018, 2020 StApps
 * This program is free software: you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation, version 3.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program. If not, see <https://www.gnu.org/licenses/>.
 */
import chai from 'chai';
import {expect} from 'chai';
import chaiAsPromised from 'chai-as-promised';
import chaiSpies from 'chai-spies';
import {Logger, AddLogLevel, Colorize} from '../src/index.js';
import {DummyTransport} from './dummy-transport.js';
import path from 'node:path';
import chalk from 'chalk';

chai.should();
chai.use(chaiSpies);
chai.use(chaiAsPromised);

chalk.level = 2;

describe('Logger', function () {
  const sandbox = chai.spy.sandbox();

  beforeEach(function () {
    Logger.setTransformations([new AddLogLevel()]);
  });

  afterEach(function () {
    sandbox.restore();
  });

  it('should read default log level', async function () {
    expect((Logger as any).getLevel('LOG')).to.be.equal(31);
    expect((Logger as any).getLevel('EXIT')).to.be.equal(0);
  });

  it('should error', async function () {
    const spy = sandbox.on(console, 'error', () => {
      // noop
    });

    await Logger.error('Foobar');

    expect(spy).to.have.been.called();
    expect(spy.__spy.calls[0][0]).to.contain('[ERROR]');
    expect(spy.__spy.calls[0][0]).to.contain('Foobar');
  });

  it('should error in productive environment', async function () {
    const spy = sandbox.on(console, 'error', () => {
      // noop
    });

    const nodeEnvironment = process.env.NODE_ENV;
    process.env.NODE_ENV = 'production';

    await Logger.error('Foobar').should.be.rejectedWith(Error);
    expect(spy).to.have.been.called.exactly(1);

    process.env.ALLOW_NO_TRANSPORT = 'true';

    await Logger.error('Foobar').should.not.be.rejectedWith(Error);
    expect(spy).to.have.been.called.exactly(2);

    delete process.env.ALLOW_NO_TRANSPORT;

    Logger.setTransport(new DummyTransport());

    await Logger.error('Foobar').should.not.be.rejectedWith(Error);
    expect(spy).to.have.been.called.exactly(3);

    Logger.setTransport();

    process.env.NODE_ENV = nodeEnvironment;
  });

  it('should error without output', async function () {
    const spy = sandbox.on(console, 'error', () => {
      // noop
    });

    process.env.STAPPS_LOG_LEVEL = '0';

    await Logger.error('Foobar');

    delete process.env.STAPPS_LOG_LEVEL;

    expect(spy).not.to.have.been.called();
  });

  it('should error with Error', async function () {
    const spy = sandbox.on(console, 'error', () => {
      // noop
    });

    // eslint-disable-next-line unicorn/error-message
    await Logger.error(new Error());

    expect(spy).to.have.been.called();
    expect(spy.__spy.calls[0][0]).to.contain('Error');
    const directory = process.cwd().replaceAll(path.sep, path.posix.sep);
    expect(spy.__spy.calls[0][0]).to.contain(directory);
  });

  it('should info', function () {
    const spy = sandbox.on(console, 'info', () => {
      // noop
    });

    Logger.info('Foobar');

    expect(spy).to.have.been.called();
    expect(spy.__spy.calls[0][0]).to.contain('[INFO]');
    expect(spy.__spy.calls[0][0]).to.contain('Foobar');
  });

  it('should exit', async function () {
    const infoSpy = sandbox.on(console, 'info', () => {
      // noop
    });
    const logSpy = sandbox.on(console, 'log', () => {
      // noop
    });
    const warnSpy = sandbox.on(console, 'warn', () => {
      // noop
    });
    const errorSpy = sandbox.on(console, 'error', () => {
      // noop
    });
    const processSpy = sandbox.on(process, 'exit', () => {
      // noop
    });

    const exitLevel = process.env.STAPPS_EXIT_LEVEL;
    process.env.STAPPS_EXIT_LEVEL = '31';

    Logger.info('Foobar');
    Logger.log('Foobar');
    Logger.warn('Foobar');
    Logger.ok('Foobar');
    await Logger.error('Foobar');

    expect(infoSpy).to.have.been.called.once;
    expect(infoSpy.__spy.calls[0][0]).to.contain('[INFO]');
    expect(infoSpy.__spy.calls[0][0]).to.contain('Foobar');

    expect(logSpy).to.have.been.called.twice;

    expect(warnSpy).to.have.been.called.once;
    expect(warnSpy.__spy.calls[0][0]).to.contain('[WARN]');
    expect(warnSpy.__spy.calls[0][0]).to.contain('Foobar');

    expect(errorSpy).to.have.been.called.exactly(6);

    expect(processSpy).to.have.been.called.exactly(5);

    process.env.STAPPS_EXIT_LEVEL = exitLevel;
  });

  it('should info without output', function () {
    const spy = sandbox.on(console, 'info', () => {
      // noop
    });

    process.env.STAPPS_LOG_LEVEL = '0';

    Logger.info('Foobar');

    delete process.env.STAPPS_LOG_LEVEL;

    expect(spy).not.to.have.been.called;
  });

  it('should be initialized', function () {
    Logger.setTransport(new DummyTransport());

    expect(() => {
      Logger.initialized();
    }).not.to.throw();

    Logger.setTransport();
  });

  it('should be initialized in productive environment', function () {
    const nodeEnvironment = process.env.NODE_ENV;
    process.env.NODE_ENV = 'production';

    Logger.setTransport(new DummyTransport());

    expect(() => {
      Logger.initialized();
    }).not.to.throw();

    Logger.setTransport();

    expect(() => {
      Logger.initialized();
    }).to.throw();

    const spy = sandbox.on(console, 'warn', () => {
      // noop
    });

    process.env.ALLOW_NO_TRANSPORT = 'true';

    expect(() => {
      Logger.initialized();
    }).not.to.throw();

    delete process.env.ALLOW_NO_TRANSPORT;

    expect(spy).to.have.been.called();

    process.env.NODE_ENV = nodeEnvironment;
  });

  it('should be compatible with log aggregation in productive environment', function () {
    Logger.setTransformations([new AddLogLevel(), new Colorize()]);

    const spy = sandbox.on(console, 'log', () => {
      // noop
    });

    Logger.log('Foo\nbar');

    expect(spy).to.have.been.called.once;
    expect(spy.__spy.calls[0][0]).to.equal('\u001B[37m[LOG] Foo\u001B[39m\n\u001B[37mbar\u001B[39m');

    const nodeEnvironment = process.env.NODE_ENV;
    process.env.NODE_ENV = 'production';
    process.env.ALLOW_NO_TRANSPORT = 'true';

    Logger.setTransformations([new AddLogLevel(), new Colorize()]);

    Logger.log('Foo\nbar');

    expect(spy).to.have.been.called.twice;
    expect(spy.__spy.calls[1][0]).to.equal('[LOG] Foo bar');

    process.env.NODE_ENV = nodeEnvironment;
    delete process.env.ALLOW_NO_TRANSPORT;
  });

  it('should log', function () {
    const spy = sandbox.on(console, 'log', () => {
      // noop
    });

    Logger.log('Foobar');

    expect(spy).to.have.been.called();
    expect(spy.__spy.calls[0][0]).to.contain('[LOG]');
    expect(spy.__spy.calls[0][0]).to.contain('Foobar');
  });

  it('should log without output', function () {
    const spy = sandbox.on(console, 'log', () => {
      // noop
    });

    process.env.STAPPS_LOG_LEVEL = '0';

    Logger.log('Foobar');

    delete process.env.STAPPS_LOG_LEVEL;

    expect(spy).to.not.have.been.called();
  });

  it('should ok', function () {
    const spy = sandbox.on(console, 'log', () => {
      // noop
    });

    Logger.ok('Foobar');

    expect(spy).to.have.been.called();
    expect(spy.__spy.calls[0][0]).to.contain('[OK]');
    expect(spy.__spy.calls[0][0]).to.contain('Foobar');
  });

  it('should ok without output', function () {
    const spy = sandbox.on(console, 'log', () => {
      // noop
    });

    process.env.STAPPS_LOG_LEVEL = '0';

    Logger.ok('Foobar');

    delete process.env.STAPPS_LOG_LEVEL;

    expect(spy).not.to.have.been.called();
  });

  it('should set transport', function () {
    expect(() => {
      Logger.setTransport(new DummyTransport());
      Logger.setTransport();
    }).not.to.throw();
  });

  it('should stringify', function () {
    const spy = sandbox.on(console, 'log', () => {
      // noop
    });

    Logger.log('foo', 'bar');

    expect(spy).to.have.been.called();
    expect(spy.__spy.calls[0][0]).to.contain('foo');
    expect(spy.__spy.calls[0][0]).to.contain('bar');
  });

  it('should stringify object', function () {
    const spy = sandbox.on(console, 'log', () => {
      // noop
    });

    Logger.log({
      foo: 'bar',
    });

    expect(spy).to.have.been.called();
    expect(spy.__spy.calls[0][0]).to.contain('foo');
    expect(spy.__spy.calls[0][0]).to.contain('bar');
  });

  it('should warn', function () {
    const spy = sandbox.on(console, 'warn', () => {
      // noop
    });

    Logger.warn('Foobar');

    expect(spy).to.have.been.called();
    expect(spy.__spy.calls[0][0]).to.contain('[WARN]');
    expect(spy.__spy.calls[0][0]).to.contain('Foobar');
  });

  it('should warn without output', function () {
    const spy = sandbox.on(console, 'warn', () => {
      // noop
    });

    process.env.STAPPS_LOG_LEVEL = '0';

    Logger.warn('Foobar');

    delete process.env.STAPPS_LOG_LEVEL;

    expect(spy).not.to.have.been.called();
  });

  it('should log level exclusiveness', function () {
    const warnSpy = sandbox.on(console, 'warn', () => {
      // noop WARN
    });
    const infoSpy = sandbox.on(console, 'info', () => {
      // noop INFO
    });
    const okSpy = sandbox.on(console, 'log', () => {
      // noop OK
    });

    // only warn and info = warn + info = 4 + 1 = 5
    process.env.STAPPS_LOG_LEVEL = '5';

    Logger.warn('Foo');
    Logger.info('Bar');
    Logger.ok('Baz');

    expect(warnSpy).to.have.been.called();
    expect(warnSpy.__spy.calls[0][0]).to.contain('[WARN]');
    expect(warnSpy.__spy.calls[0][0]).to.contain('Foo');

    expect(infoSpy).to.have.been.called();
    expect(infoSpy.__spy.calls[0][0]).to.contain('[INFO]');
    expect(infoSpy.__spy.calls[0][0]).to.contain('Bar');

    expect(okSpy).to.not.have.been.called();

    delete process.env.STAPPS_LOG_LEVEL;
  });

  it('should getExitLevel', function () {
    const savedProcess = process;

    // @ts-expect-error ignore
    process = undefined;

    (global as any).window = {
      STAPPS_EXIT_LEVEL: 0,
    };

    const stub = sandbox.on(console, 'info', () => {
      // noop
    });

    Logger.info('Foobar');

    process = savedProcess;

    delete (global as any).window;

    expect(stub).not.to.have.been.called();
  });

  it('should getLogLevel', function () {
    const savedProcess = process;

    // @ts-expect-error ignore
    process = undefined;

    (global as any).window = {
      STAPPS_LOG_LEVEL: 0,
    };

    const stub = sandbox.on(console, 'info', () => {
      // noop
    });

    Logger.info('Foobar');

    process = savedProcess;

    delete (global as any).window;

    expect(stub).not.to.have.been.called();
  });

  it('should output without transformations', function () {
    Logger.setTransformations([]);

    const stub = sandbox.on(console, 'log', () => {
      // noop
    });
    const applyTransformationsSpy = sandbox.on(new Logger(), 'applyTransformations');

    Logger.log('Foobar');

    expect(stub).to.have.been.called.with('Foobar');
    expect(applyTransformationsSpy).not.to.have.been.called;
  });
});
