// @flow

/**
 * Logging setup for our application.
 */
import stream from "stream";
import expressWinston from "express-winston";
import winston from "winston";

import * as lw from "@google-cloud/logging-winston";

import args from "./arguments.js";

import type {Middleware, $Request} from "express";
import type {Transport, NpmLogLevels, Format} from "winston";
import type {Info, Logger, LogLevel} from "./types.js";

/**
 * This is how the log message gets formatted.
 * We're adding the durationMs from the profiler commands in winston so
 * that we can read them :)
 */
const prodFormatter = ({message, durationMs}: Info): string =>
    `${message} ${(durationMs && `(${durationMs}ms)`) || ""}`;

/**
 * Our dev format adds the log level.
 */
const devFormatter = (info: Info): string =>
    `${info.level}: ${prodFormatter(info)}`;

function getFormatters(isDev: boolean) {
    const formatters: Array<Format> = [
        winston.format.splat(), // Allows for %s style substitutions
    ];

    formatters.push(winston.format.cli({level: isDev}));
    formatters.push(
        winston.format.printf((info: any) =>
            (isDev ? devFormatter : prodFormatter)(info),
        ),
    );

    return winston.format.combine(...formatters);
}

function getTransports(isDev: boolean): Array<Transport> {
    const transports = [];
    if (!isDev) {
        transports.push(new lw.LoggingWinston());
    }

    if (process.env.NODE_ENV === "test") {
        // During testing, we just dump logging to a stream.
        // This isn't used for anything at all right now, but we could use
        // it for snapshot testing with some updates at some point.
        const sink = new stream.Writable({write: () => {}});
        // This is a hack to make our writable stream work $FlowFixMe
        sink._write = sink.write;
        transports.push(
            new winston.transports.Stream({
                format: getFormatters(isDev),
                stream: sink,
            }),
        );
    } else {
        transports.push(
            new winston.transports.Console({
                format: getFormatters(isDev),
            }),
        );
    }
    return transports;
}

function initLogging(logLevel: LogLevel, isDev: boolean): Logger {
    // This is the logger that we use to log general information in our app.
    // Whereever one might use console, use this instead.
    const winstonLogger = winston.createLogger<NpmLogLevels>({
        level: logLevel,
        transports: getTransports(isDev),
    });

    winstonLogger.debug(
        `Intialized logging with Level=${logLevel} DeveloperMode=${
            isDev ? "true" : "false"
        }`,
    );

    return winstonLogger;
}

export function extractErrorInfo(error: any): string {
    if (typeof error === "string") {
        return error;
    }

    if (error.response && error.response.error) {
        return `${error.response.error}: ${error.stack}`;
    }

    if (error.error && error !== error.error) {
        return extractErrorInfo(error.error);
    }

    if (error.stack) {
        return error.stack;
    }

    return `${error}`;
}

export function makeErrorMiddleware(logger: Logger): Middleware {
    // This is the logger that captures errors in our express server.
    return expressWinston.errorLogger({
        /**
         * Specify the level that this logger logs at.
         * (use a function to dynamically change level based on req, res and
         * err)
         *     `function(req, res, err) { return String; }`
         */
        level: "error",

        /**
         * Use the logger we already set up.
         */
        winstonInstance: logger,
    });
}

export function makeRequestMiddleware(logger: Logger): Promise<Middleware> {
    // This is the logger that captures requests handled by our express server.
    return args.dev
        ? /**
           * If we're in dev, we're going to use the expressWinston logger
           */
          Promise.resolve(
              expressWinston.logger({
                  /**
                   * Specify the level that this logger logs at.
                   * (use a function to dynamically change level based on req and res)
                   *     `function(req, res) { return String; }`
                   */
                  level: "info",

                  /**
                   * Use the logger we already set up.
                   */
                  winstonInstance: logger,
                  expressFormat: true,
                  colorize: true,
                  meta: false,
              }),
          )
        : /**
           * Otherwise, we're using the Google middleware
           */
          lw.express.makeMiddleware(logger);
}

export const rootLogger: Logger = initLogging(args.logLevel, args.dev);

export const getLogger = (req: $Request): Logger => {
    /**
     * NOTE: the $Request type doesn't have a log field, officially.
     * $FlowIgnore
     */
    return req.log == null ? rootLogger : req.log;
};

@@ -577,20 +577,19 @@
 /%0Aconst 
-prod
+dev
 Formatte
@@ -594,16 +594,23 @@
 ter = (%7B
+level, 
 message,
@@ -645,16 +645,26 @@
 =%3E%0A    %60
+$%7Blevel%7D: 
 $%7Bmessag
@@ -718,145 +718,8 @@
 %60;%0A%0A
-/**%0A * Our dev format adds the log level.%0A */%0Aconst devFormatter = (info: Info): string =%3E%0A    %60$%7Binfo.level%7D: $%7BprodFormatter(info)%7D%60;%0A%0A
 func
@@ -737,30 +737,16 @@
 matters(
-isDev: boolean
 ) %7B%0A    
@@ -905,21 +905,20 @@
 %7Blevel: 
-isDev
+true
 %7D));%0A   
@@ -934,25 +934,16 @@
 rs.push(
-%0A        
 winston.
@@ -974,82 +974,28 @@
 ) =%3E
-%0A            (isDev ? devFormatter : prodFormatter)(info),%0A        ),%0A    
+ devFormatter(info))
 );%0A%0A
@@ -1110,110 +1110,8 @@
 %3E %7B%0A
-    const transports = %5B%5D;%0A    if (!isDev) %7B%0A        transports.push(new lw.LoggingWinston());%0A    %7D%0A%0A
     
@@ -1526,32 +1526,24 @@
         
-transports.push(
+return %5B
 %0A       
@@ -1609,37 +1609,32 @@
 : getFormatters(
-isDev
 ),%0A             
@@ -1678,48 +1678,126 @@
     
-)
+%5D
 ;%0A    %7D
- else %7B%0A        transports.push(
+%0A%0A    /**%0A     * If we're in dev mode, just use a console transport.%0A     */%0A    if (isDev) %7B%0A        return %5B
 %0A   
@@ -1876,21 +1876,16 @@
 matters(
-isDev
 ),%0A     
@@ -1903,25 +1903,25 @@
         
-)
+%5D
 ;%0A    %7D%0A
     retu
@@ -1916,29 +1916,132 @@
   %7D%0A
+%0A
     
-return transports
+/**%0A     * We must be in production, so use the Stackdriver logging setup.%0A     */%0A    return %5Bnew lw.LoggingWinston()%5D
 ;%0A%7D%0A

Remove console transport from prod (#50)