
const NluLogic = require("./logic/NluLogic").NluLogic;
const { default: axios } = require("axios");
const projectorConfig = require(`../config/config.${
  process.env.NODE_ENV ?? "testenv"
}.js`).config;

const readModel = require("../lib/esSDK/readModel").readModel;
const writeModel = require("../lib/esSDK/writeModel").writeModel;
const roomManager = require("../lib/licodeSDK/roomManager");
const events = require("events");
const { END } = require("@eventstore/db-client");
const eventEmitter = new events.EventEmitter();
const logger = require("../utils/logger").Logger("DMProjector");
const { streamNameFilter } = require("@eventstore/db-client/dist");
const BotLogic = require("../lib/flowFsm/flow/bot/logic/index").BotLogic;
const EventLogic = require("../lib/flowFsm/flow/event/logic/index").EventLogic;
const SayLogic = require("../lib/flowFsm/flow/say/index").SayLogic;

const DmService = require("../service/dmService").DmService;

const dmService = new DmService();
const botLogic = new BotLogic();
const eventLogic = new EventLogic();
const sayLogic = new SayLogic();

const redisService = require("../service/redis").RedisService;
const oneYearCacheTime = 365 * 24 * 60 * 60;
const oneHourCacheTime = 60 * 60;

const dmDriverMap = new Map();

const esReadModel = new readModel();

const esWriteModel = new writeModel();

export async function run() {
  init();
  getStreamSubscribe();
}

async function init() {
  logger.info("DMProjector 启动");
  const token =
    "eyJ0b2tlbklkIjoiNjMwYzFjOTZlYTAyMjY0MDdhNGEwNDk4IiwiaG9zdCI6ImxpY29kZS5jYW5ueWNvLmNuOjQ0MyIsInNlY3VyZSI6dHJ1ZSwic2lnbmF0dXJlIjoiWmpCak5HRTFaVGxqT1dZNVlUWTJZVE0yT0dKak9UZG1NVGM0TUdSbU5UZzNabU0xWW1Ga013PT0ifQ==";
  const roomName = "main";
  roomManager
    .connect(token, roomName, (result) => {})
    .then(() => {
      eventEmitter.on("sendLicodeMessage", sendLicodeMessage);
    });
  eventEmitter.on("getAskEvent", getAskEvent);
  eventEmitter.on("getActionEvent", getActionEvent);
  eventEmitter.on("dialogOpen", dialogOpen);
  eventEmitter.on("dialogClose", dialogClose);

  // eventEmitter.on('getProcessEvent', getProcessEvent);
}

async function getStreamSubscribe() {
  // const myReadModel = new readModel();
  const filter = streamNameFilter({ prefixes: ["session_"] });
  const subscription = await esReadModel.subscribeAllStream(END, filter);

  for await (const resolvedEvent of subscription) {
    try {
      //获取dmInfo
      const dmId = resolvedEvent.event.streamId.replace("session_", "");
      var dmInfo = JSON.parse(await redisService.get("DM_" + dmId));
      //获取上下文
      var nluInfo = dmInfo?.nluInfo;
      //eventLogic
      var event = {
        key: resolvedEvent.event.type,
        param: {},
      };
      var action = eventLogic.eventInputLogic(nluInfo?.botState, event).actions;
      logger.info("Event Input Logic:", action);

      if (action.lifecycle) {
        if (action.lifecycle.open) {
          eventEmitter.emit("dialogOpen", resolvedEvent.event, dmInfo);
        } else if (action.lifecycle.close) {
          eventEmitter.emit("dialogClose", resolvedEvent.event, dmInfo);
        }
      } else if (action.ctrl) {
        if (action.ctrl.next) {
          eventEmitter.emit(
            "getActionEvent",
            resolvedEvent.event,
            "next",
            dmInfo
          );
        } else if (action.ctrl.timeout) {
          eventEmitter.emit(
            "getActionEvent",
            resolvedEvent.event,
            "timeout",
            dmInfo
          );
        } else if (action.ctrl.utterance) {
          eventEmitter.emit("getAskEvent", resolvedEvent.event, dmInfo);
        }
      } else if (action.log) {
        logger.info(
          "【eventInputLogic】%s ===",
          action.log.next,
          JSON.stringify(resolvedEvent.event.type)
        );
      } else {
        logger.info(
          "resolvedEvent.event.type: ",
          JSON.stringify(resolvedEvent.event.type)
        );
      }
    } catch (e) {
      logger.error("event error", e);
    }
  }
}

