const { test } = require("tap");
const debug = require("debug")("log4js:test.logger");
const sandbox = require("@log4js-node/sandboxed-module");
const callsites = require("callsites");
const levels = require("../../lib/levels");

const events = [];
const Logger = sandbox.require("../../lib/logger", {
  requires: {
    "./levels": levels,
    "./clustering": {
      isMaster: () => true,
      onlyOnMaster: fn => fn(),
      send: evt => {
        debug("fake clustering got event:", evt);
        events.push(evt);
      }
    }
  }
});

const testConfig = {
  level: levels.TRACE
};

test("../../lib/logger", batch => {
  batch.beforeEach(done => {
    events.length = 0;
    testConfig.level = levels.TRACE;
    done();
  });

  batch.test("constructor with no parameters", t => {
    t.throws(() => new Logger(), new Error("No category provided."));
    t.end();
  });

  batch.test("constructor with category", t => {
    const logger = new Logger("cheese");
    t.equal(logger.category, "cheese", "should use category");
    t.equal(logger.level, levels.OFF, "should use OFF log level");
    t.end();
  });

  batch.test("set level should delegate", t => {
    const logger = new Logger("cheese");
    logger.level = "debug";
    t.equal(logger.category, "cheese", "should use category");
    t.equal(logger.level, levels.DEBUG, "should use level");
    t.end();
  });

  batch.test("isLevelEnabled", t => {
    const logger = new Logger("cheese");
    const functions = [
      "isTraceEnabled",
      "isDebugEnabled",
      "isInfoEnabled",
      "isWarnEnabled",
      "isErrorEnabled",
      "isFatalEnabled"
    ];
    t.test(
      "should provide a level enabled function for all levels",
      subtest => {
        subtest.plan(functions.length);
        functions.forEach(fn => {
          subtest.type(logger[fn], "function");
        });
      }
    );
    logger.level = "INFO";
    t.notOk(logger.isTraceEnabled());
    t.notOk(logger.isDebugEnabled());
    t.ok(logger.isInfoEnabled());
    t.ok(logger.isWarnEnabled());
    t.ok(logger.isErrorEnabled());
    t.ok(logger.isFatalEnabled());
    t.end();
  });

  batch.test("should send log events to dispatch function", t => {
    const logger = new Logger("cheese");
    logger.level = "debug";
    logger.debug("Event 1");
    logger.debug("Event 2");
    logger.debug("Event 3");

    t.equal(events.length, 3);
    t.equal(events[0].data[0], "Event 1");
    t.equal(events[1].data[0], "Event 2");
    t.equal(events[2].data[0], "Event 3");
    t.end();
  });

  batch.test("should add context values to every event", t => {
    const logger = new Logger("fromage");
    logger.level = "debug";
    logger.debug("Event 1");
    logger.addContext("cheese", "edam");
    logger.debug("Event 2");
    logger.debug("Event 3");
    logger.addContext("biscuits", "timtam");
    logger.debug("Event 4");
    logger.removeContext("cheese");
    logger.debug("Event 5");
    logger.clearContext();
    logger.debug("Event 6");

    t.equal(events.length, 6);
    t.same(events[0].context, {});
    t.same(events[1].context, { cheese: "edam" });
    t.same(events[2].context, { cheese: "edam" });
    t.same(events[3].context, { cheese: "edam", biscuits: "timtam" });
    t.same(events[4].context, { biscuits: "timtam" });
    t.same(events[5].context, {});
    t.end();
  });

  batch.test("should not break when log data has no toString", t => {
    const logger = new Logger("thing");
    logger.level = "debug";
    logger.info("Just testing ", Object.create(null));

    t.equal(events.length, 1);
    t.end();
  });

  batch.test("default should disable useCallStack unless manual enable", t => {
    const logger = new Logger("stack");
    logger.level = "debug";

    t.equal(logger.useCallStack, false);

    logger.useCallStack = false;
    t.equal(logger.useCallStack, false);

    logger.useCallStack = 0;
    t.equal(logger.useCallStack, false);

    logger.useCallStack = "";
    t.equal(logger.useCallStack, false);

    logger.useCallStack = null;
    t.equal(logger.useCallStack, false);

    logger.useCallStack = undefined;
    t.equal(logger.useCallStack, false);

    logger.useCallStack = "true";
    t.equal(logger.useCallStack, false);

    logger.useCallStack = true;
    t.equal(logger.useCallStack, true);
    t.end();
  });

  batch.test("should correctly switch on/off useCallStack", t => {
    const logger = new Logger("stack");
    logger.level = "debug";
    logger.useCallStack = true;
    t.equal(logger.useCallStack, true);

    logger.info("hello world");
    const callsite = callsites()[0];

    t.equal(events.length, 1);
    t.equal(events[0].data[0], "hello world");
    t.equal(events[0].fileName, callsite.getFileName());
    t.equal(events[0].lineNumber, callsite.getLineNumber() - 1);
    t.equal(events[0].columnNumber, 12);

    logger.useCallStack = false;
    logger.info("disabled");
    t.equal(logger.useCallStack, false);
    t.equal(events[1].data[0], "disabled");
    t.equal(events[1].fileName, undefined);
    t.equal(events[1].lineNumber, undefined);
    t.equal(events[1].columnNumber, undefined);
    t.end();
  });

  batch.test(
    "Once switch on/off useCallStack will apply all same category loggers",
    t => {
      const logger1 = new Logger("stack");
      logger1.level = "debug";
      logger1.useCallStack = true;
      const logger2 = new Logger("stack");
      logger2.level = "debug";

      logger1.info("hello world");
      const callsite = callsites()[0];

      t.equal(logger1.useCallStack, true);
      t.equal(events.length, 1);
      t.equal(events[0].data[0], "hello world");
      t.equal(events[0].fileName, callsite.getFileName());
      t.equal(events[0].lineNumber, callsite.getLineNumber() - 1);
      t.equal(events[0].columnNumber, 15); // col of the '.' in logger1.info(...)

      logger2.info("hello world");
      const callsite2 = callsites()[0];

      t.equal(logger2.useCallStack, true);
      t.equal(events[1].data[0], "hello world");
      t.equal(events[1].fileName, callsite2.getFileName());
      t.equal(events[1].lineNumber, callsite2.getLineNumber() - 1);
      t.equal(events[1].columnNumber, 15); // col of the '.' in logger1.info(...)

      logger1.useCallStack = false;
      logger2.info("hello world");
      t.equal(logger2.useCallStack, false);
      t.equal(events[2].data[0], "hello world");
      t.equal(events[2].fileName, undefined);
      t.equal(events[2].lineNumber, undefined);
      t.equal(events[2].columnNumber, undefined);

      t.end();
    }
  );

  batch.test("should correctly change the parseCallStack function", t => {
    const logger = new Logger("stack");
    const parseFunction = function() {
      return {
        functionName: "test function name",
        fileName: "test file name",
        lineNumber: 15,
        columnNumber: 25,
        callStack: "test callstack"
      };
    };
    logger.level = "debug";
    logger.useCallStack = true;
    logger.setParseCallStackFunction(parseFunction);

    t.equal(logger.parseCallStack, parseFunction);

    logger.info("test parseCallStack");
    t.equal(events[0].functionName, "test function name");
    t.equal(events[0].fileName, "test file name");
    t.equal(events[0].lineNumber, 15);
    t.equal(events[0].columnNumber, 25);
    t.equal(events[0].callStack, "test callstack");

    t.end();
  });

  batch.end();
});
