<script setup lang="ts">
import {
  FACEID_LINENESS_URL,
  WEBSOCKET_FOR_FACE_ID,
} from "@/constants/constants";
import { httpApp } from "@/plugins/axiosApp";
import { useTimers } from "@/setups/useTimers";
import { useWebSocket } from "@/setups/useWebSocket";
import { store } from "@/store/store";
import { showLogout } from "@/utils/utils";
import { onUnmounted } from "vue";

const emit = defineEmits<{ (e: "close-faceid"): void }>();

const { addTimeout, addInterval } = useTimers();

let electronDir = "";
if (typeof window.nodeRequire != "undefined") {
  const remote: import("electron").Remote =
    window.nodeRequire("electron").remote;
  electronDir = "file://" + remote.app.getAppPath() + "/";
  logger({ cnl: ["server"], pgn: "活体检测弹出框", ext: { electronDir } });
}

logger({ cnl: ["server", "local"], pgn: "活体检测弹出框", act: "弹出框" });

const { startWS } = useWebSocket();
startWS(
  WEBSOCKET_FOR_FACE_ID + `?key=${store.user.key}&token=${store.user.token}`,
  onFaceIdMessage,
  "活体检测socket"
);

// 退出faceid的办法只能二选一。下面是socket返回结果退出。
let processedBySocketMsg = $ref(false);
function onFaceIdMessage(event: MessageEvent<string>) {
  logger({ cnl: ["server"], act: "活体检测FaceId response", dtl: event.data });
  if (processedByTimeout) {
    logger({ cnl: ["server"], act: "活体检测websocket消息来迟了，拒绝处理" });
    return;
  }
  if (event.data.indexOf("verifyResult") > -1) {
    logger({
      cnl: ["server"],
      pgn: "活体检测弹出框",
      dtl: "websocket得到verifyResult消息",
    });
    processedBySocketMsg = true;
    try {
      const receivedMsgData: { content: { returnMsgJson: string } } =
        JSON.parse(event.data);
      const receivedMsg: FaceIDMessage = JSON.parse(
        receivedMsgData.content.returnMsgJson
      );
      // 两个结束点。第二个结束点：从websocket得到消息。
      void faceTestEnd(receivedMsg);
    } catch (error) {
      logger({
        cnl: ["server"],
        pgn: "活体检测弹出框",
        act: "处理websocket消息时",
        possibleError: error,
      });
    }
    emit("close-faceid");
  }
}

store.exam.isDoingFaceLiveness = true;
onUnmounted(() => {
  logger({ cnl: ["server"], pgn: "关闭活体检测弹出框" });
  clearTimeout(iframeDomReadyTimeout);
  store.exam.isDoingFaceLiveness = false;
});

let showIframe = $ref(false);
let redoBtnDisabled = $ref(true);
let redoBtnShow = $ref(false);
let redoBtnMsg = $ref("");

/** 设置重做按钮的文本 */
function showRedo(redoMsg: string) {
  showIframe = false;
  redoBtnDisabled = false;
  redoBtnShow = true;
  redoBtnMsg = redoMsg || "系统繁忙，请手动点击重试";
  logger({ cnl: ["server"], pgn: "活体检测弹出框", act: redoBtnMsg });
}

async function updateFaceVerifyMsg(errorMsg: string) {
  logger({
    cnl: ["server"],
    pgn: "活体检测弹出框",
    act: "faceid页面报错",
    dtl: errorMsg,
  });
  await httpApp.get(
    "/api/ecs_oe_student/examFaceLivenessVerify/updateFaceLivenessVerify/" +
      store.exam.examRecordDataId,
    { params: { errorMsg: errorMsg } }
  );
}

