<script setup lang="ts">
import OnlineExamFaceCheckModal from "./OnlineExamFaceCheckModal.vue";
import CommittmentDialog from "./CommittmentDialog.vue";
import { OnlineExam } from "@/types/student-client";
import CheckComputer from "./CheckComputer.vue";
import { onMounted, onUnmounted, watch } from "vue";
import { httpApp } from "@/plugins/axiosApp";
import router from "@/router";
import { checkExamInProgress } from "../UserLogin/useExamInProgress";
import { store } from "@/store/store";
import { closeMediaStream } from "@/utils/camera";
import { getKey } from "@/utils/utils";
import { AxiosError } from "axios";
import { doDec, doEnc } from "@/utils/encDec";

const { course } = defineProps<{ course: OnlineExam }>();

const emit = defineEmits<{ (e: "on-unselect-course"): void }>();

/** 采用状态机，根据每一步的状态来执行初始化，以及跳转
 * 驱动步骤向前分两类：
 * 1. 自动跳转在watch里面
 * 2. 用户事件跳转，通过组件注册事件
 */
const STEPS = [
  "COMMITTMENT",
  "CHECK_ENV_1",
  "CHECK_ENV_2",
  "CHECK_FACE",
  "CHECK_FACE_COMMITTMENT",
] as const;

let curentStep = $ref(-1);

// 为了页面在跳过此步骤是不出现此步骤的页面，所以加上这个变量来控制modal.show
let showCheckEnv1 = $ref(false);

watch(
  () => curentStep,
  async (step, oldStep) => {
    logger({
      cnl: ["server", "console"],
      act: "StartExamModal",
      ext: {
        currentStep: STEPS[step],
        oldStep: STEPS[oldStep],
      },
    });

    if (STEPS[step] === "COMMITTMENT") {
      if (!course.showUndertaking) curentStep++;
    }
    if (STEPS[step] === "CHECK_ENV_1") {
      if (!(await isGetCheckEnvOk())) {
        emit("on-unselect-course");
        return;
      }
      if (!serverWantCheckEnv) curentStep += 2;
      showCheckEnv1 = true;
    }
    if (STEPS[step] === "CHECK_FACE") {
      if (course.faceEnable) {
        if (!store.user.photoPath) {
          logger({ cnl: ["server"], act: "无底照" });
          $message.info(
            "本场考试需要进行人脸检测，但是您没有上传底照，请联系老师！"
          );
          emit("on-unselect-course");
          return true;
        }

        if (!(await isGetFaceLivenessOk())) {
          emit("on-unselect-course");
          return true;
        }
      } else {
        curentStep++;
        await enterExam();
      }
    }
  }
);

function applyCommittmentResult(isCommitted: boolean) {
  if (!isCommitted) {
    emit("on-unselect-course");
  } else {
    curentStep++;
  }
}

let serverWantCheckEnv = $ref(false);
function skipCheckEnv() {
  curentStep += 2;
}
function doCheckEnv() {
  curentStep++;
}

async function isIPOk(): Promise<boolean> {
  logger({ cnl: ["server"], act: "正在检测IP合法性..." });
  try {
    const ipLimit = (
      await httpApp.get("/api/ecs_exam_work/exam/ipLimit/" + course.examId)
    ).data;
    if (ipLimit.limited) {
      logger({ cnl: ["server"], act: "IP受限，请到中心指定地点进行考试！" });
      $message.error("IP受限，请到中心指定地点进行考试！");
      return false;
    }
  } catch (error) {
    $message.error("查询IP限制出错！");
    logger({ cnl: ["server"], act: "查询IP限制出错！" });
    return false;
  }

  return true;
}

async function isGetCheckEnvOk(): Promise<boolean> {
  try {
    let checkEnv = await httpApp.get(
      "/api/ecs_exam_work/exam/getExamPropertyFromCacheByStudentSession/" +
        course.examId +
        `/CHECK_ENVIRONMENT`
    );

    if (checkEnv.data.CHECK_ENVIRONMENT === "true") {
      serverWantCheckEnv = true;
    }
  } catch (error) {
    logger({
      cnl: ["server"],
      act: "获取CHECK_ENVIRONMENT失败",
    });
    $message.warning("网络错误，请重试！");
    // 获取不到流程必要的参数，就中断流程
    return false;
  }

  return true;
}

async function isGetFaceLivenessOk(): Promise<boolean> {
  let faceLiveness = null;
  try {
    faceLiveness = await httpApp.get(
      "/api/ecs_exam_work/exam/identificationOfLivingEnabled/" + course.examId
    );
  } catch (error) {
    $message.error("查询考试的人脸检测设置属性出错！");
    logger({ cnl: ["server"], act: "查询考试的人脸检测设置属性出错！" });
    return false;
  }
  if (faceLiveness.data) {
    logger({ cnl: ["server"], act: "正在检测底照是否满足活体检测标准..." });
    let checkBasePhoto;
    try {
      checkBasePhoto = (
        await httpApp.get(
          "/api/ecs_oe_student/examFaceLivenessVerify/checkFaceLiveness" +
            `?${store.user.token}` // 考生采用相同的机器考试，使用不同的请求的缓存
        )
      ).data;
      if (!checkBasePhoto.success) {
        $message.error("您上传的底照不符合活体检测的要求，请联系老师！");
        logger({
          cnl: ["server"],
          act: "您上传的底照不符合活体检测的要求，请联系老师！",
        });
        return false;
      }
    } catch (error) {
      $message.error("网络错误，请稍后重试！");
      logger({
        cnl: ["server"],
        act: "查询检测底照是否满足活体检测标准的接口出错！",
        possibleError: error,
      });
      return false;
    }
  }

  return true;
}

