<?php

declare(strict_types=1);

namespace app\common\model\exam;

use app\common\model\Base;
use app\common\model\core\Excel;
use app\control\model\User;
use app\test\QuestionTest;
use Error;
use Exception;
use mb\helper\Collection;
use think\db\exception\DataNotFoundException;
use think\db\exception\DbException;
use think\db\exception\ModelNotFoundException;
use think\facade\Db;
use think\facade\Log;
use think\Model;

/**
 * Class Question
 * @package app\common\model\exam\test
 */
class Question
{
    /**
     * 题型
     */
    public const TYPE = [
        'judge' => '判断题',

        'choice' => '单选题',
        'choices' => '多选题',

        'composition' => '作文题', //作文
        'fillIn' => '填空题', //填空
        'answer' => '问答题', //问答

        'typing' => '打字题', //打字

        'handel' => '操作题', //操作
    ];

    public const MARKTYPE = [
        'composition',
        'fillIn',
        'answer',
        'handel',
    ];

    /**
     * 选择序号
     */
    public const CHOICES = [
        '1' => 'A',
        '2' => 'B',
        '3' => 'C',
        '4' => 'D',
        '5' => 'E',
        '6' => 'F',
        '7' => 'G',
        '8' => 'H',
        '9' => 'I',
        '10' => 'J',
        '11' => 'K',
    ];

    /**
     * 难度
     */
    public const DIFFICULTY_LEVEL = [
        'easy' => '易',
        'moreEasily' => '较易',
        'medium' => '中等',
        'moreDifficult' => '较难',
        'difficult' => '难',
    ];

    public const EXPORT_FIELDS = [
        '' => '',
        'type' => '题型',
        'name' => '基本内容',
        'answer' => '正确答案',
        'percent' => '得分率',
        'A' => '选A',
        'B' => '选B',
        'C' => '选C',
        'D' => '选D',
        'E' => '选E',
        'F' => '选F',
    ];

    public const EXPORT_TYPE_FIELDS = [
        '' => '',
        'type' => '题型',
        'questionNum' => '试题数量',
        'score' => '试题分数',
        'avg' => '平均分',
        'percent' => '得分率',
    ];

    /**
     * @param $questionInfo
     * @return bool|int|string
     */
    public static function add($questionInfo)
    {
        $newRow = Collection::keyStyle($questionInfo, Collection::NAME_STYLE_C);
        $newRow = Collection::elements(
            [
                'name',
                'options',
                'answer',
                'type',
                'score',
                'founder',
                'difficulty_level',
                'knowledge',
                'subject',
                'analysis',
            ],
            $newRow
        );
        $newRow['created_time'] = time();
        $newRow['score'] = 0;
        try {
            return Db::table('exam_questions')
                ->insertGetId($newRow);
        } catch (Exception $e) {
            Log::channel('myError')->write($e->getMessage(), \think\Log::ERROR);
        }
        return false;
    }

    /**
     * @param $filters
     * @return array|Model|null
     */
    public static function fetch($filters)
    {
        $where = self::parseFilters($filters);
        try {
            $questionInfo = Db::table('exam_questions')
                ->where($where)
                ->find();
            if (!empty($questionInfo)) {
                $questionInfo['options'] = @unserialize($questionInfo['options']);
                $questionInfo['answer'] = @unserialize($questionInfo['answer']);
                return $questionInfo;
            }
        } catch (Exception $e) {
            Log::channel('myError')->write($e->getMessage(), \think\Log::ERROR);
        }
        return [];
    }

    /**
     * @param $filters
     * @param $newState
     * @return bool
     */
    public static function update($filters, array $newState)
    {
        $where = self::parseFilters($filters);
        $newState = Collection::keyStyle($newState, Collection::NAME_STYLE_C);
        try {
            $offect = Db::table('exam_questions')
                ->where($where)
                ->update($newState);
            if ($offect === 1) {
                return true;
            }
            return false;
        } catch (Exception $e) {
            Log::channel('myError')->write($e->getMessage(), \think\Log::ERROR);
        }
        return false;
    }

