package com.qmth.exam.reserve.service.impl;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.qmth.boot.core.exception.StatusException;
import com.qmth.boot.tools.excel.model.DataMap;
import com.qmth.exam.reserve.cache.impl.ApplyTaskCacheService;
import com.qmth.exam.reserve.entity.CategoryEntity;
import com.qmth.exam.reserve.entity.StudentEntity;
import com.qmth.exam.reserve.entity.StudentImportTaskEntity;
import com.qmth.exam.reserve.enums.CategoryLevel;
import com.qmth.exam.reserve.enums.ImportStatus;
import com.qmth.exam.reserve.service.CategoryService;
import com.qmth.exam.reserve.service.StudentImportAsyncService;
import com.qmth.exam.reserve.service.StudentImportTaskService;
import com.qmth.exam.reserve.service.StudentService;

@Service
public class StudentImportAsyncServiceImpl implements StudentImportAsyncService {

    private final static Logger log = LoggerFactory.getLogger(StudentImportAsyncServiceImpl.class);

    private static final String[] EXCEL_HEADER = new String[] { "学号", "姓名", "证件号", "所属教学点代码", "所属教学点名称", "可约时段数" };

    @Autowired
    private CategoryService categoryService;

    @Autowired
    private StudentService studentService;

    @Autowired
    private StudentImportTaskService importTaskService;

    @Autowired
    private ApplyTaskCacheService cacheService;

    @Async
    @Transactional
    @Override
    public void asyncImportStudent(StudentImportTaskEntity task, Long orgId, List<DataMap> lineList) {
        Map<String, CategoryEntity> teachingCache = getTeachingCache();
        List<StudentEntity> studentList = new ArrayList<StudentEntity>();
        List<Map<String, Object>> failRecords = new ArrayList<Map<String, Object>>();
        for (int i = 0; i < lineList.size(); i++) {
            DataMap line = lineList.get(i);
            StringBuilder msg = new StringBuilder();
            StudentEntity student = new StudentEntity();
            String studentCode = trimAndNullIfBlank(line.get(EXCEL_HEADER[0]));
            if (StringUtils.isBlank(studentCode)) {
                msg.append(" 学号不能为空");
            } else if (studentCode.length() > 20) {
                msg.append(" 学号的长度不能超过20位");
            } else {
                student.setStudentCode(studentCode);
            }
            String name = trimAndNullIfBlank(line.get(EXCEL_HEADER[1]));
            if (StringUtils.isBlank(name)) {
                msg.append(" 姓名不能为空");
            } else if (name.length() > 50) {
                msg.append(" 考生姓名的长度不能超过50位");
            } else {
                student.setName(name);
            }

            String identityNumber = trimAndNullIfBlank(line.get(EXCEL_HEADER[2]));
            if (StringUtils.isBlank(identityNumber)) {
                msg.append(" 证件号不能为空");
            } else if (identityNumber.length() < 6 || identityNumber.length() > 20) {
                msg.append(" 证件号不正确");
            } else {
                student.setIdentityNumber(identityNumber);
                String password = identityNumber.substring(identityNumber.length() - 6);
                student.setPassword(DigestUtils.sha256Hex(password).toUpperCase());
            }

            String teachingCode = trimAndNullIfBlank(line.get(EXCEL_HEADER[3]));
            if (StringUtils.isBlank(teachingCode)) {
                msg.append(" 教学点代码不能为空");
            }
            CategoryEntity category = teachingCache.get(teachingCode);
            if (category == null) {
                msg.append(" 教学点不存在");
            }

            String teachingName = trimAndNullIfBlank(line.get(EXCEL_HEADER[4]));
            if (StringUtils.isBlank(teachingName)) {
                msg.append(" 教学点名称不能为空");
            }
            if (category != null && !category.getName().equals(teachingName)) {
                msg.append(" 教学点名称错误");
            }
            if (category != null && category.getName().equals(teachingName)) {
                student.setCategoryId(category.getId());
                student.setOrgId(orgId);
                student.setApplyTaskId(task.getApplyTaskId());
            }
            try {
                String applyNumber = trimAndNullIfBlank(line.get(EXCEL_HEADER[5]));
                if (StringUtils.isBlank(applyNumber)) {
                    msg.append(" 可约时段数不能为空");
                } else {
                    student.setApplyNumber(Integer.parseInt(applyNumber));
                }
            } catch (Exception e) {
                msg.append(" 可约时段只能为数字");
            }
            if (msg.length() > 0) {
                failRecords.add(newError(i + 1, msg.toString()));
            } else {
                studentList.add(student);
            }
        }

        if (CollectionUtils.isNotEmpty(failRecords)) {
            updateStudentImportTask(task, ImportStatus.FAILURE.toString(), failRecords);
            return;
        }
        for (int i = 0; i < studentList.size(); i++) {
            StudentEntity studentEntity = studentList.get(i);
            try {
                saveStudent(studentEntity);
            } catch (StatusException e) {
                failRecords.add(newError(i + 1, e.getMessage()));
            } catch (Exception e) {
                failRecords.add(newError(i + 1, " 系统异常"));
                log.error("导入异常", e);
                updateStudentImportTask(task, ImportStatus.FAILURE.toString(), failRecords);
                return;
            }
        }
        updateStudentImportTask(task, ImportStatus.SUCCESS.toString(), failRecords);
    }

    private void updateStudentImportTask(StudentImportTaskEntity task, String status,
            List<Map<String, Object>> failRecords) {
        task.setMessage(failRecords.size() == 0 ? null : failRecords.toString());
        task.setStatus(status);
        importTaskService.saveOrUpdate(task);
    }

    private void saveStudent(StudentEntity studentEntity) {
        LambdaQueryWrapper<StudentEntity> wrapper = new LambdaQueryWrapper<StudentEntity>()
                .eq(StudentEntity::getStudentCode, studentEntity.getStudentCode())
                .eq(StudentEntity::getApplyTaskId, studentEntity.getApplyTaskId());
        StudentEntity existStudent = studentService.getOne(wrapper);
        if (existStudent != null) {
            existStudent.setName(studentEntity.getName());
            existStudent.setIdentityNumber(studentEntity.getIdentityNumber());
            existStudent.setCategoryId(studentEntity.getCategoryId());
            existStudent.setApplyNumber(studentEntity.getApplyNumber());
            studentService.updateById(existStudent);
            // 清空缓存
            cacheService.clearStudentApplyNumberCache(existStudent.getId());
        } else {
            studentEntity.setApplyFinished(Boolean.FALSE);
            studentService.save(studentEntity);
        }
    }

    private String trimAndNullIfBlank(String s) {
        if (StringUtils.isBlank(s)) {
            return null;
        }
        return s.trim();
    }

    private Map<String, Object> newError(int lineNum, String msg) {
        Map<String, Object> map = new HashMap<>();
        map.put("lineNum", lineNum);
        map.put("msg", msg);
        return map;
    }

    private Map<String, CategoryEntity> getTeachingCache() {
        LambdaQueryWrapper<CategoryEntity> lm = new LambdaQueryWrapper<>();
        lm.eq(CategoryEntity::getEnable, Boolean.TRUE);
        lm.eq(CategoryEntity::getLevel, CategoryLevel.TEACHING.getValue());
        List<CategoryEntity> categoryList = categoryService.list(lm);
        return categoryList.stream().collect(Collectors.toMap(CategoryEntity::getCode, Function.identity()));
    }

}
