// Load environment variables THIS MUST BE AT THE TOP
const InstanceManager = require("../utils/instanceMgr").default;
const winston = require("winston");
const WinstonCloudWatch = require("winston-cloudwatch");
const { uploadFileToS3 } = require("./s3");
const { v4 } = require("uuid");
const { InstanceRegion, InstanceIsLocalStackEnabled, InstanceLocalStackEndpoint } = require("./instanceMgr");
const getHostNameOrIP = require("./getHostNameOrIP");
const client = require("../graphql-client/graphql-client").client;
const queries = require("../graphql-client/queries");

const LOG_LEVELS = {
  error: { level: 0, name: "error" },
  warn: { level: 1, name: "warn" },
  info: { level: 2, name: "info" },
  http: { level: 3, name: "http" },
  verbose: { level: 4, name: "verbose" },
  debug: { level: 5, name: "debug" },
  silly: { level: 6, name: "silly" }
};

const LOG_LENGTH_LIMIT = 256 * 1024; // 256KB

const S3_BUCKET_NAME = InstanceManager({
  imex: "imex-large-log",
  rome: "rome-large-log"
});

const region = InstanceRegion();

const estimateLogSize = (logEntry) => {
  let estimatedSize = 0;
  for (const key in logEntry) {
    if (Object.prototype.hasOwnProperty.call(logEntry, key)) {
      const value = logEntry[key];
      if (value === undefined || value === null) {
        estimatedSize += key.length; // Only count the key length if value is undefined or null
      } else {
        estimatedSize += key.length + (typeof value === "string" ? value.length : JSON.stringify(value).length);
      }
    }
  }
  return estimatedSize;
};

const normalizeLevel = (level) => (level ? level.toLowerCase() : LOG_LEVELS.debug.name);

