/* Any copyright is dedicated to the Public Domain.
   http://creativecommons.org/publicdomain/zero/1.0/ */

// NOTE: The sync test_errorhandler_* tests have quite good coverage for
// other aspects of this.

const { LogManager } = ChromeUtils.importESModule(
  "resource://gre/modules/LogManager.sys.mjs"
);
const { Log } = ChromeUtils.importESModule(
  "resource://gre/modules/Log.sys.mjs"
);
const logManagerDefaultOptions = {
  logFileSubDirectoryEntries: ["weave", "logs"],
  testTopicPrefix: "services-tests:common:log-manager:",
};

// Returns an array of [consoleAppender, dumpAppender, [fileAppenders]] for
// the specified log.  Note that fileAppenders will usually have length=1
function getAppenders(log) {
  let capps = log.appenders.filter(app => app instanceof Log.ConsoleAppender);
  equal(capps.length, 1, "should only have one console appender");
  let dapps = log.appenders.filter(app => app instanceof Log.DumpAppender);
  equal(dapps.length, 1, "should only have one dump appender");
  let fapps = log.appenders.filter(
    app => app instanceof LogManager.StorageStreamAppender
  );
  return [capps[0], dapps[0], fapps];
}

// Test that the correct thing happens when no prefs exist for the log manager.
add_task(async function test_noPrefs() {
  // tell the log manager to init with a pref branch that doesn't exist.
  let lm = new LogManager({
    ...logManagerDefaultOptions,
    prefRoot: "no-such-branch.",
    logNames: ["TestLog"],
    logFilePrefix: "test",
  });

  let log = Log.repository.getLogger("TestLog");
  let [capp, dapp, fapps] = getAppenders(log);
  // The console appender gets "Fatal" while the "dump" appender gets "Error" levels
  equal(capp.level, Log.Level.Fatal);
  equal(dapp.level, Log.Level.Error);
  // and the file (stream) appender gets Debug by default
  equal(fapps.length, 1, "only 1 file appender");
  equal(fapps[0].level, Log.Level.Debug);
  lm.finalize();
});

// Test that changes to the prefs used by the log manager are updated dynamically.
add_task(async function test_PrefChanges() {
  Services.prefs.setStringPref(
    "log-manager.test.log.appender.console",
    "Trace"
  );
  Services.prefs.setStringPref("log-manager.test.log.appender.dump", "Trace");
  Services.prefs.setStringPref(
    "log-manager.test.log.appender.file.level",
    "Trace"
  );
  let lm = new LogManager({
    ...logManagerDefaultOptions,
    prefRoot: "log-manager.test.",
    logNames: ["TestLog2"],
    logFilePrefix: "test",
  });

  let log = Log.repository.getLogger("TestLog2");
  let [capp, dapp, [fapp]] = getAppenders(log);
  equal(capp.level, Log.Level.Trace);
  equal(dapp.level, Log.Level.Trace);
  equal(fapp.level, Log.Level.Trace);
  // adjust the prefs and they should magically be reflected in the appenders.
  Services.prefs.setStringPref(
    "log-manager.test.log.appender.console",
    "Debug"
  );
  Services.prefs.setStringPref("log-manager.test.log.appender.dump", "Debug");
  Services.prefs.setStringPref(
    "log-manager.test.log.appender.file.level",
    "Debug"
  );
  equal(capp.level, Log.Level.Debug);
  equal(dapp.level, Log.Level.Debug);
  equal(fapp.level, Log.Level.Debug);
  // and invalid values should cause them to fallback to their defaults.
  Services.prefs.setStringPref("log-manager.test.log.appender.console", "xxx");
  Services.prefs.setStringPref("log-manager.test.log.appender.dump", "xxx");
  Services.prefs.setStringPref(
    "log-manager.test.log.appender.file.level",
    "xxx"
  );
  equal(capp.level, Log.Level.Fatal);
  equal(dapp.level, Log.Level.Error);
  equal(fapp.level, Log.Level.Debug);
  lm.finalize();
});