export async function getActionEvent(eventData, queryText, dmInfo) {
  logger.info("【action】eventData: ", eventData);
  var nluInfo = dmInfo.nluInfo;
  const streamId = eventData.streamId;
  const sessionId = dmInfo.dm.sessionId;
  const nluId = nluInfo.nluId;
  const id = eventData.data.id;

  const bot = nluInfo.botInfo;
  const botId = bot.botId;
  const supplier = bot.supplierName;
  const dmId = nluInfo.dmId;

  //请求nlu
  axios({
    url: projectorConfig.projector.askUrl + "/session-manage/dm/ask/original",
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    data: {
      askId: id,
      botKey: botId,
      nluId: nluId,
      sessionId: sessionId,
      queryText: queryText,
      config: { premise: { supplier: supplier } },
    },
  })
    .then(function (response) {
      // const response = getResponse();
      const responseData = response.data;
      logger.info("responseData++++++++++++++=", responseData);
      if (!(Object.keys(responseData).length === 0)) {
        //answer写入ES
        writeToES(dmId, "answer", {
          id: id,
          nluId: nluId,
          dmId: dmId,
          actionId: id,
          data: responseData,
        });
        //处理nlu
        toDoNluLogic(dmInfo, responseData, id, nluId, sessionId, dmId);
      }
    })
    .catch(function (error) {
      logger.info(error);
      throw error;
    });
}

async function dialogOpen(eventData, dmInfo) {
  logger.info("【DIALOG_OPEN】  eventData: ", eventData);

  try {
    var nluInfo = dmInfo.nluInfo;
    const streamId = eventData.streamId;
    const roomId = "630322799a94c5505b9b2a16";
    const dmId = streamId.replace("session_", "");
    const botKey = eventData.data.data.botKey;
    var user_intent_check = true;
    if (eventData.data.data.user_intent_check === false) {
      user_intent_check = false;
    }
    const id = eventData.data.id;
    const sessionId = dmInfo.dm.sessionId;
    const cacheBot = JSON.parse(
      await redisService.get(`BotToken_${sessionId}`)
    );

    const bot = await getBotidByBotkey(botKey, dmId, cacheBot.id);
    const botId = bot.botKey;

    await redisService.set(
      `BotToken_${sessionId}`,
      { ...bot, ...cacheBot },
      oneHourCacheTime
    );

    const partialBot = {
      botId: botId,
      botKey: botKey,
      supplierName: bot.supplierName,
    };

    if (botId) {
      const option = {
        url:
          projectorConfig.projector.askUrl + "/session-manage/dm/ask/original",
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        data: {
          askId: id,
          sessionId: sessionId,
          botKey: botId,
          queryText: user_intent_check ? "start" : "start:user_intent_check",
          config: { premise: { supplier: bot.supplierName } },
        },
      };
      const result = await axios(option);
      var nluId = result.data.result?.session_id;
      //处理start业务
      const instanceRes = await initInstance(
        sessionId,
        dmId,
        partialBot,
        nluId
      );
      logger.info("result", result.data);
      //写入answer事件
      await writeToES(dmId, "answer", {
        id: id,
        dmid: dmId,
        nluId: nluId,
        data: result.data,
      });
      dmInfo.nluInfo = {
        nluId: nluId,
        botInfo: partialBot,
      };
      //处理nlu
      toDoNluLogic(dmInfo, result.data, id, nluId, sessionId, dmId);
    }
  } catch (error) {
    logger.error(error);
  }
}

async function dialogClose(eventData, dmInfo) {
  try {
    logger.info("【DIALOG_CLOSE】  eventData: ", eventData);
    const roomId = "630322799a94c5505b9b2a16";
    const streamId = eventData.streamId;
    const dmId = streamId.replace("session_", "");
    const sessionId = dmInfo.dm.sessionId; //关闭对话
    axios({
      method: "post",
      data: {
        sessionId,
        dmId,
        ...eventData.data,
      },

      url: `${projectorConfig.projector.dmhubOfSessionUrl}/dmhub-of-session/dialog/close`,
      headers: {
        "Content-Type": "application/json",
      },
    });

    var message = {
      name: "message",
      action: "DIALOG_CLOSE",
      data: {},
    };
    const licodeMessage = getLicodeMessage(
      sessionId,
      roomId,
      message,
      ["default"],
      "client"
    );
    eventEmitter.emit("sendLicodeMessage", licodeMessage);
  } catch (error) {
    logger.error(error);
  }
}