    /**
     * @param $filters
     * @return bool
     */
    public static function remove($filters)
    {
        $where = self::parseFilters($filters);
        try {
            $offect = Db::table('exam_questions')
                ->where($where)
                ->delete();
            if (!empty($offect)) {
                return true;
            }
            return false;
        } catch (Exception $e) {
            Log::channel('myError')->write($e->getMessage(), \think\Log::ERROR);
        }
        return false;
    }

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

    /**
     * filters.type
     * filters.knowledge
     * filters.subjects
     * filters.difficultyLevel
     * filters.name
     * filters.founder
     * array filters.order 排序列子 ['id' => 'desc']
     * @param $filters
     * @param int $pIndex
     * @param int $pSize
     * @param int $total
     * @return array
     */
    public static function search($filters, $pIndex = 1, $pSize = 10, &$total = 0)
    {
        $where = [];
        if (!empty($filters['type'])) {
            $where[] = ['type', '=', $filters['type']];
        }
        if (!empty($filters['package'])) {
            $where[] = ['package', '=', $filters['package']];
        }
        if (!empty($filters['knowledge'])) {
            $where[] = ['knowledge', '=', $filters['knowledge']];
        }
        if (!empty($filters['subjects'])) {
            $where[] = ['subject', '=', $filters['subjects']];
        }
        if (!empty($filters['subject'])) {
            $where[] = ['subject', '=', $filters['subject']];
        }
        if (!empty($filters['difficultyLevel'])) {
            $where[] = ['difficulty_level', '=', $filters['difficultyLevel']];
        }
        if (!empty($filters['name'])) {
            $where[] = ['name', 'like', "%{$filters['name']}%"];
        }
        if (!empty($filters['founder'])) {
            $where[] = ['founder', '=', $filters['founder']];
        }
        if (!empty($filters['ids'])) {
            $where[] = ['id', 'in', $filters['ids']];
        }
        try {
            $total = Db::table('exam_questions')
                ->where($where)
                ->count();

            $query = Db::table('exam_questions')
                ->where($where);
            if (!empty($pIndex)) {
                $query->page($pIndex, $pSize);
            }
            if (!empty($filters['order'])) {
                $query->order($filters['order']);
            }
            $dataSet = $query->select()->toArray();
            if (!empty($dataSet)) {
                return array_map(
                    function ($row) {
                        $row['answer'] = unserialize($row['answer']);
                        $row['options'] = unserialize($row['options']);
                        $row['analysis'] = unserialize($row['analysis']);
                        return Collection::keyStyle($row, Collection::NAME_STYLE_JAVA);
                    },
                    $dataSet
                );
            }
        } catch (Exception $e) {
            Log::channel('myError')->write($e->getMessage(), \think\Log::ERROR);
        }
        return [];
    }

