const process = require("process");
const { test } = require("tap");
const debug = require("debug");
const fs = require("fs");
const log4js = require("../../lib/log4js");

const removeFiles = async filenames => {
  if (!Array.isArray(filenames))
    filenames = [filenames];
  const promises = filenames.map(filename => fs.promises.unlink(filename));
  await Promise.allSettled(promises);
};

test("multiFile appender", batch => {
  batch.test(
    "should write to multiple files based on the loggingEvent property",
    t => {
      t.tearDown(async () => {
        await removeFiles(["logs/A.log", "logs/B.log"]);
      });
      log4js.configure({
        appenders: {
          multi: {
            type: "multiFile",
            base: "logs/",
            property: "categoryName",
            extension: ".log"
          }
        },
        categories: { default: { appenders: ["multi"], level: "info" } }
      });
      const loggerA = log4js.getLogger("A");
      const loggerB = log4js.getLogger("B");
      loggerA.info("I am in logger A");
      loggerB.info("I am in logger B");
      log4js.shutdown(() => {
        t.contains(fs.readFileSync("logs/A.log", "utf-8"), "I am in logger A");
        t.contains(fs.readFileSync("logs/B.log", "utf-8"), "I am in logger B");
        t.end();
      });
    }
  );

  batch.test(
    "should write to multiple files based on loggingEvent.context properties",
    t => {
      t.tearDown(async () => {
        await removeFiles(["logs/C.log", "logs/D.log"]);
      });
      log4js.configure({
        appenders: {
          multi: {
            type: "multiFile",
            base: "logs/",
            property: "label",
            extension: ".log"
          }
        },
        categories: { default: { appenders: ["multi"], level: "info" } }
      });
      const loggerC = log4js.getLogger("cheese");
      const loggerD = log4js.getLogger("biscuits");
      loggerC.addContext("label", "C");
      loggerD.addContext("label", "D");
      loggerC.info("I am in logger C");
      loggerD.info("I am in logger D");
      log4js.shutdown(() => {
        t.contains(fs.readFileSync("logs/C.log", "utf-8"), "I am in logger C");
        t.contains(fs.readFileSync("logs/D.log", "utf-8"), "I am in logger D");
        t.end();
      });
    }
  );

  batch.test("should close file after timeout", t => {
    t.tearDown(async () => {
      await removeFiles("logs/C.log");
    });
    /* checking that the file is closed after a timeout is done by looking at the debug logs
      since detecting file locks with node.js is platform specific.
     */
    const debugWasEnabled = debug.enabled("log4js:multiFile");
    const debugLogs = [];
    const originalWrite = process.stderr.write;
    process.stderr.write = (string, encoding, fd) => {
      debugLogs.push(string);
      if (debugWasEnabled) {
        originalWrite.apply(process.stderr, [string, encoding, fd]);
      }
    };
    debug.enable("log4js:multiFile");
    log4js.configure({
      appenders: {
        multi: {
          type: "multiFile",
          base: "logs/",
          property: "label",
          extension: ".log",
          timeout: 20
        }
      },
      categories: { default: { appenders: ["multi"], level: "info" } }
    });
    const loggerC = log4js.getLogger("cheese");
    loggerC.addContext("label", "C");
    loggerC.info("I am in logger C");
    setTimeout(() => {
      t.contains(
        debugLogs[debugLogs.length - 1],
        "C not used for > 20 ms => close"
      );
      if (!debugWasEnabled) {
        debug.disable("log4js:multiFile");
      }
      process.stderr.write = originalWrite;
      t.end();
    }, 50);
  });

  batch.test(
    "should fail silently if loggingEvent property has no value",
    t => {
      t.tearDown(async () => {
        await removeFiles("logs/E.log");
      });
      log4js.configure({
        appenders: {
          multi: {
            type: "multiFile",
            base: "logs/",
            property: "label",
            extension: ".log"
          }
        },
        categories: { default: { appenders: ["multi"], level: "info" } }
      });
      const loggerE = log4js.getLogger();
      loggerE.addContext("label", "E");
      loggerE.info("I am in logger E");
      loggerE.removeContext("label");
      loggerE.info("I am not in logger E");
      loggerE.addContext("label", null);
      loggerE.info("I am also not in logger E");
      log4js.shutdown(() => {
        const contents = fs.readFileSync("logs/E.log", "utf-8");
        t.contains(contents, "I am in logger E");
        t.notMatch(contents, "I am not in logger E");
        t.notMatch(contents, "I am also not in logger E");
        t.end();
      });
    }
  );

  batch.test("should pass options to rolling file stream", t => {
    t.tearDown(async () => {
      await removeFiles(["logs/F.log", "logs/F.log.1", "logs/F.log.2"]);
    });
    log4js.configure({
      appenders: {
        multi: {
          type: "multiFile",
          base: "logs/",
          property: "label",
          extension: ".log",
          maxLogSize: 30,
          backups: 2,
          layout: { type: "messagePassThrough" }
        }
      },
      categories: { default: { appenders: ["multi"], level: "info" } }
    });
    const loggerF = log4js.getLogger();
    loggerF.addContext("label", "F");
    loggerF.info("Being in logger F is the best.");
    loggerF.info("I am also in logger F, awesome");
    loggerF.info("I am in logger F");
    log4js.shutdown(() => {
      let contents = fs.readFileSync("logs/F.log", "utf-8");
      t.contains(contents, "I am in logger F");
      contents = fs.readFileSync("logs/F.log.1", "utf-8");
      t.contains(contents, "I am also in logger F");
      contents = fs.readFileSync("logs/F.log.2", "utf-8");
      t.contains(contents, "Being in logger F is the best");
      t.end();
    });
  });

  batch.test("should inherit config from category hierarchy", t => {
    t.tearDown(async () => {
      await removeFiles("logs/test.someTest.log");
    });
    log4js.configure({
      appenders: {
        out: { type: "stdout" },
        test: {
          type: "multiFile",
          base: "logs/",
          property: "categoryName",
          extension: ".log"
        }
      },
      categories: {
        default: { appenders: ["out"], level: "info" },
        test: { appenders: ["test"], level: "debug" }
      }
    });

    const testLogger = log4js.getLogger("test.someTest");
    testLogger.debug("This should go to the file");
    log4js.shutdown(() => {
      const contents = fs.readFileSync("logs/test.someTest.log", "utf-8");
      t.contains(contents, "This should go to the file");
      t.end();
    });
  });

  batch.test("should shutdown safely even if it is not used", t => {
    log4js.configure({
      appenders: {
        out: { type: "stdout" },
        test: {
          type: "multiFile",
          base: "logs/",
          property: "categoryName",
          extension: ".log"
        }
      },
      categories: {
        default: { appenders: ["out"], level: "info" },
        test: { appenders: ["test"], level: "debug" }
      }
    });
    log4js.shutdown(() => {
      t.ok("callback is called");
      t.end();
    });
  });

  batch.tearDown(async () => {
    try {
      const files = fs.readdirSync("logs");
      await removeFiles(files.map(filename => `logs/${filename}`));
      fs.rmdirSync("logs");
    } catch (e) {
      // doesn't matter
    }
  });

  batch.end();
});
