package cn.com.qmth.examcloud.tool.service.batch_import_exam_student;

import cn.com.qmth.examcloud.tool.cache.LoginSessionManager;
import cn.com.qmth.examcloud.tool.config.SysProperty;
import cn.com.qmth.examcloud.tool.entity.TaskEntity;
import cn.com.qmth.examcloud.tool.service.CommonService;
import cn.com.qmth.examcloud.tool.service.TaskService;
import cn.com.qmth.examcloud.tool.service.batch_import_exam_student.vo.ExamStudentInfo;
import cn.com.qmth.examcloud.tool.service.batch_import_exam_student.vo.ExamStudentInfoListener;
import cn.com.qmth.examcloud.tool.utils.HttpHelper;
import cn.com.qmth.examcloud.tool.utils.JsonMapper;
import cn.com.qmth.examcloud.tool.utils.StatusException;
import cn.com.qmth.examcloud.tool.vo.user.User;
import com.alibaba.excel.EasyExcel;
import com.fasterxml.jackson.databind.JsonNode;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;

@Component
public class BatchImportExamStudentTask implements TaskService {

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

    private ExecutorService threadService = Executors.newCachedThreadPool();

    @Autowired
    private SysProperty sysProperty;

    @Autowired
    private CommonService commonService;

    @Override
    public void start(TaskEntity task) {
        User loginUser = LoginSessionManager.getLoginSession(task.getUserToken());
        if (loginUser == null) {
            throw new StatusException("用户未登录！");
        }

        this.execute(loginUser, task);
    }

    private void execute(User loginUser, TaskEntity task) {
        JsonNode jsonParams = new JsonMapper().getNode(task.getParams());
        if (jsonParams == null) {
            throw new StatusException("任务参数解析错误！");
        }

        Long examId = jsonParams.get("examId").asLong();
        String dataFilePath = jsonParams.get("dataFilePath").asText();
        if (StringUtils.isBlank(dataFilePath)) {
            throw new StatusException("参数dataFilePath不能为空！");
        }

        ExamStudentInfoListener dataListener = new ExamStudentInfoListener();
        try {
            EasyExcel.read(dataFilePath, ExamStudentInfo.class, dataListener).sheet().doRead();
        } catch (Exception e) {
            log.error(e.getMessage());
            throw new StatusException("Excel内容解析错误，请使用标准模板！");
        }
        List<ExamStudentInfo> dataList = dataListener.getList();

        int total = dataList.size();
        log.info("共{}条数据！examId:{}", total, examId);
        if (total == 0) {
            return;
        }

        if (total > 100000) {
            throw new StatusException("Excel的数据条数限制最大10万条！");
        }

        // 检验数据
        Map<String, Long> orgMaps = new HashMap<>();
        Map<String, Long> courseMaps = new HashMap<>();
        List<String> errMessages = new ArrayList<>();
        for (int i = 0; i < total; i++) {
            ExamStudentInfo examStudent = dataList.get(i);
            examStudent.setExamId(examId);

            if (StringUtils.isBlank(examStudent.getStudentName())) {
                errMessages.add("第" + (i + 1) + "条 姓名不能为空！");
            } else {
                examStudent.setStudentName(examStudent.getStudentName().trim());
            }

            if (StringUtils.isBlank(examStudent.getStudentCode())) {
                errMessages.add("第" + (i + 1) + "条 学号不能为空！");
            } else {
                examStudent.setStudentCode(examStudent.getStudentCode().trim());
            }

            if (StringUtils.isBlank(examStudent.getIdentityNumber())) {
                errMessages.add("第" + (i + 1) + "条 身份证号不能为空！");
            } else {
                examStudent.setIdentityNumber(examStudent.getIdentityNumber().trim());
            }

            if (StringUtils.isBlank(examStudent.getOrgCode())) {
                errMessages.add("第" + (i + 1) + "条 机构代码不能为空！");
            } else {
                examStudent.setOrgCode(examStudent.getOrgCode().trim());
            }

            if (StringUtils.isBlank(examStudent.getCourseCode())) {
                errMessages.add("第" + (i + 1) + "条 课程代码不能为空！");
            } else {
                examStudent.setCourseCode(examStudent.getCourseCode().trim());
            }

            Long orgId = orgMaps.get(examStudent.getOrgCode());
            if (orgId == null) {
                orgId = commonService.queryOrgIdByCode(loginUser, examStudent.getOrgCode());
                if (orgId == null) {
                    errMessages.add("第" + (i + 1) + "条 机构代码不正确！" + examStudent.getOrgCode());
                } else {
                    orgMaps.put(examStudent.getOrgCode(), orgId);
                }
            }
            examStudent.setOrgId(orgId);

            Long courseId = courseMaps.get(examStudent.getCourseCode());
            if (courseId == null) {
                courseId = commonService.queryCourseIdByCode(loginUser, examStudent.getCourseCode());
                if (courseId == null) {
                    errMessages.add("第" + (i + 1) + "条 课程代码不正确！" + examStudent.getCourseCode());
                } else {
                    courseMaps.put(examStudent.getCourseCode(), courseId);
                }
            }
            examStudent.setCourseId(courseId);
        }

        if (!errMessages.isEmpty()) {
            throw new StatusException(StringUtils.join(errMessages, "\n"));
        }

        // 开始执行导入
        long startTime = System.currentTimeMillis();
        AtomicInteger successCount = new AtomicInteger(), failCount = new AtomicInteger();
        if (total <= 1000) {
            this.singleRun(loginUser, dataList, successCount, failCount);
        } else {
            final int batchSize = 20;// 分批数量
            for (int start = 0; start < total; start += batchSize) {
                int end = Math.min(start + batchSize, total);
                List<ExamStudentInfo> batchList = dataList.subList(start, end);
                this.concurrentRun(loginUser, batchList, successCount, failCount);

                long cost = Math.max((System.currentTimeMillis() - startTime) / 1000L, 1);
                int runCount = successCount.get() + failCount.get();
                log.info("总数:{} 成功数:{} 失败数:{} 已执行:{}条 已耗时:{}秒 平均每秒{}条 进度:{}%"
                        , total, successCount.get(), failCount.get(), runCount, cost
                        , runCount / cost, runCount * 100f / total);
            }
        }

        long cost = Math.max((System.currentTimeMillis() - startTime) / 1000L, 1);
        String msg = String.format("总数:%s 成功数:%s 失败数:%s 平均每秒%s条 总耗时:%s秒"
                , total, successCount.get(), failCount.get(), total / cost, cost);
        log.info(msg);
        task.setDescription(msg);

        dataList.clear();
    }

