<?php

declare(strict_types=1);

namespace app\common\model\exam;

use app\common\model\account\User as UserModel;
use app\common\model\Base;
use app\common\model\exam\paper\Record;
use app\control\model\User;
use app\common\model\account\User as ModelUser;
use Exception;
use mb\helper\Collection;
use think\facade\Db;
use think\facade\Log;

/**
 * Class Paper
 * @package app\common\model\exam
 */
class Paper
{
    //组卷方式
    public const MODE = ['hand', 'random'];

    //试卷类型
    public const TYPE = ['competition', 'promote', 'beginner', 'simulate'];

    //出题方式
    public const WAY = ['fixed', 'random', 'question'];

    /**
     * @param array $filter
     * @param int $pIndex
     * @param int $pSize
     * @param int $total
     * @return array
     */
    public static function search(array $filter, int $pIndex = 1, int $pSize = 10, &$total = 0)
    {
        $where = [];
        if (!empty($filter['type'])) {
            $where[] = ['type', '=', $filter['type']];
        }
        if (!empty($filter['title'])) {
            $where[] = ['title', 'like', "%{$filter['title']}%"];
        }
        if (!empty($filter['mode'])) {
            $where[] = ['mode', '=', $filter['mode']];
        }
        if (!empty($filter['timeStart'])) {
            $where[] = ['time_start', '<=', strtotime($filter['timeStart'])];
        }
        if (!empty($filter['timeEnd'])) {
            $where[] = ['time_end', '>=', strtotime($filter['timeEnd'])];
        }
        if (!empty($filter['id'])) {
            $where[] = ['id', '=', $filter['id']];
        }
        if (!empty($filter['ids'])) {
            $where[] = ['id', 'in', $filter['ids']];
        }
        if (!empty($filter['question'])) {
            $where[] = ['question', 'like', "%:{$filter['question']};%"];
        }
        try {
            $total = Db::table('exam_papers')->where($where)->count();
            $query = Db::table('exam_papers')->where($where);
            if (!empty($pIndex)) {
                $query->page($pIndex, $pSize);
            }
            $dataSet = $query->order('time_created', 'desc')
                ->field('id,title,type,mode,way,founder,time_start,time_end,total_points,question_type,time,result')
                ->select()->toArray();
            if (!empty($dataSet)) {
                return array_map(
                    function ($val) {
                        $val = Collection::keyStyle($val, Collection::NAME_STYLE_JAVA);
                        $userInfo = UserModel::fetch(intval($val['founder']));
                        $val['founderTitle'] = isset($userInfo['uid']) ? $userInfo['uid'] : '';
                        return $val;
                    },
                    $dataSet
                );
            } else {
                return [];
            }
        } catch (Exception $e) {
            Log::channel('myError')->write($e->getMessage(), \think\Log::ERROR);
        }
        return [];
    }

    /**
     * @param array $data
     * @param int $id
     * @return int|string
     */
    public static function modify(array $data, int $id)
    {
        $newData = Collection::keyStyle($data, Collection::NAME_STYLE_C);
        $questions = [];
        if ($newData['mode'] == 'hand') {
            foreach ($newData['strategies'] as &$str) {
                foreach ($str['strategy'] as $k => &$val) {
                    if (!empty($val)) {
                        if (!isset($questions[$str['type']])) {
                            $questions[$str['type']] = [];
                        }
                        if ($newData['mode'] == 'hand') {
                            array_push($questions[$str['type']], $val);
                            continue;
                        }
                    }
                }
            }
        } else {
            $questions = self::make($newData['strategies']);
            if (is_error($questions)) {
                return error(-10, '题库策略发生变更，请修改试题策略');
            }
        }
        $newData['strategies'] = serialize($newData['strategies']);
        $newData['question_type'] = serialize($newData['question_type']);
        $newData['question'] = serialize($questions);
        $newData['time_created'] = time();
        $watch = $newData['watch'];
        $mark = $newData['mark'];
        $newData['watch'] = serialize($newData['watch']);
        $newData['mark'] = serialize($newData['mark']);
        try {
            if (empty($id)) {
                $id = Db::table('exam_papers')->insertGetId($newData);
            } else {
                unset($newData['time_created']);
                $where = self::parseFilters($id);
                $offect = Db::table('exam_papers')->where($where)->update($newData);
                if (!$offect === 1) {
                    return 0;
                }
            }
            Base::watch('paper_watch', array_merge($watch, ['paramId' => $id, 'type' => '', 'binding_id' => 0]));
            Base::watch('paper_mark', array_merge($mark, ['paramId' => $id, 'type' => '', 'binding_id' => 0]));
            return $id;
        } catch (Exception $e) {
            Log::channel('myError')->write($e->getMessage(), \think\Log::ERROR);
        }
        return 0;
    }