    /**
     * 导入excel
     * @param $excelUrl
     * @return array|Error
     */
    public static function questionImport($excelUrl)
    {
        $error = [];
        $types = Base::exchangeKey(self::TYPE);
        $difficultys = Base::exchangeKey(self::DIFFICULTY_LEVEL);
        try {
            $subjects = Db::table('exam_subjects')->select()->toArray();
            $subjects = Base::headelId('title', $subjects);
        } catch (Exception $e) {
            return error(-20, '科目查询失败');
        }
        try {
            $knowledges = Db::table('exam_knowledge')->select()->toArray();
            $knowledges = Base::headelId('title', $knowledges);
        } catch (Exception $e) {
            return error(-20, '科目查询失败');
        }
        $currentUser = User::fetchCurrent();
        $questionInfo = Excel::read(
            $excelUrl,
            [
                ['field' => 'subject', 'column' => 'A'],
                ['field' => 'knowledge', 'column' => 'B'],
                ['field' => 'typeTitle', 'column' => 'C'],
                ['field' => 'difficultyLevel', 'column' => 'D'],
//            ['field' => 'score', 'column' => 'E'],
                ['field' => 'name', 'column' => 'E'],
                ['field' => 'options', 'column' => 'F'],
                ['field' => 'answer', 'column' => 'G'],
                ['field' => 'analysis', 'column' => 'H'],
            ]
        );
        if (empty($questionInfo) || is_error($questionInfo)) {
            return error(-24, '文件错误,未读取到数据');
        }
        $line = 1;
        foreach ($questionInfo as $k => $v) {
            $line++;
            if (is_object($v)) {
                $error[$line] = ['line' => $line, 'msg' => '模板格式有误'];
                continue;
            }
            $obj = false;
            foreach ($v as $item) {
                if (is_object($item)) {
                    $obj = true;
                }
            }
            if ($obj) {
                $error[$line] = ['line' => $line, 'msg' => '内容存在格式错误'];
                continue;
            }
            $params = [];
            $params['founder'] = $currentUser['id'];
            if (empty($types[$v['typeTitle']])) {
                $error[$line] = ['line' => $line, 'msg' => '分类错误'];
                continue;
            }
            if (empty($difficultys[$v['difficultyLevel']])) {
                $error[$line] = ['line' => $line, 'msg' => '等级错误'];
                continue;
            }
            if (empty($subjects[$v['subject']])) {
                $error[$line] = ['line' => $line, 'msg' => '没有此科目:' . $v['subject']];
                continue;
            } else {
                $params['subject'] = $subjects[$v['subject']]['id'];
            }
            if (empty($knowledges[$v['knowledge']])) {
                $error[$line] = ['line' => $line, 'msg' => '没有此知识点:' . $v['knowledge']];
                continue;
            } else {
                $params['knowledge'] = $knowledges[$v['knowledge']]['id'];
            }
//            if (empty($v['score'])) {
//                $error[$line] = ['line' => $line, 'msg' => '该题没有分数'];
//                continue;
//            }
            if (empty($v['name'])) {
                $error[$line] = ['line' => $line, 'msg' => '该题题目不能为空'];
                continue;
            }
            if (empty($v['answer'])) {
                $error[$line] = ['line' => $line, 'msg' => '该题标准答案不能为空'];
                continue;
            }
            $params['type'] = $types[$v['typeTitle']];
            $params['difficultyLevel'] = $difficultys[$v['difficultyLevel']];
//            $params['score'] = $v['score'];
            $params['name'] = $v['name'];
            switch ($types[$v['typeTitle']]) {
                case 'fillIn':
                case 'answer':
                case 'composition':
                    $params['answer'] = serialize($v['answer']);
                    break;
                case 'handel':
                    $params['answer'] = serialize(['url' => $v['answer']]);
                    break;
                case 'judge':
                    $params['answer'] = ($v['answer'] == '正确') ? 1 : 0;
                    $params['answer'] = serialize($params['answer']);
                    break;
                case 'choice':
                case 'choices':
                    if (is_object($v['options'])) {
                        $error[$line] = ['line' => $line, 'msg' => '选项模板格式有误'];
                        break;
                    }
                    if (empty($v['options'])) {
                        $error[$line] = ['line' => $line, 'msg' => '该题没有选项'];
                        break;
                    }
                    $v['options'] = explode('|', $v['options']);
                    $options = [];
                    foreach ($v['options'] as $k1 => $v1) {
                        $options[self::CHOICES[$k1 + 1]] = $v1;
                    }
                    $params['options'] = serialize($options);
                    if ($types[$v['typeTitle']] == 'choice') {
                        $params['answer'] = serialize($v['answer']);
                    } else {
                        $params['answer'] = serialize(str_split($v['answer']));
                    }
                    break;
                case 'typing':
                    $typingInfo = explode(',', $v['answer']);
                    if (count($typingInfo) != 2) {
                        $error[$line] = ['line' => $line, 'msg' => '参数错误'];
                    }
                    $params['answer'] = serialize(['time' => $typingInfo[0], 'number' => $typingInfo[1]]);
                    break;
                default:
                    $error[$line] = ['line' => $line, 'msg' => '题型错误'];
                    break;
            }
            $filters = [
                'name' => $params['name'],
                'knowledge' => $params['knowledge']
            ];
            $exists = Question::fetch($filters);
            if (!empty($exists)) {
                $error[$line] = ['line' => $line, 'msg' => '已存在此题'];
            }
            if (empty($error[$line])) {
                $params['analysis'] = serialize($v['analysis']);
                $result = Question::add($params);
                if (!$result) {
                    $error[$line] = ['line' => $line, 'msg' => '添加失败'];
                }
            }
        }
        return $error;
    }

