<?php
namespace app\mobile\controller;

use think\facade\Db;
use app\admin\model\MemberNewcomerExam;

/** 入职考试由服务端发卷和计分；提交回执与成绩同时持久化。 */
class OmsExam extends OmsBase
{
    public function start()
    {
        return $this->guarded(function () {
            $this->requireMethod('POST');
            if ($this->request->post('protocol') !== 2) $this->fail('考试已升级，请更新小程序后重新进入考试',409);
            $name = $this->requiredText('ename', 90);
            if (mb_strlen($name, 'UTF-8') > 30) $this->fail('姓名长度不正确');
            $phone = $this->requiredText('phone', 11);
            if (!preg_match('/^[0-9]{11}$/', $phone)) $this->fail('请输入 11 位手机号码');
            $paperType = (int)$this->request->post('item_type', 0);
            if (!in_array($paperType, [1, 2], true)) $this->fail('试卷类型不正确');
            $last = Db::name('member_newcomer_score')->where('phone', $phone)->order('exam_date desc,id desc')->find();
            if ($last) $paperType = (int)$last['item_type'];
            $rows = Db::name('member_newcomer_exam')->where('item_type', $paperType)->where('status', 1)->select()->toArray();
            if (!$rows) $this->fail('暂无可用试题，请联系监考老师');
            $groups = [2=>[], 1=>[], 3=>[]];
            foreach ($rows as $row) {
                $type = (int)$row['exam_type'];
                if (!isset($groups[$type])) $this->fail('题库题型异常，请联系监考老师');
                $options = [];
                foreach (['item_a', 'item_b', 'item_c', 'item_d'] as $field) {
                    if (isset($row[$field]) && $row[$field] !== '') $options[] = $row[$field];
                }
                $answers = json_decode($row['answer'], true);
                if (!is_array($answers) || !$answers || count($options) < 2) $this->fail('题库答案异常，请联系监考老师');
                $answers = array_map('strval', $answers);
                foreach ($answers as $answer) {
                    if (!in_array($answer, array_map(function ($option) { return substr($option, 0, 1); }, $options), true)) $this->fail('题库答案与选项不匹配，请联系监考老师');
                }
                if ($type !== 2 && count($answers) !== 1) $this->fail('题库单选答案异常，请联系监考老师');
                if ($type !== 3) shuffle($options);
                $groups[$type][] = ['id'=>(int)$row['id'], 'exam_type'=>$type, 'answer_type'=>(int)$row['answer_type'], 'content'=>$row['content'], 'img'=>$row['img'], 'exam'=>$options, 'answer'=>$type === 2 ? $answers : $answers[0]];
            }
            foreach ($groups as &$group) shuffle($group);
            unset($group);
            $questions = array_merge($groups[2], $groups[1], $groups[3]);
            $paperId=bin2hex(random_bytes(16));
            // 不设答题时限；同一手机号重新发卷会替换未提交的旧试卷。
            $paper=['id'=>$paperId,'ename'=>$name,'phone'=>$phone,'item'=>$paperType,'questions'=>$questions];
            if(!$this->cache()->set($this->cacheKey('newcomer-paper:'.hash('sha256',$phone)),$paper,0))$this->fail('试卷保存失败，请稍后重试',503);
            foreach($questions as &$question)unset($question['answer']);unset($question);
            return json(['code'=>0,'message'=>'成功','data'=>['paperId'=>$paperId,'item'=>$paperType,'passScore'=>MemberNewcomerExam::PASS_SCORE,'data'=>$questions]]);
        });
    }