    /**
     * @param string $type
     * @param int $knowledge
     * @param int $subject
     * @param string $level
     * @return array
     */
    public static function matchQuestion(string $type, int $knowledge, int $subject, string $level)
    {
        $question = Question::search(
            ['type' => $type, 'knowledge' => $knowledge, 'subjects' => $subject, 'difficultyLevel' => $level],
            0
        );
        return Base::neaten($question, 'id');
    }

    /**
     * 生成策略
     * filter.type
     * filter.subject
     * filter.knowledge
     * mode
     * @param array $filter
     * @param string $mode
     * @return array
     */
    public static function strategy(array $filter, string $mode)
    {
        $question = Question::search($filter, 0);
        if (empty($question)) {
            return  [];
        }
        if ($mode == 'hand') {
            $question = array_map(function ($val) {
                unset($val['analysis'], $val['options'], $val['answer'], $val['score']);
                return $val;
            }, $question);
            return $question;
        }
        $type = [];
        foreach ($question as $v) {
            $type[$v['type']][] = $v;
        }
        $difficulty = Question::DIFFICULTY_LEVEL;
        $difficulty = array_keys($difficulty);
        $count = [];
        $res = [];
        foreach ($type as $t) {
            foreach ($difficulty as $d) {
                $count[$d] = 0;
            }
            foreach ($t as $item) {
                ++$count[$item['difficultyLevel']];
            }
            $res[] = [
                'type' => $t[0]['type'],
                'knowledge' => $t[0]['knowledge'],
                'subject' => $t[0]['subject'],
                'strategy' => $count
            ];
        }
        return $res;
    }

    /**
     * @param $filters
     * @return array
     * @throws \think\Exception
     */
    public static function parseFilters($filters)
    {
        $newFilters = [];
        if (is_array($filters)) {
            if (!empty($filters['id'])) {
                $newFilters[] = ['id', '=', $filters['id']];
            }
            if (empty($filters)) {
                throw error(-19, '缺少必填参数ID');
            }
            if (!empty($filters['founder'])) {
                $newFilters[] = ['founder', '=', $filters['founder']];
            }
        } else {
            $newFilters[] = ['id', '=' ,intval($filters)];
        }
        return $newFilters;
    }

    /**
     * @param $id
     * @return array|\think\Model|null
     * @throws \think\Exception
     */
    public static function detail($id)
    {
        $filter = self::parseFilters($id);
        try {
            $detail = Db::table('exam_papers')->where($filter)->find();
            if (empty($detail)) {
                return [];
            }
            $detail['strategies'] = @unserialize($detail['strategies']);
            $detail['question_type'] = @unserialize($detail['question_type']);
            $detail['watch'] = @unserialize($detail['watch']);
            $detail['mark'] = @unserialize($detail['mark']);
            $detail = Collection::keyStyle($detail, Collection::NAME_STYLE_JAVA);
            return $detail;
        } catch (Exception $e) {
            Log::channel('myError')->write($e->getMessage(), \think\Log::ERROR);
        }
        return [];
    }