const createLogger = () => {
  try {
    const isLocal = InstanceIsLocalStackEnabled();
    const logGroupName = isLocal ? "development" : process.env.CLOUDWATCH_LOG_GROUP;

    const winstonCloudwatchTransportDefaults = {
      logGroupName: logGroupName,
      awsOptions: {
        region
      },
      jsonMessage: true
    };

    if (isLocal) {
      winstonCloudwatchTransportDefaults.awsOptions.endpoint = InstanceLocalStackEndpoint();
    }

    const levelFilter = (levels) => {
      return winston.format((info) => {
        if (Array.isArray(levels)) {
          return levels.includes(info.level) ? info : false;
        } else {
          return info.level === levels ? info : false;
        }
      })();
    };

    const createProductionTransport = (level, logStreamName, filters) => {
      return new WinstonCloudWatch({
        level,
        logStreamName: logStreamName || level,
        format: levelFilter(filters || level),
        ...winstonCloudwatchTransportDefaults
      });
    };

    const internalHostname = process.env.HOSTNAME || getHostNameOrIP();

    const getDevelopmentTransports = () => [
      new winston.transports.Console({
        level: "silly",
        format: winston.format.combine(
          winston.format.colorize(),
          winston.format.timestamp(),
          winston.format.printf(({ level, message, timestamp, user, record, meta }) => {
            const hostnameColor = `\x1b[34m${internalHostname}\x1b[0m`; // Blue
            const timestampColor = `\x1b[36m${timestamp}\x1b[0m`; // Cyan
            const labelColor = "\x1b[33m"; // Yellow
            const separatorColor = "\x1b[35m|\x1b[0m"; // Magenta for separators

            return `${timestampColor} [${hostnameColor}] [${level}]: ${message} ${
              user ? `${separatorColor} ${labelColor}user:\x1b[0m ${JSON.stringify(user)}` : ""
            } ${record ? `${separatorColor} ${labelColor}record:\x1b[0m ${JSON.stringify(record)}` : ""}${
              meta
                ? `\n${separatorColor} ${labelColor}meta:\x1b[0m ${JSON.stringify(meta, null, 2)} ${separatorColor}`
                : ""
            }`;
          })
        )
      })
    ];

    const getProductionTransports = () => [
      createProductionTransport("error"),
      createProductionTransport("warn"),
      createProductionTransport("info"),
      createProductionTransport("silly", "debug", ["http", "verbose", "debug", "silly"])
    ];

    const winstonLogger = winston.createLogger({
      format: winston.format.json(),
      transports:
        process.env.NODE_ENV === "production"
          ? getProductionTransports()
          : [...getDevelopmentTransports(), ...getProductionTransports()]
    });

    if (isLocal) {
      winstonLogger.debug(
        `CloudWatch set to LocalStack end point: ${winstonCloudwatchTransportDefaults.awsOptions.endpoint}`
      );
    }

    const log = (message, type, user, record, meta, upload) => {
      const logEntry = {
        level: normalizeLevel(type),
        message,
        user,
        record,
        hostname: internalHostname,
        meta
      };

      const uploadLogToS3 = (logEntry, message, type, user) => {
        const uniqueId = v4();
        const dateTimeString = new Date().toISOString().replace(/:/g, "-");
        const envName = process.env?.NODE_ENV ? process.env.NODE_ENV : "";
        const logStreamName = `${envName}-${internalHostname}-${dateTimeString}-${uniqueId}.json`;
        const logString = JSON.stringify(logEntry);
        const webPath = isLocal
          ? `https://${S3_BUCKET_NAME}.s3.localhost.localstack.cloud:4566/${logStreamName}`
          : `https://${S3_BUCKET_NAME}.s3.${region}.amazonaws.com/${logStreamName}`;

        uploadFileToS3({ bucketName: S3_BUCKET_NAME, key: logStreamName, content: logString })
          .then(() => {
            log("A log file has been uploaded to S3", "info", "S3", null, {
              logStreamName,
              webPath,
              message: message?.slice(0, 200),
              type,
              user
            });
          })
          .catch((err) => {
            log("Error in S3 Upload", "error", "S3", null, {
              logStreamName,
              webPath,
              message: message?.slice(0, 100),
              type,
              user,
              errorMessage: err?.message?.slice(0, 100)
            });
          });
      };

      const checkAndUploadLog = () => {
        const estimatedSize = estimateLogSize(logEntry);

        if (estimatedSize > LOG_LENGTH_LIMIT * 0.9 || estimatedSize > LOG_LENGTH_LIMIT) {
          uploadLogToS3(logEntry, message, type, user);
          return true;
        }
        return false;
      };
      // Upload log immediately if upload is true, otherwise check the log size.
      if (upload) {
        uploadLogToS3(logEntry, message, type, user);
        return;
      }

      if (checkAndUploadLog()) return;

      winstonLogger.log(logEntry);
    };

    const LogIntegrationCall = async ({
      platform,
      method,
      name,
      jobid,
      paymentid,
      billid,
      status,
      bodyshopid,
      email
    }) => {
      try {
        //Insert the record.
        await client.request(queries.INSERT_INTEGRATION_LOG, {
          log: {
            platform,
            method,
            name,
            jobid,
            paymentid,
            billid,
            status: status?.toString() ?? "0",
            bodyshopid,
            email
          }
        });
      } catch (error) {
        console.trace("Stack", error?.stack);
        log("integration-log-error", "ERROR", email, null, {
          message: error?.message,
          stack: error?.stack,
          platform,
          method,
          name,
          jobid,
          paymentid,
          billid,
          status,
          bodyshopid,
          email
        });
      }
    };

    return {
      log,
      logger: winstonLogger,
      LogIntegrationCall
    };
  } catch (e) {
    console.error("Error setting up enhanced Logger, defaulting to console.: " + e?.message || "");
    return {
      log: console.log,
      logger: console.log,
      LOG_LEVELS
    };
  }
};

module.exports = createLogger();
