import { Log } from "meteor/logging";
import { Meteor } from "meteor/meteor";

Tinytest.add("logging - _getCallerDetails", function (test) {
  const details = Log._getCallerDetails();
  // Ignore this test for Opera, IE, Safari since this test would work only
  // in Chrome and Firefox, other browsers don't give us an ability to get
  // stacktrace.
  if (new Error().stack) {
    if (Meteor.isServer) {
      test.equal(details.file, "tinytest.js");
    } else {
      // Note that we want this to work in --production too, so we need to allow
      // for the minified filename.
      test.matches(details.file, /^(?:tinytest\.js|[a-f0-9]{40}\.js)$/);
    }

    // evaled statements shouldn't crash
    const code = "Log._getCallerDetails().file";
    // Note that we want this to work in --production too, so we need to allow
    // for the minified filename
    test.matches(
      eval(code),
      /^(?:eval|local-test_logging\.js|[a-f0-9]{40}\.js)/
    );
  }
});

Tinytest.add("logging - log", function (test) {
  const logBothMessageAndObject = function (log, level) {
    Log._intercept(3);
    // Tests for correctness
    log("message");
    log({ property1: "foo", property2: "bar" });
    log({ message: "mixed", property1: "foo", property2: "bar" });

    let intercepted = Log._intercepted();
    test.equal(intercepted.length, 3);

    const obj1 = EJSON.parse(intercepted[0]);
    test.equal(obj1.message, "message");
    test.equal(obj1.level, level);
    test.instanceOf(obj1.time, Date);

    const obj2 = EJSON.parse(intercepted[1]);
    test.isFalse(obj2.message);
    test.equal(obj2.property1, "foo");
    test.equal(obj2.property2, "bar");
    test.equal(obj2.level, level);
    test.instanceOf(obj2.time, Date);

    const obj3 = EJSON.parse(intercepted[2]);
    test.equal(obj3.message, "mixed");
    test.equal(obj3.property1, "foo");
    test.equal(obj3.property2, "bar");
    test.equal(obj3.level, level);
    test.instanceOf(obj3.time, Date);

    // Test logging falsy values, as well as single digits
    // and some other non-stringy things
    // In a format of testcase, expected result, name of the test.
    const testcases = [
      [1, "1", "single digit"],
      [0, "0", "falsy - 0"],
      [null, "null", "falsy - null"],
      [undefined, "undefined", "falsy - undefined"],
      [
        new Date("2013-06-13T01:15:16.000Z"),
        new Date("2013-06-13T01:15:16.000Z"),
        "date",
      ],
      [/[^regexp]{0,1}/g, "/[^regexp]{0,1}/g", "regexp"],
      [true, "true", "boolean - true"],
      [false, "false", "boolean - false"],
      [-Infinity, "-Infinity", "number - -Infinity"],
    ];

    Log._intercept(testcases.length);
    testcases.forEach((testcase) => {
      log(testcase[0]);
    });

    intercepted = Log._intercepted();

    test.equal(intercepted.length, testcases.length);

    testcases.forEach((testcase, index) => {
      const expected = testcase[1];
      const testName = testcase[2];
      const recieved = intercepted[index];
      const obj = EJSON.parse(recieved);

      // IE8 and old Safari don't support this date format. Skip it.
      if (
        expected &&
        expected.toString &&
        (expected.toString() === "NaN" ||
          expected.toString() === "Invalid Date")
      ) {
        return;
      }

      if (testcase[0] instanceof Date) {
        obj.message = new Date(obj.message);
      }

      test.equal(obj.message, expected, `Logging ${testName}`);
    });

    // Tests for correct exceptions
    Log._intercept(6);

    test.throws(function () {
      log({ time: "not the right time" });
    });
    test.throws(function () {
      log({ level: "not the right level" });
    });
    ["file", "line", "program", "originApp", "satellite"].forEach(function (
      restrictedKey
    ) {
      test.throws(function () {
        const obj = {};
        obj[restrictedKey] = "usage of restricted key";
        log(obj);
      });
    });

    // Can't pass numbers, objects, arrays, regexps or functions as message
    const throwingTestcases = [
      1,
      NaN,
      { foo: "bar" },
      ["a", "r", "r"],
      null,
      undefined,
      new Date(),
      function () {
        return 42;
      },
      /[regexp]/,
    ];
    Log._intercept(throwingTestcases.length);
    throwingTestcases.forEach(function (testcase) {
      test.throws(function () {
        log({ message: testcase });
      });
    });

    // Since all tests above should throw, nothing should be printed.
    // This call will set the logging interception to the clean state as well.
    test.equal(Log._intercepted().length, 0);
  };

  logBothMessageAndObject(Log, "info");
  ["info", "warn", "error"].forEach((level) => {
    logBothMessageAndObject(Log[level], level);
  });
});

Tinytest.add("logging - parse", function (test) {
  test.equal(Log.parse("message"), null);
  test.equal(Log.parse('{"foo": "bar"}'), null);
  const time = new Date();
  test.equal(Log.parse(`{"foo": "bar", "time": ${EJSON.stringify(time)}}`), {
    foo: "bar",
    time,
  });
  test.equal(Log.parse('{"foo": not json "bar"}'), null);
  test.equal(Log.parse('{"time": "not a date object"}'), null);
});

