const queries = require("../graphql-client/queries");
const moment = require("moment-timezone");
const converter = require("json-2-csv");
const logger = require("../utils/logger");
const fs = require("fs");
const { SecretsManagerClient, GetSecretValueCommand } = require("@aws-sdk/client-secrets-manager");
const { defaultProvider } = require("@aws-sdk/credential-provider-node");
const { InstanceIsLocalStackEnabled, InstanceLocalStackEndpoint } = require("../utils/instanceMgr");

let Client = require("ssh2-sftp-client");

const client = require("../graphql-client/graphql-client").client;
const { sendServerEmail } = require("../email/sendemail");

const ftpSetup = {
  host: process.env.CHATTER_HOST,
  port: process.env.CHATTER_PORT,
  username: process.env.CHATTER_USER,
  privateKey: null,
  debug:
    process.env.NODE_ENV !== "production"
      ? (message, ...data) => logger.log(message, "DEBUG", "api", null, data)
      : () => {},
  algorithms: {
    serverHostKey: ["ssh-rsa", "ssh-dss", "rsa-sha2-256", "rsa-sha2-512", "ecdsa-sha2-nistp256", "ecdsa-sha2-nistp384"]
  }
};

exports.default = async (req, res) => {
  // Only process if in production environment.
  if (process.env.NODE_ENV !== "production") {
    return res.sendStatus(403);
  }
  // Only process if the appropriate token is provided.
  if (req.headers["x-imex-auth"] !== process.env.AUTOHOUSE_AUTH_TOKEN) {
    return res.sendStatus(401);
  }

  // Send immediate response and continue processing.
  res.status(202).json({
    success: true,
    message: "Processing request ...",
    timestamp: new Date().toISOString()
  });

  try {
    logger.log("chatter-start", "DEBUG", "api", null, null);
    const allChatterObjects = [];
    const allErrors = [];

    const { bodyshops } = await client.request(queries.GET_CHATTER_SHOPS); //Query for the List of Bodyshop Clients.
    const specificShopIds = req.body.bodyshopIds; // ['uuid];
    const { start, end, skipUpload } = req.body; //YYYY-MM-DD

    const shopsToProcess =
      specificShopIds?.length > 0 ? bodyshops.filter((shop) => specificShopIds.includes(shop.id)) : bodyshops;
    logger.log("chatter-shopsToProcess-generated", "DEBUG", "api", null, null);

    if (shopsToProcess.length === 0) {
      logger.log("chatter-shopsToProcess-empty", "DEBUG", "api", null, null);
      return;
    }

    await processBatch(shopsToProcess, start, end, allChatterObjects, allErrors);

    const csvToUpload = {
      count: allChatterObjects.length,
      csv: converter.json2csv(allChatterObjects, { emptyFieldValue: "" }),
      filename: `IMEX_ONLINE_solicitation_${moment().format("YYYYMMDD")}.csv`
    };

    if (skipUpload) {
      await fs.promises.writeFile(`./logs/${csvToUpload.filename}`, csvToUpload.csv);
    } else {
      await uploadViaSFTP(csvToUpload);
    }

    await sendServerEmail({
      subject: `Chatter Report ${moment().format("MM-DD-YY")}`,
      text: `Errors:\n${JSON.stringify(allErrors, null, 2)}\n\n
      Uploaded:\n${JSON.stringify(
        {
          filename: csvToUpload.filename,
          count: csvToUpload.count,
          result: csvToUpload.result
        },
        null,
        2
      )}`
    });

    logger.log("chatter-end", "DEBUG", "api", null, null);
  } catch (error) {
    logger.log("chatter-error", "ERROR", "api", null, { error: error.message, stack: error.stack });
  }
};