// Test that the same log used by multiple log managers does the right thing.
add_task(async function test_SharedLogs() {
  // create the prefs for the first instance.
  Services.prefs.setStringPref(
    "log-manager-1.test.log.appender.console",
    "Trace"
  );
  Services.prefs.setStringPref("log-manager-1.test.log.appender.dump", "Trace");
  Services.prefs.setStringPref(
    "log-manager-1.test.log.appender.file.level",
    "Trace"
  );
  let lm1 = new LogManager({
    ...logManagerDefaultOptions,
    prefRoot: "log-manager-1.test.",
    logNames: ["TestLog3"],
    logFilePrefix: "test",
  });

  // and the second.
  Services.prefs.setStringPref(
    "log-manager-2.test.log.appender.console",
    "Debug"
  );
  Services.prefs.setStringPref("log-manager-2.test.log.appender.dump", "Debug");
  Services.prefs.setStringPref(
    "log-manager-2.test.log.appender.file.level",
    "Debug"
  );
  let lm2 = new LogManager({
    ...logManagerDefaultOptions,
    prefRoot: "log-manager-2.test.",
    logNames: ["TestLog3"],
    logFilePrefix: "test",
  });

  let log = Log.repository.getLogger("TestLog3");
  let [capp, dapp] = getAppenders(log);

  // console and dump appenders should be "trace" as it is more verbose than
  // "debug"
  equal(capp.level, Log.Level.Trace);
  equal(dapp.level, Log.Level.Trace);

  // Set the prefs on the -1 branch to "Error" - it should then end up with
  // "Debug" from the -2 branch.
  Services.prefs.setStringPref(
    "log-manager-1.test.log.appender.console",
    "Error"
  );
  Services.prefs.setStringPref("log-manager-1.test.log.appender.dump", "Error");
  Services.prefs.setStringPref(
    "log-manager-1.test.log.appender.file.level",
    "Error"
  );

  equal(capp.level, Log.Level.Debug);
  equal(dapp.level, Log.Level.Debug);

  lm1.finalize();
  lm2.finalize();
});

// A little helper to test what log files exist.  We expect exactly zero (if
// prefix is null) or exactly one with the specified prefix.
function checkLogFile(prefix) {
  let logsdir = FileUtils.getDir("ProfD", ["weave", "logs"]);
  let entries = logsdir.directoryEntries;
  if (!prefix) {
    // expecting no files.
    ok(!entries.hasMoreElements());
  } else {
    // expecting 1 file.
    ok(entries.hasMoreElements());
    let logfile = entries.getNext().QueryInterface(Ci.nsIFile);
    equal(logfile.leafName.slice(-4), ".txt");
    ok(logfile.leafName.startsWith(prefix + "-test-"), logfile.leafName);
    // and remove it ready for the next check.
    logfile.remove(false);
  }
}

// Test that we correctly write error logs by default
add_task(async function test_logFileErrorDefault() {
  let lm = new LogManager({
    ...logManagerDefaultOptions,
    prefRoot: "log-manager.test.",
    logNames: ["TestLog2"],
    logFilePrefix: "test",
  });

  let log = Log.repository.getLogger("TestLog2");
  log.error("an error message");
  await lm.resetFileLog(lm.REASON_ERROR);
  // One error log file exists.
  checkLogFile("error");

  lm.finalize();
});

// Test that we correctly write success logs.
add_task(async function test_logFileSuccess() {
  Services.prefs.setBoolPref(
    "log-manager.test.log.appender.file.logOnError",
    false
  );
  Services.prefs.setBoolPref(
    "log-manager.test.log.appender.file.logOnSuccess",
    false
  );

  let lm = new LogManager({
    ...logManagerDefaultOptions,
    prefRoot: "log-manager.test.",
    logNames: ["TestLog2"],
    logFilePrefix: "test",
  });

  let log = Log.repository.getLogger("TestLog2");
  log.info("an info message");
  await lm.resetFileLog();
  // Zero log files exist.
  checkLogFile(null);

  // Reset logOnSuccess and do it again - log should appear.
  Services.prefs.setBoolPref(
    "log-manager.test.log.appender.file.logOnSuccess",
    true
  );
  log.info("an info message");
  await lm.resetFileLog();

  checkLogFile("success");

  // Now test with no "reason" specified and no "error" record.
  log.info("an info message");
  await lm.resetFileLog();
  // should get a "success" entry.
  checkLogFile("success");

  // With no "reason" and an error record - should get no success log.
  log.error("an error message");
  await lm.resetFileLog();
  // should get no entry
  checkLogFile(null);

  // And finally now with no error, to ensure that the fact we had an error
  // previously doesn't persist after the .resetFileLog call.
  log.info("an info message");
  await lm.resetFileLog();
  checkLogFile("success");

  lm.finalize();
});