    /**
     * 统计题型分类
     * 统计难度分类
     * 统计知识点分类
     * @param $subjectId
     * @return array|Error
     */
    public static function statistics($subjectId)
    {
        $where = [];
        $where[] = ['subject', '=', $subjectId];
        $count = [
            'questionCount' => 0,
            'knowledgeCount' => 0,
        ];
        try {
            $typeCount = Db::table('exam_questions')
                ->field('type,count(*) count')
                ->where($where)
                ->group('type')
                ->select()->toArray();

            foreach ($typeCount as $v) {
                $count['questionCount'] += $v['count'];
            }
            $knowledgeCount = Db::table('exam_questions')
                ->field('knowledge type,count(*) count')
                ->where($where)
                ->group('knowledge')
                ->select()->toArray();
            foreach ($typeCount as $v) {
                $count['knowledgeCount'] += $v['count'];
            }
            $difficultyCount = Db::table('exam_questions')
                ->field('difficulty_level type,count(*) count')
                ->where($where)
                ->group('difficulty_level')
                ->select()->toArray();
            return [
                'typeCount' => $typeCount,
                'knowledgeCount' => $knowledgeCount,
                'difficultyCount' => $difficultyCount,
                'count' => $count
            ];
        } catch (Exception $e) {
            Log::channel('myError')->write($e->getMessage(), \think\Log::ERROR);
        }
        return error(-20, '统计失败');
    }

    /**
     * 根据科目统计试题
     * @param $subjectId
     * @return int
     */
    public static function questionCount($subjectId)
    {
        $where = [];
        $where[] = ['subject', '=', $subjectId];
        try {
            return Db::table('exam_questions')
                ->where($where)
                ->count();
        } catch (Exception $e) {
            Log::channel('myError')->write($e->getMessage(), \think\Log::ERROR);
        }
        return 0;
    }

    /**
     * 判断答案是否正确
     * @param $questionId
     * @param $content
     * @return bool|Error
     * @see QuestionTest::testQuestionVerify()
     */
    public static function verify($questionId, $content)
    {
        $questionInfo = self::fetch(intval($questionId));
        if (empty($questionInfo)) {
            return error(-20, '该题不存在');
        }
        $type = $questionInfo['type'];
        if (!in_array($type, ['judge', 'choice', 'choices', 'fillIn'])) {
            return error(-21, '此题参考答案不能作为分数判断依据');
        }
        if ($type == 'choices') {
            if (!is_array($content) || (count($questionInfo['answer']) != count($content))) {
                return false;
            }
            foreach ($questionInfo['answer'] as $answer) {
                if (!in_array($answer, $content)) {
                    return false;
                }
            }
        } elseif ($content !== $questionInfo['answer']) {
            return false;
        }
        return true;
    }

    /**
     * 按照题库包分组聚合类型知识点科目
     * @param $package
     * @param $group
     * @return array|array[]|\array[][]
     */
    public static function group($package, $group)
    {
        $where = [];
        $where[] = ['package', '=', $package];
        try {
            return Db::table('exam_questions')->where($where)->group($group)->field($group)->select()->toArray();
        } catch (Exception $e) {
            Log::channel('myError')->write($e->getMessage(), \think\Log::ERROR);
        }
        return error(-20, '统计失败');
    }
}