// 退出faceid的办法只能二选一。下面是timeout退出。
let processedByTimeout = false;
async function faceidLoadedButTimeouted() {
  if (processedBySocketMsg) {
    logger({
      cnl: ["server"],
      act: "faceidLoadedButTimeouted",
      dtl: "已被websocket处理了",
    });
    return;
  }

  processedByTimeout = true;
  logger({
    cnl: ["server"],
    act: "faceidLoadedButTimeouted",
    dtl: "进入超时处理",
  });
  await httpApp
    .get(
      "/api/ecs_oe_student/examFaceLivenessVerify/getFaceVerifyResult/" +
        faceVerifyId,
      { "axios-retry": { retries: 4 } }
    )
    .then((response) => {
      logger({
        cnl: ["server"],
        pgn: "活体检测弹出框",
        act: "60秒超时非websocket处理结果",
      });
      const receivedMsg: FaceIDMessage = response.data;
      void faceTestEnd(receivedMsg);
      emit("close-faceid");
    })
    .catch((error) => {
      logger({
        cnl: ["server"],
        pgn: "活体检测弹出框",
        act: "60秒超时非websocket处理结果--api失败",
        possibleError: error,
      });
    });
}

type FaceIDMessage = {
  verifyCount: number;
  verifyResult: string;
};
/** 指定动作检测结束 */
async function faceTestEnd(receivedMsg: FaceIDMessage) {
  logger({
    cnl: ["server"],
    pgn: "活体检测弹出框",
    act: "指定动作检测结束",
    ext: {
      verifyCount: receivedMsg.verifyCount,
      verifyResult: receivedMsg.verifyResult,
    },
  });
  if (receivedMsg.verifyCount == 1) {
    if (receivedMsg.verifyResult == "TIME_OUT") {
      if (store.QECSConfig.FACE_VERIFY_FORCE_EXIT === "false") {
        logger({
          cnl: ["server"],
          act: "第一次指定动作检测超时，但是因为配置不强制，于是允许继续考试",
        });
        return;
      }
      logger({
        cnl: ["server"],
        act: "第一次指定动作检测超时，检测失败，系统退出，请重新登录",
      });
      showLogout("第一次指定动作检测超时，检测失败，系统退出，请重新登录");
    } else if (receivedMsg.verifyResult == "VERIFY_FAILED") {
      if (store.QECSConfig.FACE_VERIFY_FORCE_EXIT === "false") {
        logger({
          cnl: ["server"],
          act: "第一次指定动作检测失败，但是因为配置不强制，于是允许继续考试",
        });
        return;
      }
      logger({
        cnl: ["server"],
        act: "第一次指定动作检测失败，系统退出，请重新登录",
      });
      showLogout("第一次指定动作检测失败，系统退出，请重新登录");
    } else if (receivedMsg.verifyResult == "NOT_ONESELF") {
      if (store.QECSConfig.FACE_VERIFY_FORCE_EXIT === "false") {
        logger({
          cnl: ["server"],
          act: "指定动作检测不合格，但是因为配置不强制，于是允许继续考试",
        });
        return;
      }
      logger({ cnl: ["server"], act: "指定动作检测不合格，结束考试" });
      $message.error("指定动作检测不合格，结束考试");
      return faceTestUploadResult("FAILED");
    } else if (receivedMsg.verifyResult == "VERIFY_SUCCESS") {
      logger({ cnl: ["server"], act: "指定动作检测成功，请继续完成考试" });
      $message.info("指定动作检测成功，请继续完成考试");
      return faceTestUploadResult("SUCCESS");
    } else if (receivedMsg.verifyResult == "UNKNOWN") {
      if (store.QECSConfig.FACE_VERIFY_FORCE_EXIT === "false") {
        logger({
          cnl: ["server"],
          act: "第一次指定动作检测异常(fid)，但是因为配置不强制，于是允许继续考试",
        });
        return;
      }
      showLogout("第一次指定动作检测异常(fid)，系统退出，请重新登录");
    }
  } else if (receivedMsg.verifyCount >= 2) {
    if (receivedMsg.verifyResult == "VERIFY_SUCCESS") {
      logger({ cnl: ["server"], act: "指定动作检测成功，请继续完成考试" });
      $message.info("指定动作检测成功，请继续完成考试");
      return faceTestUploadResult("SUCCESS");
    } else {
      if (store.QECSConfig.FACE_VERIFY_FORCE_EXIT === "false") {
        logger({
          cnl: ["server"],
          act: "指定动作检测不合格，但是因为配置不强制，于是允许继续考试",
        });
        return;
      }
      logger({ cnl: ["server"], act: "指定动作检测不合格，结束考试" });
      $message.error("指定动作检测不合格，结束考试");
      return faceTestUploadResult("FAILED");
    }
  }
}