    private void singleRun(User loginUser, List<ExamStudentInfo> batchList, AtomicInteger successCount, AtomicInteger failCount) {
        long startTime = System.currentTimeMillis();
        for (int n = 0; n < batchList.size(); n++) {
            ExamStudentInfo examStudent = batchList.get(n);
            try {
                this.saveExamStudent(loginUser, examStudent);
                successCount.incrementAndGet();
            } catch (Exception e) {
                failCount.incrementAndGet();
                log.error("学号:{} 执行失败！{}", examStudent.getStudentCode(), e.getMessage());
            }

            long cost = Math.max((System.currentTimeMillis() - startTime) / 1000L, 1);
            int runCount = n + 1;
            if (runCount % 20 == 0) {
                log.info("总数:{} 成功数:{} 失败数:{} 已执行:{}条 已耗时:{}秒 平均每秒{}条 进度:{}%"
                        , batchList.size(), successCount.get(), failCount.get(), runCount, cost
                        , runCount / cost, runCount * 100f / batchList.size());
            }
        }
    }

    private void concurrentRun(User loginUser, List<ExamStudentInfo> batchList, AtomicInteger successCount, AtomicInteger failCount) {
        if (batchList.isEmpty()) {
            return;
        }

        final CountDownLatch commander = new CountDownLatch(1);
        final CountDownLatch worker = new CountDownLatch(batchList.size());

        for (int n = 0; n < batchList.size(); n++) {
            ExamStudentInfo examStudent = batchList.get(n);

            Runnable runnable = () -> {
                try {
                    this.saveExamStudent(loginUser, examStudent);
                    successCount.incrementAndGet();
                } catch (Exception e) {
                    failCount.incrementAndGet();
                    log.error("学号:{} 执行失败！{}", examStudent.getStudentCode(), e.getMessage());
                } finally {
                    worker.countDown();
                }
            };

            threadService.execute(runnable);
        }

        try {
            commander.countDown();
            worker.await();
        } catch (Exception e) {
            log.error("线程异常！{}", e.getMessage());
        }
    }

    private void saveExamStudent(User loginUser, ExamStudentInfo examStudent) {
        Map<String, String> headers = new HashMap<>();
        headers.put("key", loginUser.getKey());
        headers.put("token", loginUser.getToken());

        Map<String, Object> params = new HashMap<>();
        params.put("examId", examStudent.getExamId());
        params.put("courseId", examStudent.getCourseId());
        params.put("orgId", examStudent.getOrgId());
        params.put("studentName", examStudent.getStudentName());
        params.put("studentCode", examStudent.getStudentCode());
        params.put("identityNumber", examStudent.getIdentityNumber());
        params.put("specialtyName", examStudent.getSpecialtyName());
        // 其它字段 暂略...

        String url = loginUser.getServerUrl() + "/api/ecs_exam_work/exam_student";
        String result = HttpHelper.put(url, headers, params);
        // log.info(result);

        JsonNode value = new JsonMapper().getNode(result);
        if (value == null || !value.has("id")) {
            // 创建成功会返回ID，否则失败
            throw new StatusException(result);
        }
    }

}