async function getAskEvent(eventData, dmInfo) {
  logger.info("【utterance】eventData: ", eventData);
  try {
    var nluInfo = dmInfo.nluInfo;
    const streamId = eventData.streamId;
    const sessionId = dmInfo.dm.sessionId;
    const nluId = nluInfo.nluId;
    const id = eventData.data.id;

    const roomId = "630322799a94c5505b9b2a16";
    const receivers = ["default"];

    const bot = nluInfo.botInfo;
    const botId = bot.botId;
    const supplier = bot.supplierName;
    const dmId = nluInfo.dmId;

    var role = "";
    var userId = "";
    var from = "";

    //ASK事件发到licode
    if (true) {
      var askData = {
        name: "utterance",
        data: {
          role: role,
          userId: userId,
          where: from,
          queryText: eventData.data.data.text,
        },
      };
      var licodeMessage = getLicodeMessage(
        sessionId,
        roomId,
        askData,
        receivers,
        "client"
      );
      eventEmitter.emit("sendLicodeMessage", licodeMessage);
      //发送 过程逻辑
      var processLogic = {
        name: "process",
        action: "utterance",
        role: role,
        userId: userId,
        where: from,
        data: { queryText: eventData.data.data.text },
      };
      licodeMessage = getLicodeMessage(
        sessionId,
        roomId,
        processLogic,
        receivers,
        "client"
      );
      eventEmitter.emit("sendLicodeMessage", licodeMessage);
    }

    axios({
      url: projectorConfig.projector.askUrl + "/session-manage/dm/ask/original",
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      data: {
        askId: id,
        botKey: botId,
        nluId: nluId,
        sessionId: sessionId,
        queryText: eventData.data.data.text,
        config: { premise: { supplier: supplier } },
      },
    })
      .then(function (response) {
        const responseData = response.data;
        logger.info("responseData++++++++++++++=", responseData);
        if (!(Object.keys(responseData).length === 0)) {
          //answer写入ES
          writeToES(dmId, "answer", {
            id: id,
            dmId: dmId,
            nluId: nluId,
            data: responseData,
          });
          //处理nlu
          toDoNluLogic(dmInfo, responseData, id, nluId, sessionId, dmId);

          //返回回答流程事件
          var processLogic = {
            name: "process",
            action: "answer",
            role: "bot",
            userId: "机器人001",
            where: "百度Unit",
            data: { ...responseData },
          };
          const licodeMessage = getLicodeMessage(
            sessionId,
            roomId,
            processLogic,
            ["default"],
            "client"
          );
          eventEmitter.emit("sendLicodeMessage", licodeMessage);
        }
      })
      .catch(function (error) {
        logger.info(error);

        throw error;
      });
  } catch (error) {
    logger.error(error);
  }
}

async function toDoNluLogic(dmInfo, responseData, id, nluId, sessionId, dmId) {
  if (responseData.error_code != 0) {
    return;
  }
  var nluInfo = dmInfo.nluInfo;
  // Nlu逻辑
  var botState = await botLogic.getBotState({}, responseData);
  // outputLogic
  var outputEventAction = eventLogic.eventOutputLogic(botState);
  // sayLogic
  var sayAction = sayLogic.getSayAction(botState);
  // 回调
  toRender(sessionId, dmId, botState, sayAction, outputEventAction, nluInfo);
  // 存入redis
  if (nluInfo) {
    nluInfo["botState"] = botState;
    nluInfo["dmId"] = dmId;
  } else {
    nluInfo = {
      botInfo: nluInfo.botInfo,
      botState: botState,
      dmId,
    };
  }
  dmInfo.nluInfo = nluInfo;
  await redisService.set("DM_" + dmId, dmInfo, oneYearCacheTime);
}

function getEventType(botState) {
  var eventType = "render";
  return eventType;
}

function getOldDMDriverData(sayAction) {
  var dmDriverDatas = {
    type: "lazy",
    data: [],
  };
  for (var action of sayAction) {
    var data = {
      text: action.say,
      role: "bot",
      type: "tts",
      sp: "unit_public_v3",
    };
    dmDriverDatas.data.push(data);
  }

  return dmDriverDatas;
}