    /**
     * @param $id
     * @return bool
     */
    public static function check($id)
    {
        try {
            $detail = self::detail($id);
            $user = User::fetchCurrent();
            if (($user['role'] != 'root') && ($detail['founder'] != $user['id'])) {
                return false;
            } else {
                return true;
            }
        } catch (Exception $e) {
            Log::channel('myError')->write($e->getMessage(), \think\Log::ERROR);
        }
        return false;
    }

    /**
     * @param string $type
     * @param string $ids
     * @return bool
     */
    public static function delete(string $type, string $ids)
    {
        if ($type == 'single') {
            $where[] = ['id', '=', $ids];
            $watchWhere[] = ['paper_id', '=', $ids];
        } else {
            $where[] = ['id', 'in', "{$ids}"];
            $watchWhere[] = ['paper_id', 'in', "{$ids}"];
        }
        try {
            $offect = Db::table('exam_papers')
                ->where($where)
                ->delete();
            if ($offect) {
                Db::table('exam_paper_mark')->where($watchWhere)->delete();
                Db::table('exam_paper_watch')->where($watchWhere)->delete();
                return true;
            }
            return false;
        } catch (Exception $e) {
            Log::channel('myError')->write($e->getMessage(), \think\Log::ERROR);
        }
        return false;
    }

    /**
     * 试卷是否被使用
     * @param array $paperIds
     * @return bool
     */
    public static function paperUsed(array $paperIds)
    {
        try {
            foreach ($paperIds as $val) {
                $record = Record::recordNum(['paper' => intval($val)]);
                if (!empty($record)) {
                    return false;
                    break;
                }
            }
            return true;
        } catch (Exception $e) {
            Log::channel('myError')->write($e->getMessage(), \think\Log::ERROR);
        }
        return false;
    }

    /**
     * 所有应参考人员
     * @param $id
     * @return array|string
     */
    public static function watchUsers($id)
    {
        try {
            $res = Db::table('exam_paper_watch')->where(['paper_id' => $id])->select()->toArray();
            if ((count($res) == 1) && empty($res[0]['type'])) {
                $users = Db::table('account_users')->field('id')->select()->toArray();
                return Base::neaten($users, 'id');
            }
            $department = [];
            $user = [];
            foreach ($res as $val) {
                if ($val['type'] == 'department') {
                    array_push($department, $val['binding_id']);
                }
                if ($val['type'] == 'user') {
                    array_push($user, $val['binding_id']);
                }
            }
            $departmentUser = [];
            if (!empty($department)) {
                foreach ($department as $v) {
                    $res = ModelUser::search(['department' => $v], 0);
                    if (!empty($res)) {
                        $ids = Base::neaten($res, 'id');
                        $departmentUser = array_merge($departmentUser, $ids);
                    }
                }
            }
            $allUsers = array_unique(array_merge($departmentUser, $user));
            return $allUsers;
        } catch (Exception $e) {
            Log::channel('myError')->write($e->getMessage(), \think\Log::ERROR);
        }
        return [];
    }

    /**
     * @param $filter
     * @param null $question
     * @return array|\Error|\think\Exception
     * @throws \think\Exception
     */
    public static function preview($filter, $question = null)
    {
        $filter = self::parseFilters($filter);
        try {
            $paper = Db::table('exam_papers')->where($filter)->withoutField('watch, mark, strategies')->find();
            $paper['question_type'] = unserialize(Base::mb_unserialize($paper['question_type']));
            if (empty($question)) {
                $question = @unserialize($paper['question']);
            }
            $check = self::checkQuestionExist($question);
            if (is_error($check)) {
                return error(-11, '题库发生变更，请联系管理员修改试卷内容');
            }
            $paper['question'] = self::sort($paper['question_type'], $question, $paper['way']);
            $paper['questionNum'] = 0;
            foreach ($paper['question_type'] as $q) {
                $paper['questionNum'] += $q['questionNum'];
            }
            $paper = Collection::keyStyle($paper, Collection::NAME_STYLE_JAVA);
            return $paper;
        } catch (Exception $e) {
            Log::channel('myError')->write($e->getMessage(), \think\Log::ERROR);
        }
        return [];
    }