// Test that we correctly write error logs.
add_task(async function test_logFileError() {
  Services.prefs.setBoolPref(
    "log-manager.test.log.appender.file.logOnError",
    false
  );
  Services.prefs.setBoolPref(
    "log-manager.test.log.appender.file.logOnSuccess",
    false
  );

  let lm = new LogManager({
    ...logManagerDefaultOptions,
    prefRoot: "log-manager.test.",
    logNames: ["TestLog2"],
    logFilePrefix: "test",
  });

  let log = Log.repository.getLogger("TestLog2");
  log.info("an info message");
  let reason = await lm.resetFileLog();
  Assert.equal(reason, null, "null returned when no file created.");
  // Zero log files exist.
  checkLogFile(null);

  // Reset logOnSuccess - success logs should appear if no error records.
  Services.prefs.setBoolPref(
    "log-manager.test.log.appender.file.logOnSuccess",
    true
  );
  log.info("an info message");
  reason = await lm.resetFileLog();
  Assert.equal(reason, lm.SUCCESS_LOG_WRITTEN);
  checkLogFile("success");

  // Set logOnError and unset logOnSuccess - error logs should appear.
  Services.prefs.setBoolPref(
    "log-manager.test.log.appender.file.logOnSuccess",
    false
  );
  Services.prefs.setBoolPref(
    "log-manager.test.log.appender.file.logOnError",
    true
  );
  log.error("an error message");
  reason = await lm.resetFileLog();
  Assert.equal(reason, lm.ERROR_LOG_WRITTEN);
  checkLogFile("error");

  // Now test with no "error" record.
  log.info("an info message");
  reason = await lm.resetFileLog();
  // should get no file
  Assert.equal(reason, null);
  checkLogFile(null);

  // With an error record we should get an error log.
  log.error("an error message");
  reason = await lm.resetFileLog();
  // should get en error log
  Assert.equal(reason, lm.ERROR_LOG_WRITTEN);
  checkLogFile("error");

  // And finally now with success, to ensure that the fact we had an error
  // previously doesn't persist after the .resetFileLog call.
  log.info("an info message");
  await lm.resetFileLog();
  checkLogFile(null);

  lm.finalize();
});

function countLogFiles() {
  let logsdir = FileUtils.getDir("ProfD", ["weave", "logs"]);
  let count = 0;
  for (let entry of logsdir.directoryEntries) {
    void entry;
    count += 1;
  }
  return count;
}

// Test that removeAllLogs removes all log files.
add_task(async function test_logFileError() {
  Services.prefs.setBoolPref(
    "log-manager.test.log.appender.file.logOnError",
    true
  );
  Services.prefs.setBoolPref(
    "log-manager.test.log.appender.file.logOnSuccess",
    true
  );

  let lm = new LogManager({
    ...logManagerDefaultOptions,
    prefRoot: "log-manager.test.",
    logNames: ["TestLog2"],
    logFilePrefix: "test",
  });

  let log = Log.repository.getLogger("TestLog2");
  log.info("an info message");
  let reason = await lm.resetFileLog();
  Assert.equal(reason, lm.SUCCESS_LOG_WRITTEN, "success log was written.");

  log.error("an error message");
  reason = await lm.resetFileLog();
  Assert.equal(reason, lm.ERROR_LOG_WRITTEN);

  Assert.equal(countLogFiles(), 2, "expect 2 log files");
  await lm.removeAllLogs();
  Assert.equal(
    countLogFiles(),
    0,
    "should be no log files after removing them"
  );

  lm.finalize();
});