/** 指定动作检测结果返回后台处理 */
async function faceTestUploadResult(result: string) {
  logger({
    cnl: ["server"],
    pgn: "活体检测弹出框",
    act: "上传活体检测结果",
    ext: { result },
  });
  return httpApp
    .get(
      "/api/ecs_oe_student/examFaceLivenessVerify/faceLivenessVerifyEnd/" +
        store.exam.examRecordDataId +
        "?result=" +
        result,
      { "axios-retry": { retries: 4 } }
    )
    .then(() => {
      if (result != "SUCCESS") {
        logger({
          cnl: ["server"],
          pgn: "活体检测弹出框",
          act: "活体检测失败-退出登录",
        });
        showLogout("活体检测失败");
      } else {
        logger({ cnl: ["server"], pgn: "活体检测弹出框", act: "活体检测成功" });
      }
    })
    .catch(() => {
      logger({
        cnl: ["server"],
        pgn: "活体检测弹出框",
        act: "上传指定动作检测结果出错！--退出登录",
      });
      showLogout("上传指定动作检测结果出错！");
    });
}

let timeCount = $ref(60); //指定动作检测倒计时60秒
function iframeLoadSuccess() {
  logger({
    cnl: ["server"],
    pgn: "活体检测弹出框",
    act: "FaceID页面iframe加载成功",
  });
  // @ts-expect-error
  if (!iframeLoadSuccess.loaded) {
    // @ts-expect-error
    iframeLoadSuccess.loaded = true;
  } else {
    logger({
      cnl: ["server"],
      key: "不可能的事情发生了",
      dtl: "iframeLoadSuccess.loaded",
    });
    return;
  }
  const timeCountInterval = addInterval(() => {
    timeCount--;
    if (timeCount === 0) {
      clearInterval(timeCountInterval);
    }
  }, 1000);

  // 两个结束点。第一个结束点：超时。先传后台，再根据后台信息进行处理。可能ws没有收到处理结果，会通过http接收一遍。
  //定时事件，如果1分钟内未完成指定动作检测，执行内部程序
  addTimeout(faceidLoadedButTimeouted, 60 * 1000); //60000
}

async function startFaceVerifyClicked() {
  logger({
    cnl: ["server"],
    pgn: "活体检测弹出框",
    act: "点击重试按钮",
  });
  await startFaceVerify();
}