    private function result($row) {
        $receipt=json_decode($row['submission_result'],true);
        return json(['code'=>0,'message'=>'成绩已保存','data'=>$receipt['result']+['scoreId'=>(int)$row['id']]]);
    }
    public function submit()
    {
        return $this->guarded(function () {
            $this->requireMethod('POST');
            $name=$this->requiredText('ename',90);$phone=$this->requiredText('phone',11);
            if(mb_strlen($name,'UTF-8')>30 || !preg_match('/^[0-9]{11}$/D',$phone))$this->fail('考试人员信息不正确');
            $paperId=$this->request->post('paperId');
            if(!is_string($paperId)||!preg_match('/^[a-f0-9]{32}$/D',$paperId))$this->fail('考试已升级，请更新小程序后重新进入考试',409);
            $answers=$this->request->post('answer');
            if(!is_array($answers)||!$answers||count($answers)>500)$this->fail('请完成全部题目');
            $provided=[];
            foreach($answers as $answer){
                if(!is_array($answer)||!isset($answer['id'],$answer['answered'])||!is_int($answer['id'])||$answer['id']<1||isset($provided[$answer['id']])||!is_string($answer['answered'])||!preg_match('/^[A-Za-z0-9](,[A-Za-z0-9]){0,3}$/D',$answer['answered']))$this->fail('答案格式不正确');
                $selected=explode(',',$answer['answered']);
                if(count(array_unique($selected))!==count($selected))$this->fail('选项不能重复');
                sort($selected,SORT_STRING);$provided[$answer['id']]=$selected;
            }
            ksort($provided);$fingerprint=hash('sha256',json_encode($provided));
            $lock='oms-newcomer:'.sha1($this->cacheKey($phone));
            if((int)Db::query('SELECT GET_LOCK(?,5) acquired',[$lock])[0]['acquired']!==1)$this->fail('成绩正在处理中，请稍后重试',409);
            try {
                $old=Db::name('member_newcomer_score')->where('submission_key',$paperId)->find();
                if($old){
                    $receipt=json_decode($old['submission_result'],true);
                    if($old['phone']!==$phone||$old['ename']!==$name||($receipt['fingerprint']??'')!==$fingerprint)$this->fail('试卷已提交，不能修改人员或答案',409);
                    return $this->result($old);
                }
                $key=$this->cacheKey('newcomer-paper:'.hash('sha256',$phone));$paper=$this->cache()->get($key);
                if(!$paper||$paper['id']!==$paperId||$paper['ename']!==$name||$paper['phone']!==$phone||(int)$this->request->post('item_type')!==$paper['item'])$this->fail('试卷已变化，请重新加载试卷',409);
                if(count($provided)!==count($paper['questions']))$this->fail('请完成本次试卷的全部题目');
                $points=0;$stored=[];$review=[];
                foreach($paper['questions'] as $question){
                    $selected=$provided[$question['id']]??[];$options=array_map(function($v){return substr($v,0,1);},$question['exam']);
                    if(!$selected||array_diff($selected,$options)||($question['exam_type']!==2&&count($selected)!==1))$this->fail('题目或选项不属于本次试卷');
                    $expected=array_map('strval',is_array($question['answer'])?$question['answer']:[$question['answer']]);sort($expected,SORT_STRING);
                    $correct=$selected===$expected;if($correct)$points+=$question['exam_type']===1?25:20;
                    $stored[$question['id']]='['.implode(',',$selected).']';
                    $review[]=['id'=>$question['id'],'answer'=>$question['answer'],'wrong'=>$correct?0:1];
                }
                $result=['score'=>$points/10,'passed'=>$points>=MemberNewcomerExam::PASS_SCORE*10,'review'=>$review];
                $saved=MemberNewcomerExam::saveScore(['ename'=>$name,'phone'=>$phone,'item_type'=>$paper['item'],'score'=>$points/10,'answers'=>json_encode($stored),
                    'submission_key'=>$paperId,'submission_result'=>json_encode(['fingerprint'=>$fingerprint,'result'=>$result],JSON_UNESCAPED_UNICODE)]);
                if($saved['code']>1)$this->fail($saved['msg']);
                // 回执已与成绩原子写入；Redis故障或响应丢失后仍可安全重试。
                return $this->result(['id'=>$saved['scoreId'],'submission_result'=>json_encode(['result'=>$result])]);
            } finally {Db::query('SELECT RELEASE_LOCK(?)',[$lock]);}
        });
    }
}