Tinytest.add("logging - format", function (test) {
  const time = new Date(2012, 9 - 1 /*0-based*/, 8, 7, 6, 5, 4);
  const utcOffsetStr = "(" + -(new Date().getTimezoneOffset() / 60) + ")";

  ["debug", "info", "warn", "error"].forEach(function (level) {
    test.equal(
      Log.format({ message: "message", time, level }),
      `${level
        .charAt(0)
        .toUpperCase()}20120908-07:06:05.004${utcOffsetStr} message`
    );

    test.equal(
      Log.format({
        message: "message",
        time,
        timeInexact: true,
        level,
      }),
      `${level
        .charAt(0)
        .toUpperCase()}20120908-07:06:05.004${utcOffsetStr}? message`
    );

    test.equal(
      Log.format({ foo1: "bar1", foo2: "bar2", time, level }),
      `${level
        .charAt(0)
        .toUpperCase()}20120908-07:06:05.004${utcOffsetStr} {"foo1":"bar1","foo2":"bar2"}`
    );

    test.equal(
      Log.format({ message: "message", foo: "bar", time, level }),
      `${level
        .charAt(0)
        .toUpperCase()}20120908-07:06:05.004${utcOffsetStr} message {"foo":"bar"}`
    );

    // Has everything except stderr field
    test.equal(
      Log.format({
        message: "message",
        foo: "bar",
        time,
        level,
        file: "app.js",
        line: 42,
        app: "myApp",
        originApp: "proxy",
        program: "server",
      }),
      `${level
        .charAt(0)
        .toUpperCase()}20120908-07:06:05.004${utcOffsetStr} [myApp via proxy] (server:app.js:42) message {\"foo\":\"bar\"}`
    );

    // stderr
    test.equal(
      Log.format({
        message: "message from stderr",
        time,
        level,
        stderr: true,
      }),
      `${level
        .charAt(0)
        .toUpperCase()}20120908-07:06:05.004${utcOffsetStr} (STDERR) message from stderr`
    );

    // app/originApp
    test.equal(
      Log.format({
        message: "message",
        time,
        level,
        app: "app",
        originApp: "app",
      }),
      `${level
        .charAt(0)
        .toUpperCase()}20120908-07:06:05.004${utcOffsetStr} [app] message`
    );
    test.equal(
      Log.format({
        message: "message",
        time,
        level,
        app: "app",
        originApp: "proxy",
      }),
      `${level
        .charAt(0)
        .toUpperCase()}20120908-07:06:05.004${utcOffsetStr} [app via proxy] message`
    );

    // source info
    test.equal(
      Log.format({
        message: "message",
        time,
        level,
        file: "app.js",
        line: 42,
        program: "server",
      }),
      `${level
        .charAt(0)
        .toUpperCase()}20120908-07:06:05.004${utcOffsetStr} (server:app.js:42) message`
    );
    test.equal(
      Log.format({
        message: "message",
        time,
        level,
        file: "app.js",
        line: 42,
      }),
      `${level
        .charAt(0)
        .toUpperCase()}20120908-07:06:05.004${utcOffsetStr} (app.js:42) message`
    );
  });

  test.matches(
    Log.format(
      {
        message: "oyez",
        time: new Date(),
        level: "error",
      },
      {
        color: true,
      }
    ),
    /oyez/
  );
});

Tinytest.add("logging - formats - without time", function (test) {
  const time = new Date(2012, 9 - 1 /*0-based*/, 8, 7, 6, 5, 4);
  // even tho time and offset are provided they should not be included in the output
  Log.showTime = false;
  const levels = ["debug", "info", "warn", "error"];
  for (const level of levels) {
    test.equal(
      Log.format({ message: "message", time, level }),
      `${level.charAt(0).toUpperCase()} message`
    );

    test.equal(
      Log.format({
        message: "message",
        time,
        timeInexact: true,
        level,
      }),
      `${level.charAt(0).toUpperCase()} message`
    );

    test.equal(
      Log.format({ foo1: "bar1", foo2: "bar2", time, level }),
      `${level.charAt(0).toUpperCase()} {"foo1":"bar1","foo2":"bar2"}`
    );

    test.equal(
      Log.format({ message: "message", foo: "bar", time, level }),
      `${level.charAt(0).toUpperCase()} message {"foo":"bar"}`
    );

    // Has everything except stderr field
    test.equal(
      Log.format({
        message: "message",
        foo: "bar",
        time,
        level,
        file: "app.js",
        line: 42,
        app: "myApp",
        originApp: "proxy",
        program: "server",
      }),
      `${level
        .charAt(0)
        .toUpperCase()} [myApp via proxy] (server:app.js:42) message {\"foo\":\"bar\"}`
    );

    // stderr
    test.equal(
      Log.format({
        message: "message from stderr",
        time,
        level,
        stderr: true,
      }),
      `${level.charAt(0).toUpperCase()} (STDERR) message from stderr`
    );

    // app/originApp
    test.equal(
      Log.format({
        message: "message",
        time,
        level,
        app: "app",
        originApp: "app",
      }),
      `${level.charAt(0).toUpperCase()} [app] message`
    );
    test.equal(
      Log.format({
        message: "message",
        time,
        level,
        app: "app",
        originApp: "proxy",
      }),
      `${level.charAt(0).toUpperCase()} [app via proxy] message`
    );

    // source info
    test.equal(
      Log.format({
        message: "message",
        time,
        level,
        file: "app.js",
        line: 42,
        program: "server",
      }),
      `${level.charAt(0).toUpperCase()} (server:app.js:42) message`
    );
    test.equal(
      Log.format({
        message: "message",
        time,
        level,
        file: "app.js",
        line: 42,
      }),
      `${level.charAt(0).toUpperCase()} (app.js:42) message`
    );
  }
  Log.showTime = true; // reset
});