let faceVerifyId: string | null = null;
let iframeDomReadyTimeout = -1;
async function startFaceVerify() {
  redoBtnDisabled = true;
  redoBtnMsg = "正在进入指定动作检测...";

  let response = null;
  if (faceVerifyId) {
    try {
      response = await httpApp.get(
        "/api/ecs_oe_student/examFaceLivenessVerify/getFaceVerifyToken/" +
          faceVerifyId,
        { "axios-retry": { retries: 4 } }
      );
    } catch (error) {
      showRedo("网络异常，请手动点击重试");
      logger({
        cnl: ["server"],
        pgn: "活体检测弹出框",
        act: "网络异常-getFaceVerifyToken",
        possibleError: error,
      });
      return;
    }
  } else {
    try {
      response = await httpApp.get(
        "/api/ecs_oe_student/examFaceLivenessVerify/startFaceVerify/" +
          store.exam.examRecordDataId,
        { "axios-retry": { retries: 4 } }
      );
      faceVerifyId = response.data.faceVerifyId;
    } catch (error) {
      logger({
        cnl: ["server"],
        pgn: "活体检测弹出框",
        act: "获取底照token失败，请重新登录！",
        possibleError: error,
      });
      showLogout("获取底照token失败，请重新登录！");
      return;
    }
  }

  if (!response.data.success) {
    logger({
      cnl: ["server"],
      pgn: "活体检测弹出框",
      act: "您上传的底照不适合做活体检测，请联系老师！",
      dtl: JSON.stringify(response.data),
    });
    showLogout("您上传的底照不适合做活体检测，请联系老师！");
    return;
  }

  showIframe = true;
  const iframe = <HTMLIFrameElement>document.getElementById("myFrame");
  try {
    iframe.src = FACEID_LINENESS_URL + response.data.faceLivenessToken;
  } catch (err) {
    logger({
      cnl: ["server"],
      pgn: "活体检测弹出框",
      act: "set iframe.src",
      key: "不可能的事情发生了",
      possibleError: err,
    });
    showLogout("网络错误，请重试！");
  }

  if (!iframe) return;

  {
    // iframe 状态管理
    clearTimeout(iframeDomReadyTimeout);

    // 网络异常，最后的管理
    iframeDomReadyTimeout = window.setTimeout(() => {
      if (iframeDomReady === false) {
        clearTimeout(iframeDomReadyTimeout);
        showRedo("网络异常，请手动点击重试");
        logger({ cnl: ["server"], pgn: "活体检测弹出框", act: "网络异常" });
      }
    }, 30 * 1000);

    let iframeDomReady = false;
    iframe.addEventListener("did-start-loading", () => {
      logger({
        cnl: ["server", "local", "console"],
        pgn: "活体检测弹出框",
        act: "loading faceid iframe",
      });
      console.log(iframe);
    });

    iframe.addEventListener("did-fail-load", () => {
      logger({
        cnl: ["server", "local", "console"],
        pgn: "活体检测弹出框",
        act: "failed loading faceid iframe",
      });
      clearTimeout(iframeDomReadyTimeout);
      if (iframe.src.includes(FACEID_LINENESS_URL)) {
        showRedo("网络异常，请手动点击重试");
        logger({
          cnl: ["server"],
          pgn: "活体检测弹出框",
          act: "网络异常-加载失败",
        });
      }
    });

    iframe.addEventListener("dom-ready", () => {
      iframeDomReady = true;
      logger({ cnl: ["server", "console"], act: "faceid iframe dom ready" });
      // TODO: need verify
      // iframe.insertCSS(".copyright { display: none !important;}");
      // iframe.openDevTools();
    });

    iframe.addEventListener("ipc-message", (event: any) => {
      logger({
        cnl: ["server", "console"],
        act: "got ipc-message",
        dtl: event.channel,
      });
      clearTimeout(iframeDomReadyTimeout);
      const iframeLoadMsg: string = event.channel;
      if (iframeLoadMsg.indexOf("error_message") > -1) {
        showRedo("");
        void updateFaceVerifyMsg(iframeLoadMsg);
      }
      if (iframeLoadMsg === "success") {
        iframeLoadSuccess();
      }
    });
  }
}

// 页面渲染后自动进入流程
void startFaceVerify();
</script>

<template>
  <n-modal
    :show="true"
    :closable="false"
    :maskClosable="false"
    preset="card"
    style="width: 800px"
  >
    <div class="col-md-12 text-center" style="padding: 8px">
      <div style="font-size: 30px">
        <span>指定动作检测</span>
        <span v-if="showIframe">({{ timeCount }})</span>
      </div>
      <div
        v-if="showIframe"
        class="text-center"
        style="color: red; font-size: 16px"
      >
        (注意：请点击下方“开始比对”按钮并在60秒内完成指定动作检测,超时将退出考试)
      </div>
    </div>
    <div
      id="faceIdDiv"
      style="
        position: relative;
        height: 710px;
        background-color: #6e6f72 !important;
        background-image: radial-gradient(circle at 50% 0, #a9a9a9, #34363c);
      "
    >
      <div
        v-show="!showIframe"
        width="100%"
        height="200px"
        style="text-align: center; line-height: 100px; margin-top: 5px"
      >
        <div style="color: white; font-weight: bold; font-size: 20px">
          {{ redoBtnMsg }}
        </div>
        <n-button
          v-if="redoBtnShow"
          size="large"
          type="info"
          :disabled="redoBtnDisabled"
          @click="startFaceVerifyClicked"
        >
          重试
        </n-button>
      </div>
      <webview
        v-show="showIframe"
        id="myFrame"
        :preload="electronDir + 'manipulateFaceID.js'"
        style="position: absolute; width: 100%; height: 710px"
      />
    </div>
  </n-modal>
</template>
