const client = require("../graphql-client/graphql-client").client;
const {
  FIND_BODYSHOP_BY_MESSAGING_SERVICE_SID,
  UNARCHIVE_CONVERSATION,
  CREATE_CONVERSATION,
  INSERT_MESSAGE,
  CHECK_PHONE_NUMBER_OPT_OUT,
  DELETE_PHONE_NUMBER_OPT_OUT,
  INSERT_PHONE_NUMBER_OPT_OUT
} = require("../graphql-client/queries");
const { phone } = require("phone");
const { admin } = require("../firebase/firebase-handler");
const InstanceManager = require("../utils/instanceMgr").default;

// Note: When we handle different languages, we might need to adjust these keywords accordingly.
const optInKeywords = ["START", "YES", "UNSTOP"];
const optOutKeywords = ["STOP", "STOPALL", "UNSUBSCRIBE", "CANCEL", "END", "QUIT", "REVOKE", "OPTOUT"];

// System Message text, will also need to be localized if we support multiple languages
const systemMessageOptions = {
  optIn: "Customer has opted-in",
  optOut: "Customer has opted-out"
};

/**
 * Receive SMS messages from Twilio and process them
 * @param req
 * @param res
 * @returns {Promise<*>}
 */
const receive = async (req, res) => {
  const {
    logger,
    ioRedis,
    ioHelpers: { getBodyshopRoom, getBodyshopConversationRoom }
  } = req;

  const loggerData = {
    msid: req.body.SmsMessageSid,
    text: req.body.Body,
    image: !!req.body.MediaUrl0,
    image_path: generateMediaArray(req.body, logger)
  };

  logger.log("sms-inbound", "DEBUG", "api", null, loggerData);

  if (!req.body || !req.body.MessagingServiceSid || !req.body.SmsMessageSid) {
    logger.log("sms-inbound-error", "ERROR", "api", null, {
      ...loggerData,
      type: "malformed-request"
    });
    return res.status(400).json({ success: false, error: "Malformed Request" });
  }

  try {
    // Step 1: Find the bodyshop and existing conversation
    const response = await client.request(FIND_BODYSHOP_BY_MESSAGING_SERVICE_SID, {
      mssid: req.body.MessagingServiceSid,
      phone: phone(req.body.From).phoneNumber
    });

    if (!response.bodyshops[0]) {
      return res.status(400).json({ success: false, error: "No matching bodyshop" });
    }

    const bodyshop = response.bodyshops[0];
    const normalizedPhone = phone(req.body.From).phoneNumber.replace(/^\+1/, ""); // Normalize phone number (remove +1 for CA numbers)
    const messageText = (req.body.Body || "").trim().toUpperCase();

    // Step 2: Process conversation
    const sortedConversations = bodyshop.conversations.sort((a, b) => new Date(a.created_at) - new Date(b.created_at));
    const existingConversation = sortedConversations.length
      ? sortedConversations[sortedConversations.length - 1]
      : null;

    let conversationid;

    if (existingConversation) {
      conversationid = existingConversation.id;
      if (existingConversation.archived) {
        await client.request(UNARCHIVE_CONVERSATION, {
          id: conversationid,
          archived: false
        });
      }
    } else {
      const newConversationResponse = await client.request(CREATE_CONVERSATION, {
        conversation: {
          bodyshopid: bodyshop.id,
          phone_num: phone(req.body.From).phoneNumber,
          archived: false
        }
      });
      const createdConversation = newConversationResponse.insert_conversations.returning[0];
      conversationid = createdConversation.id;
    }

    // Step 3: Handle opt-in or opt-out keywords
    let systemMessageText = "";
    let socketEventType = "";

    if (optInKeywords.includes(messageText) || optOutKeywords.includes(messageText)) {
      // Check if the phone number is in phone_number_opt_out
      const optOutCheck = await client.request(CHECK_PHONE_NUMBER_OPT_OUT, {
        bodyshopid: bodyshop.id,
        phone_number: normalizedPhone
      });

      // Opt In
      if (optInKeywords.includes(messageText)) {
        // Handle opt-in
        if (optOutCheck.phone_number_opt_out.length > 0) {
          // Phone number is opted out; delete the record
          const deleteResponse = await client.request(DELETE_PHONE_NUMBER_OPT_OUT, {
            bodyshopid: bodyshop.id,
            phone_number: normalizedPhone
          });

          logger.log("sms-opt-in-success", "INFO", "api", null, {
            msid: req.body.SmsMessageSid,
            bodyshopid: bodyshop.id,
            phone_number: normalizedPhone,
            affected_rows: deleteResponse.delete_phone_number_opt_out.affected_rows
          });

          systemMessageText = systemMessageOptions.optIn;
          socketEventType = "phone-number-opted-in";
        }
      }
      // Opt Out
      else if (optOutKeywords.includes(messageText)) {
        // Handle opt-out
        if (optOutCheck.phone_number_opt_out.length === 0) {
          // Phone number is not opted out; insert a new record
          const now = new Date().toISOString();
          const optOutInput = {
            bodyshopid: bodyshop.id,
            phone_number: normalizedPhone,
            created_at: now,
            updated_at: now
          };

          const insertResponse = await client.request(INSERT_PHONE_NUMBER_OPT_OUT, {
            optOutInput: [optOutInput]
          });

          logger.log("sms-opt-out-success", "INFO", "api", null, {
            msid: req.body.SmsMessageSid,
            bodyshopid: bodyshop.id,
            phone_number: normalizedPhone,
            affected_rows: insertResponse.insert_phone_number_opt_out.affected_rows
          });

          systemMessageText = systemMessageOptions.optOut;
          socketEventType = "phone-number-opted-out";
        }
      }

      // Insert system message if an opt-in or opt-out action was taken
      if (systemMessageText) {
        const systemMessage = {
          msid: `SYS_${req.body.SmsMessageSid}_${Date.now()}`, // Unique ID for system message
          text: systemMessageText,
          conversationid,
          isoutbound: false,
          userid: null,
          image: false,
          image_path: null,
          is_system: true
        };

        const systemMessageResponse = await client.request(INSERT_MESSAGE, {
          msg: systemMessage,
          conversationid
        });

        const insertedSystemMessage = systemMessageResponse.insert_messages.returning[0];

        // Emit WebSocket events for system message
        const broadcastRoom = getBodyshopRoom(bodyshop.id);
        const conversationRoom = getBodyshopConversationRoom({
          bodyshopId: bodyshop.id,
          conversationId: conversationid
        });

        const systemPayload = {
          isoutbound: false,
          conversationId: conversationid,
          updated_at: insertedSystemMessage.updated_at,
          msid: insertedSystemMessage.msid,
          existingConversation: !!existingConversation,
          newConversation: !existingConversation ? insertedSystemMessage.conversation : null
        };

        ioRedis.to(broadcastRoom).emit("new-message-summary", {
          ...systemPayload,
          summary: true
        });

        ioRedis.to(conversationRoom).emit("new-message-detailed", {
          newMessage: insertedSystemMessage,
          ...systemPayload,
          summary: false
        });

        // Emit opt-in or opt-out event
        ioRedis.to(broadcastRoom).emit(socketEventType, {
          bodyshopid: bodyshop.id,
          phone_number: normalizedPhone
        });
      }
    }

    // Step 4: Insert the original message
    const newMessage = {
      msid: req.body.SmsMessageSid,
      text: req.body.Body,
      image: !!req.body.MediaUrl0,
      image_path: generateMediaArray(req.body, logger),
      isoutbound: false,
      userid: null,
      conversationid,
      is_system: false
    };

    const insertresp = await client.request(INSERT_MESSAGE, {
      msg: newMessage,
      conversationid
    });

    const message = insertresp?.insert_messages?.returning?.[0];
    const conversation = message?.conversation || null;

    if (!conversation) {
      throw new Error("Conversation data is missing from the response.");
    }

    // Step 5: Notify clients for original message
    const conversationRoom = getBodyshopConversationRoom({
      bodyshopId: conversation.bodyshop.id,
      conversationId: conversation.id
    });

    const commonPayload = {
      isoutbound: false,
      conversationId: conversation.id,
      updated_at: message.updated_at,
      msid: message.msid
    };

    const broadcastRoom = getBodyshopRoom(conversation.bodyshop.id);

    ioRedis.to(broadcastRoom).emit("new-message-summary", {
      ...commonPayload,
      existingConversation: !!existingConversation,
      newConversation: !existingConversation ? conversation : null,
      summary: true
    });

    ioRedis.to(conversationRoom).emit("new-message-detailed", {
      newMessage: message,
      ...commonPayload,
      newConversation: !existingConversation ? conversation : null,
      existingConversation: !!existingConversation,
      summary: false
    });

    // Step 6: Send FCM notification
    const fcmresp = await admin.messaging().send({
      topic: `${message.conversation.bodyshop.imexshopid}-messaging`,
      notification: {
        title: InstanceManager({
          imex: `ImEX Online Message - ${message.conversation.phone_num}`,
          rome: `Rome Online Message - ${message.conversation.phone_num}`
        }),
        body: message.image_path ? `Image ${message.text}` : message.text
      },
      data: {
        type: "messaging-inbound",
        conversationid: message.conversationid || "",
        text: message.text || "",
        messageid: message.id || "",
        phone_num: message.conversation.phone_num || ""
      }
    });

    logger.log("sms-inbound-success", "DEBUG", "api", null, {
      newMessage,
      fcmresp
    });

    res.status(200).send("");
  } catch (e) {
    handleError({
      req,
      res,
      logger,
      error: e,
      context: "RECEIVE_MESSAGE"
    });
  }
};

/**
 * Generate media array from the request body
 * @param body
 * @param logger
 * @returns {null|*[]}
 */
const generateMediaArray = (body) => {
  const { NumMedia } = body;
  if (parseInt(NumMedia, 10) > 0) {
    const ret = [];
    for (let i = 0; i < parseInt(NumMedia, 10); i++) {
      ret.push(body[`MediaUrl${i}`]);
    }
    return ret;
  } else {
    return null;
  }
};

/**
 * Handle error logging and response
 * @param req
 * @param error
 * @param res
 * @param context
 * @param logger
 */
const handleError = ({ req, error, res, context, logger }) => {
  logger.log("sms-inbound-error", "ERROR", "api", null, {
    msid: req.body.SmsMessageSid,
    text: req.body.Body,
    image: !!req.body.MediaUrl0,
    image_path: generateMediaArray(req.body, logger),
    messagingServiceSid: req.body.MessagingServiceSid,
    context,
    error
  });

  res.status(500).json({ error: error.message || "Internal Server Error" });
};

module.exports = {
  receive
};