async function processBatch(shopsToProcess, start, end, allChatterObjects, allErrors) {
  for (const bodyshop of shopsToProcess) {
    try {
      logger.log("chatter-start-shop-extract", "DEBUG", "api", bodyshop.id, {
        shopname: bodyshop.shopname
      });

      const { jobs, bodyshops_by_pk } = await client.request(queries.CHATTER_QUERY, {
        bodyshopid: bodyshop.id,
        start: start ? moment(start).startOf("day") : moment().subtract(1, "days").startOf("day"),
        ...(end && { end: moment(end).endOf("day") })
      });

      const chatterObject = jobs.map((j) => {
        return {
          poc_trigger_code: bodyshops_by_pk.chatterid,
          firstname: j.ownr_co_nm ? null : j.ownr_fn,
          lastname: j.ownr_co_nm ? j.ownr_co_nm : j.ownr_ln,
          transaction_id: j.ro_number,
          email: j.ownr_ea,
          phone_number: j.ownr_ph1,
          transaction_time:
            (j.actual_delivery && moment(j.actual_delivery).tz(bodyshop.timezone).format("YYYYMMDD-HHmm")) || ""
        };
      });
      allChatterObjects.push(...chatterObject);
      logger.log("chatter-end-shop-extract", "DEBUG", "api", bodyshop.id, {
        shopname: bodyshop.shopname
      });
    } catch (error) {
      //Error at the shop level.
      logger.log("chatter-error-shop", "ERROR", "api", bodyshop.id, { error: error.message, stack: error.stack });

      allErrors.push({
        bodyshopid: bodyshop.id,
        imexshopid: bodyshop.imexshopid,
        shopname: bodyshop.shopname,
        fatal: true,
        errors: [error.toString()]
      });
    } finally {
      allErrors.push({
        bodyshopid: bodyshop.id,
        imexshopid: bodyshop.imexshopid,
        shopname: bodyshop.shopname
      });
    }
  }
}

async function getPrivateKey() {
  // Connect to AWS Secrets Manager
  const secretsClientOptions = {
    region: "ca-central-1",
    credentials: defaultProvider()
  };

  if (InstanceIsLocalStackEnabled()) {
    secretsClientOptions.endpoint = InstanceLocalStackEndpoint();
  }

  const client = new SecretsManagerClient(secretsClientOptions);
  const command = new GetSecretValueCommand({ SecretId: "CHATTER_PRIVATE_KEY" });

  logger.log("chatter-get-private-key", "DEBUG", "api", null, null);
  try {
    const { SecretString, SecretBinary } = await client.send(command);
    if (SecretString || SecretBinary) logger.log("chatter-retrieved-private-key", "DEBUG", "api", null, null);
    const chatterPrivateKey = SecretString ? SecretString : Buffer.from(SecretBinary, "base64").toString("ascii");
    return chatterPrivateKey;
  } catch (error) {
    logger.log("chatter-get-private-key", "ERROR", "api", null, { error: error.message, stack: error.stack });
    throw error;
  }
}

async function uploadViaSFTP(csvToUpload) {
  const sftp = new Client();
  sftp.on("error", (errors) =>
    logger.log("chatter-sftp-connection-error", "ERROR", "api", null, { error: errors.message, stack: errors.stack })
  );
  try {
    //Get the private key from AWS Secrets Manager.
    const privateKey = await getPrivateKey();

    //Connect to the FTP and upload all.
    await sftp.connect({ ...ftpSetup, privateKey });

    try {
      csvToUpload.result = await sftp.put(Buffer.from(csvToUpload.csv), `${csvToUpload.filename}`);
      logger.log("chatter-sftp-upload", "DEBUG", "api", null, {
        filename: csvToUpload.filename,
        result: csvToUpload.result
      });
    } catch (error) {
      logger.log("chatter-sftp-upload-error", "ERROR", "api", null, {
        filename: csvToUpload.filename,
        error: csvToUpload.message,
        stack: csvToUpload.stack
      });
      throw error;
    }
  } catch (error) {
    logger.log("chatter-sftp-error", "ERROR", "api", null, { error: error.message, stack: error.stack });
    throw error;
  } finally {
    sftp.end();
  }
}