onMounted(async () => {
  if (!(await isIPOk())) {
    emit("on-unselect-course");
    return true;
  }

  if (await checkExamInProgress().catch(() => true)) {
    // 检测到有断点，或者断点有异常，退出当前流程
    emit("on-unselect-course");
    return;
  }

  curentStep = 0;
});

function onFacePassed(isRealStudent: boolean) {
  if (isRealStudent) {
    void enterExam();
  } else {
    if (!course.faceCheck) curentStep++;
  }
}

function onFaceClosedByUser() {
  emit("on-unselect-course");
}

function disagreeCommittment() {
  emit("on-unselect-course");
}

let passedAllChecks = false;
async function enterExam() {
  let hstate = { examStudentId: course.examStudentId };
  /** 为了代码加密混淆效果更好，隐藏在线考试开始考试的启动逻辑，将在线考试启动接口调用放在该组件内部 */
  if (course.examType === "ONLINE") {
    const timestamp = Date.now();
    const rawStr = JSON.stringify({
      examStudentId: course.examStudentId,
      timestamp,
    });

    const key = getKey(timestamp);

    let encryptParams = "";
    encryptParams = doEnc(rawStr, key);

    try {
      let res = await httpApp.post<string>(
        "/api/ecs_oe_student/examControl/online/startExam",
        encryptParams,
        {
          "axios-retry": { retries: 4 },
          noErrorMessage: true,
          headers: {
            timestamp,
            "Content-Type": "text/plain",
            accept: "text/plain",
          },
          responseType: "text",
          transitional: { forcedJSONParsing: false },
        }
      );

      res.data = doDec(res.data, key);
      const newRes: {
        courseName: string;
        examRecordDataId: number;
      } = JSON.parse(res.data);

      Object.assign(hstate, {
        examRecordDataId: newRes.examRecordDataId,
        courseName: encodeURIComponent(newRes.courseName),
      });
    } catch (error) {
      // console.log(error, error.response, error.isAxiosError);
      if ("isAxiosError" in <any>error) {
        const ne = <AxiosError>error;
        const jsonRes = JSON.parse(<string>ne.response?.data ?? "null");
        const desc: string = jsonRes.desc;
        if (desc) {
          $message.error(desc);
          logger({ cnl: ["server"], act: "开考失败", dtl: desc });
        } else {
          logger({
            cnl: ["server"],
            act: "开考失败",
            dtl: "开考失败，并且无错误消息",
            possibleError: error,
          });
        }
      } else {
        logger({
          cnl: ["server"],
          act: "开考失败",
          dtl: "非接口失败",
          possibleError: error,
        });
      }
      return;
    }
  }
  passedAllChecks = true;
  await router.push({
    name: "OnlineExamOverview",
    params: { examId: course.examId },
    query: hstate,
  });
}

onUnmounted(() => {
  /** TODO: 摄像头关闭时机,需要完善 */
  if (!passedAllChecks && course.faceEnable) {
    // debug级别备选。因为初期上线，摄像头比较容易出错，所以保留此日志
    logger({
      cnl: ["server", "console"],
      act: "关闭sharedtream",
      dtl: "!passedAllChecks",
    });
    closeMediaStream();
  } else {
    logger({
      cnl: ["server", "console"],
      act: "保留sharedtream",
      dtl: "passedAllChecks",
    });
  }
});
</script>

<template>
  <CommittmentDialog
    v-if="STEPS[curentStep] === 'COMMITTMENT'"
    ref="committmentDialogRef"
    :content="course.undertaking"
    @onCommitResult="applyCommittmentResult"
  />

  <n-modal
    v-if="STEPS[curentStep] === 'CHECK_ENV_1'"
    :show="showCheckEnv1"
    :closable="false"
    preset="card"
    title="进行环境检测"
    style="width: 600px"
  >
    <div>
      环境检测可以检测电脑的硬件配置、网络速度和常用操作。环境检测不通过的话，可能影响考试的正常进行。
    </div>
    <div class="tw-flex tw-justify-center tw-mt-2">
      <n-button
        type="success"
        style="margin-right: 5px; min-width: 120px"
        @click="skipCheckEnv"
      >
        跳过检测
      </n-button>
      <n-button
        type="success"
        style="margin-right: 5px; min-width: 120px"
        @click="doCheckEnv"
      >
        进行检测
      </n-button>
    </div>
  </n-modal>

  <CheckComputer
    v-if="STEPS[curentStep] === 'CHECK_ENV_2'"
    @onClose="curentStep++"
  />

  <OnlineExamFaceCheckModal
    v-if="STEPS[curentStep] === 'CHECK_FACE'"
    @passed="onFacePassed"
    @close="onFaceClosedByUser"
  />

  <n-modal
    v-if="STEPS[curentStep] === 'CHECK_FACE_COMMITTMENT'"
    :show="true"
    :closable="false"
    preset="card"
    title="郑重承诺"
    style="width: 600px"
  >
    <div>
      我承诺由本人参加考试，并且同意接受考试监控系统信息审核，一经发现作弊，立即取消本门课程考试成绩。
    </div>
    <div class="tw-flex tw-justify-center tw-mt-2">
      <n-button
        type="success"
        style="margin-right: 5px; min-width: 120px"
        @click="disagreeCommittment"
      >
        取消
      </n-button>
      <n-button
        type="success"
        style="margin-right: 5px; min-width: 120px"
        @click="enterExam"
      >
        确定
      </n-button>
    </div>
  </n-modal>
</template>
