import rewiremock from 'rewiremock';
import '../../../../mocks/electron';

import chai, { expect } from 'chai';
import 'mocha';
import { container } from '../../core/container';
import { setDev } from '../../core/env.spec';
import { ILogger } from './i-logger';
import fs from 'fs-extra';
import { createInterface, Interface } from 'readline';
import fc from 'fast-check';
import { LogLevel } from './log-level';
import chaiFs from 'chai-fs';

chai.use(chaiFs);

describe('Logger Service', () => {
  function getLastLine(rl: Interface): Promise<string> {
    return new Promise((resolve) => {
      let lastLine = '';
      rl.on('line', (line) => {
        lastLine = line;
      });

      rl.on('close', () => {
        resolve(lastLine);
      });
    });
  }

  function getDefaultLogReadLineInterface(logger: ILogger) {
    return createInterface(fs.createReadStream(logger.getLogFile()));
  }

  // no debug because it gets tested separately
  const logLevels = ['fatal', 'error', 'warning', 'notice', 'info'];
  const logLevelsNumber = [0, 1, 2, 3, 4];

  // hard-coded because it is hardcoded in the logger as well and therefore can be tested to be this way, in byte
  const maxLogSize = 50000;

  type LogLevelArbitrary = fc.Arbitrary<'fatal' | 'error' | 'warning' | 'notice' | 'info'>;
  const logLevelArbitrary = fc.constantFrom(...logLevels);

  const logLevelNumberArbitrary = fc.constantFrom(...logLevelsNumber);

  before(() => {
    rewiremock.enable();
  });

  after(() => {
    rewiremock.disable();
  });

  it('creates log files', () => {
    const logger: ILogger = container.get('logger');

    expect(logger.getLogFile()).path('log file is not created');
    expect(logger.getExceptionsLogFile()).path('exception log file is not created');
  });

  it('logs exceptions', async () => {
    const logger: ILogger = container.get('logger');

    await logger.exception(new Error('this is an error'));
  });

  it("default log file doesn't get bigger than 50KB @slow", async () => {
    const logger: ILogger = container.get('logger');

    for (let i = 0; i < maxLogSize; i++) {
      await logger.log(4, 'your waifu is trash');
    }

    const logFileStats = await fs.stat(logger.getLogFile());
    expect(logFileStats.size).lessThan(maxLogSize, 'log is bigger than its max size');
  }).timeout(15000);

  it("exception log file doesn't get bigger than 50KB @slow", async () => {
    const logger: ILogger = container.get('logger');

    for (let i = 0; i < maxLogSize; ++i) {
      await logger.exception(new Error('your waifu is trash'));
    }

    const logFileStats = await fs.stat(logger.getLogFile());
    expect(logFileStats.size).lessThan(maxLogSize, 'log is bigger than its max size');
  }).timeout(15000);

  it('logs different levels directly', () => {
    const logger: ILogger = container.get('logger');

    return fc.assert(
      fc.asyncProperty(logLevelArbitrary as LogLevelArbitrary, fc.string(), async (logLevel, message) => {
        await logger[logLevel](message);
        const lastLine = await getLastLine(getDefaultLogReadLineInterface(logger));
        expect(lastLine).contains(message, 'the log line does not contain the message');
        expect(lastLine).contains(logLevel, `the log line does not contain the '${logLevel}' keyword`);
      }),
      {
        numRuns: 50,
      }
    );
  });

  it('logs different levels indirectly via the generic log function', () => {
    const logger: ILogger = container.get('logger');

    return fc.assert(
      fc.asyncProperty(logLevelNumberArbitrary, fc.string(), async (logLevelNumber, message) => {
        await logger.log(logLevelNumber, message);
        const lastLine = await getLastLine(getDefaultLogReadLineInterface(logger));
        expect(lastLine).contains(message, 'the log line does not contain the message');
        expect(lastLine).contains(
          logLevels[logLevelNumber],
          `the log line does not contain the '${logLevels[logLevelNumber]}' keyword`
        );
      }),
      {
        numRuns: 50,
      }
    );
  });

  it('logs debug only in dev mode', async () => {
    const logger: ILogger = container.get('logger');

    setDev();
    await logger.debug('this is a development message');
    const lastLine = await getLastLine(getDefaultLogReadLineInterface(logger));
    expect(lastLine).contains('this is a development message', 'the dev log line does not contain the message');
    expect(lastLine).contains('debug', `the dev log line does not contain the 'debug' keyword`);
    setDev(false);
    await logger.log(LogLevel.warning, 'this is a warning');
    await logger.debug('this is a second development message, should not be here');
    expect(lastLine).not.contain(
      'this is a second development message, should not be here',
      'debug is logged even in non-dev mode'
    );
  });
});