async function toRender(
  sessionId,
  dmId,
  botState,
  sayAction,
  outputEventAction,
  nlu
) {
  var option = {
    method: "post",
    data: {
      sessionId: sessionId,
      dmId: dmId,
      event: {
        type: "render",
        key: outputEventAction.eventKey,
        param: {
          seqid: "seq_" + new Date().getTime(),
          bot: {
            key: nlu?.botInfo.botKey,
            actions: sayAction,
            vars:botState.SYS_VARS
          },
        },
      },
    },
    url: projectorConfig.projector.restBackUrl + "/api/videoBot/receiveMsg",
    headers: {
      "Content-Type": "application/json",
    },
  };
  try {
    logger.info(
      "【sendEvent】%s 发起回调: ",
      outputEventAction.eventKey,
      JSON.stringify(option)
    );
    await axios(option);
  } catch (error) {
    logger.error(
      "【sendEvent】%s 发起回调: ",
      outputEventAction.eventKey,
      error
    );
  }

  //回显发送到licode
  var dmDriver = dmDriverMap.get(sessionId);
  if (!dmDriver) {
    dmDriver = new DMDriver(sessionId);
    dmDriverMap.set(sessionId, dmDriver);
  }
  dmDriver.onOutput = (event) => {
    const licodeMessage = getLicodeMessage(
      sessionId,
      "630322799a94c5505b9b2a16",
      event.event,
      ["default"],
      "client"
    );
    eventEmitter.emit("sendLicodeMessage", licodeMessage);
  };
  var actionData = getOldDMDriverData(sayAction);
  dmDriver.drive(actionData);
}

async function sendEvent(sessionId, dmId, eventType) {
  var option = {
    method: "post",
    data: {
      sessionId: sessionId,
      dmId: dmId,
      renderId: "",
      type: "lazy",
      eventType: eventType,
      data: [],
    },
    url: projectorConfig.projector.restBackUrl + "/api/videoBot/receiveMsg",
    headers: {
      "Content-Type": "application/json",
    },
  };
  try {
    logger.info( "【sendEvent】%s 发起回调: ",eventType, JSON.stringify(option));
    await axios(option);
  } catch (error) {
    logger.error("【sendEvent】%s 发起回调: ", eventType, error);
  }
}

async function getBotidByBotkey(botKey, dmId, resourceId) {
  const result = await dmService.getBotByBotKey(botKey, dmId, resourceId);

  return { botKey: result.botId, supplierName: result.supplierName };
}

async function initInstance(sessionId, dmId, bot, nluId) {
  try {
    const result = await axios({
      method: "post",
      data: {
        sessionId,
        dmId,
        bot,
        nluId,
      },

      url: `${projectorConfig.projector.dmhubOfSessionUrl}/dmhub-of-session/instance/init`,
      headers: {
        "Content-Type": "application/json",
      },
    });

    return result.data;
  } catch (error) {
    logger.error(error);
    return error;
  }
}

function sendLicodeMessage(msg) {
  try {
    roomManager.sendGenericMessage(msg);
  } catch (e) {
    logger.error("sendLicodeMessage", e);
  }
}

async function writeToES(dmId, eventType, data) {
  // const myWriteModel = new writeModel();
  var eventMessage = {
    ...data,
  };
  esWriteModel.appendToStream("session_" + dmId, eventType, eventMessage);
  logger.info("write to Event Store:", eventMessage);
}
function getErrorMessage(sessionId, roomId, responseData, requestParams) {
  console.log("responseData: ", responseData);
  const errorCode = responseData.errorCode || "none";
  const errorInfo = responseData.errorInfo || "";
  return {
    roomId: roomId,
    data: {
      sessionId: sessionId,
      event: {
        name: "error",
        data: {
          errorCode: errorCode,
          errorInfo: errorInfo,
          source: requestParams,
        },
      },
    },
    options: {
      receivers: ["parent"],
    },
  };
}
function getLicodeMessage(sessionId, roomId, data, receivers, to) {
  return {
    roomId: roomId,
    data: {
      sessionId: sessionId,
      event: data,
      to: to,
      sourceRoom: roomId,
    },
    options: {
      receivers: receivers,
    },
  };
}