    /**
     * 试卷题目排序
     * @param $questionType
     * @param $question
     * @param $type
     * @return array
     */
    public static function sort($questionType, $question, $type)
    {
        if (count($questionType) > 1) {
            $order = array_column($questionType, 'sort');
            array_multisort($order, SORT_ASC, $questionType);
        }
        $questionType = Base::neaten($questionType, 'type');
        $newData = [];
        foreach ($questionType as $v) {
            if ($type != 'fixed') {
                shuffle($question[$v]);
            }
            $newData[$v] = $question[$v];
        }
        return $newData;
    }

    /**
     * @param $question
     * @return array|bool|\Error|\think\Exception
     */
    public static function checkQuestionExist($question)
    {
        foreach ($question as $item) {
            $count = 0;
            Question::search(['ids' => $item], 0, 0, $count);
            if ($count != count($item)) {
                return error(-1, '题库发生变更，请联系管理员修改试卷内容');
            }
        }
        return true;
    }

    /**
     * 所有用户可以看的试卷
     * @param null $user
     * @return array
     */
    public static function userWatch($user = null)
    {
        try {
            if (empty($user)) {
                $user = User::fetchCurrent();
            }
            if ($user['role'] != 'root') {
                $paperIds = Db::table('exam_paper_watch')->where(
                    "`type` = '' or (`type` = 'department' and `binding_id` = {$user['department']}) 
            or (`type` = 'user' and `binding_id` = {$user['id']})"
                )->group('paper_id')->field('paper_id')->select()->toArray();
            } else {
                $paperIds = Db::table('exam_papers')->group('id')->field('id as paper_id')->select()->toArray();
            }
            return Base::neaten($paperIds, 'paper_id');
        } catch (Exception $e) {
            Log::channel('myError')->write($e->getMessage(), \think\Log::ERROR);
        }
        return [];
    }

    /**
     * @param $filter
     * @return array
     * @throws \think\Exception
     */
    public static function fetch($filter)
    {
        $filter = self::parseFilters($filter);
        try {
            return Db::table('exam_papers')->where($filter)->find();
        } catch (Exception $e) {
            Log::channel('myError')->write($e->getMessage(), \think\Log::ERROR);
        }
        return [];
    }

    /**
     * 组题
     * @param $strategies
     * @return array
     */
    public static function make($strategies)
    {
        $questions = [];
        foreach ($strategies as &$str) {
            foreach ($str['strategy'] as $k => &$val) {
                if (!empty($val)) {
                    if (!isset($questions[$str['type']])) {
                        $questions[$str['type']] = [];
                    }
                    $tmpQue = self::matchQuestion(
                        $str['type'],
                        $str['knowledge'],
                        $str['subject'],
                        $k
                    );
                    if (empty($tmpQue) || ($val > count($tmpQue))) {
                        return error(-1, '题库策略发生更改，请修改试卷策略');
                    }
                    $tmpKey = array_rand($tmpQue, $val);
                    $newArr = [];
                    if (is_array($tmpKey)) {
                        foreach ($tmpKey as $v) {
                            $newArr[] = $tmpQue[$v];
                        }
                    } else {
                        $newArr = [$tmpQue[rand(0, count($tmpQue) - 1)]];
                    }
                    $questions[$str['type']] = array_merge($questions[$str['type']], $newArr);
                }
            }
        }
        return $questions;
    }

    /**
     * 转换答题时间
     * @param $times
     * @return string
     */
    public static function secToTime($times)
    {
        $result = '00:00:00';
        if ($times > 0) {
            $hour =  strval(floor($times / 3600));
            $minute = strval(floor(($times - 3600 * $hour) / 60));
            $second = strval(floor((($times - 3600 * $hour) - 60 * $minute) % 60));
            $hour = strlen($hour) == 1 ? '0' . $hour : $hour;
            $minute = strlen($minute) == 1 ? '0' . $minute : $minute;
            $second = strlen($second) == 1 ? '0' . $second : $second;
            $result = $hour . ':' . $minute . ':' . $second;
        }
        return $result;
    }
}