package cn.com.qmth.stmms.api.controller;

import cn.com.qmth.stmms.admin.utils.PaperJsonUtils;
import cn.com.qmth.stmms.api.exception.ApiException;
import cn.com.qmth.stmms.biz.common.domain.card.CardFile;
import cn.com.qmth.stmms.biz.exam.model.*;
import cn.com.qmth.stmms.biz.exam.service.*;
import cn.com.qmth.stmms.biz.file.enums.FormatType;
import cn.com.qmth.stmms.biz.file.service.FileService;
import cn.com.qmth.stmms.common.annotation.RoleRequire;
import cn.com.qmth.stmms.common.domain.ApiUser;
import cn.com.qmth.stmms.common.enums.*;
import cn.com.qmth.stmms.common.utils.RequestUtils;

import com.aliyun.oss.common.utils.BinaryUtil;
import com.google.common.io.ByteStreams;

import net.sf.json.JSONArray;
import net.sf.json.JSONObject;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map;

/**
 * 统一的对外文件服务接口
 *
 * @author luoshi
 */
@Controller("fileApiController")
@RequestMapping("/api/file")
public class FileController extends BaseApiController {

    protected static final Logger log = LoggerFactory.getLogger(FileController.class);

    @Autowired
    private FileService fileService;

    @Autowired
    private ExamService examService;

    @Autowired
    private ExamSubjectService subjectService;

    @Autowired
    private ExamStudentService studentService;

    @Autowired
    private ExamPackageService packageService;

    @Autowired
    private ExamQuestionService questionService;

    @Autowired
    private MarkGroupService groupService;

    @Autowired
    private AnswerCardService answerCardService;

    private Exam validateExam(ApiUser au, Integer examId, ExamType... types) {
        Exam exam = examService.findById(examId);
        if (exam == null || !validateSchool(au.getSchoolId(), exam.getSchoolId())
                || exam.getStatus() != ExamStatus.START) {
            throw ApiException.EXAM_NOT_ACCESSIBLED;
        }
        if (types != null && types.length > 0 && !Arrays.asList(types).contains(exam.getType())) {
            throw ApiException.QUERY_PARAM_ERROR.replaceMessage("exam type invalid");
        }
        return exam;
    }

    private ExamPackage validateExamPackage(Integer examId, String packageCode) {
        ExamPackage ep = packageService.find(examId, packageCode);
        if (ep == null) {
            throw ApiException.QUERY_PARAM_ERROR.appendMessage(": packageCode error");
        }
        return ep;
    }

    private ExamStudent validateExamStudent(Integer examId, String examNumber) {
        ExamStudent student = studentService.findByExamIdAndExamNumber(examId, examNumber);
        if (student == null) {
            throw ApiException.QUERY_PARAM_ERROR.appendMessage(": examNumber error");
        }
        return student;
    }

    private ExamSubject validateExamSubject(Integer examId, String subjectCode) {
        ExamSubject subject = subjectService.find(examId, subjectCode);
        if (subject == null) {
            throw ApiException.QUERY_PARAM_ERROR.appendMessage(": subjectCode error");
        }
        return subject;
    }

    private FormatType validateFormatType(FormatType type, FormatType... types) {
        if (type == null) {
            throw ApiException.FORMAT_TYPE_ERROR.replaceMessage("format type is null");
        }
        if (types != null && !Arrays.asList(types).contains(type)) {
            throw ApiException.FORMAT_TYPE_ERROR;
        }
        return type;
    }

    private void validateIndex(Integer index) {
        if (index == null || index <= 0) {
            throw ApiException.QUERY_PARAM_ERROR.appendMessage(": index error");
        }
    }

    private void validatePageCount(Integer pageCount) {
        if (pageCount == null) {
            throw ApiException.QUERY_PARAM_ERROR.replaceMessage("card page count is null");
        }
        if (pageCount != 1 && pageCount % 2 != 0) {
            throw ApiException.QUERY_PARAM_ERROR.replaceMessage("card page count error");
        }
    }

    @RequestMapping(value = "/sheet/upload", method = RequestMethod.POST)
    @RoleRequire({ Role.SCHOOL_ADMIN, Role.SCHOOL_DEV, Role.SCANNER })
    @ResponseBody
    public JSONObject uploadSheet(HttpServletRequest request, @RequestParam Integer examId,
            @RequestParam String examNumber, @RequestParam Integer index, @RequestParam String md5,
            @RequestParam MultipartFile file) {
        ApiUser au = RequestUtils.getApiUser(request);
        validateExam(au, examId, ExamType.SCAN_IMAGE);
        validateExamStudent(examId, examNumber);
        validateIndex(index);
        try {
            fileService.uploadSheet(file.getInputStream(), md5, examId, examNumber, index);
        } catch (Exception e) {
            log.error("sheet upload error", e);
            throw ApiException.FILE_UPLOAD_ERROR.replaceMessage("sheet upload error: " + e.getMessage());
        }
        return result(true, fileService.getFileServer().concat(fileService.getSheetUri(examId, examNumber, index)));
    }

    @RequestMapping(value = "/slice/upload", method = RequestMethod.POST)
    @RoleRequire({ Role.SCHOOL_ADMIN, Role.SCHOOL_DEV, Role.SCANNER })
    @ResponseBody
    public JSONObject uploadSlice(HttpServletRequest request, @RequestParam Integer examId,
            @RequestParam String examNumber, @RequestParam Integer index, @RequestParam String md5,
            @RequestParam MultipartFile file) {
        ApiUser au = RequestUtils.getApiUser(request);
        validateExam(au, examId, ExamType.SCAN_IMAGE);
        ExamStudent student = validateExamStudent(examId, examNumber);
        validateIndex(index);
        try {
            fileService.uploadSlice(file.getInputStream(), md5, examId, student.getSecretNumber(), index);
        } catch (Exception e) {
            log.error("slice upload error", e);
            throw ApiException.FILE_UPLOAD_ERROR.replaceMessage("slice upload error: " + e.getMessage());
        }
        return result(true,
                fileService.getFileServer().concat(fileService.getSliceUri(examId, student.getSecretNumber(), index)));
    }

    @RequestMapping(value = "/json/upload", method = RequestMethod.POST)
    @RoleRequire({ Role.SCHOOL_ADMIN, Role.SCHOOL_DEV, Role.SCANNER })
    @ResponseBody
    public JSONObject uploadJson(HttpServletRequest request, @RequestParam Integer examId,
            @RequestParam String examNumber, @RequestParam String md5, @RequestParam MultipartFile file) {
        ApiUser au = RequestUtils.getApiUser(request);
        validateExam(au, examId, ExamType.MULTI_MEDIA);
        ExamStudent student = validateExamStudent(examId, examNumber);
        try {
            fileService.uploadJson(file.getInputStream(), md5, examId, student.getSecretNumber());
            student.setUpload(true);
            student.setAbsent(false);
            student.setBatchCode(StringUtils.EMPTY);
            student.setAnswers(StringUtils.EMPTY);
            saveUploadStudent(student);
        } catch (Exception e) {
            log.error("json upload error", e);
            throw ApiException.FILE_UPLOAD_ERROR.replaceMessage("json upload error: " + e.getMessage());
        }
        return result(true,
                fileService.getFileServer().concat(fileService.getJsonUri(examId, student.getSecretNumber())));
    }

    @RequestMapping(value = "/package/upload", method = RequestMethod.POST)
    @RoleRequire({ Role.SCHOOL_ADMIN, Role.SCHOOL_DEV, Role.SCANNER })
    @ResponseBody
    public JSONObject uploadPackage(HttpServletRequest request, @RequestParam Integer examId,
            @RequestParam String packageCode, @RequestParam Integer index, @RequestParam String md5,
            @RequestParam MultipartFile file) {
        ApiUser au = RequestUtils.getApiUser(request);
        validateExam(au, examId, ExamType.SCAN_IMAGE);
        validateExamPackage(examId, packageCode);
        validateIndex(index);
        try {
            fileService.uploadPackage(file.getInputStream(), md5, examId, packageCode, index);
        } catch (Exception e) {
            log.error("package upload error", e);
            throw ApiException.FILE_UPLOAD_ERROR.replaceMessage("package upload error: " + e.getMessage());
        }
        return result(true, fileService.getFileServer().concat(fileService.getPackageUri(examId, packageCode, index)));
    }

    @RequestMapping(value = "/paper/upload", method = RequestMethod.POST)
    @RoleRequire({ Role.SCHOOL_ADMIN, Role.SCHOOL_DEV, Role.SCANNER })
    @ResponseBody
    public JSONObject uploadPaper(HttpServletRequest request, @RequestParam Integer examId,
            @RequestParam String subjectCode, @RequestParam FormatType format, @RequestParam String md5,
            @RequestParam MultipartFile file) {
        ApiUser au = RequestUtils.getApiUser(request);
        Exam exam = validateExam(au, examId);
        // ExamSubject subject = validateExamSubject(examId, subjectCode);
        ExamSubject subject = subjectService.find(examId, subjectCode);
        if (subject == null) {
            return result(false);
        }
        if (exam.getType() == ExamType.SCAN_IMAGE) {
            validateFormatType(format, FormatType.PDF);
        } else if (exam.getType() == ExamType.MULTI_MEDIA) {
            validateFormatType(format, FormatType.JSON);
        }
        try {
            if (exam.getType() == ExamType.MULTI_MEDIA) {
                String text = new String(ByteStreams.toByteArray(file.getInputStream()), "UTF-8");
                Map<Boolean, List<ExamQuestion>> map = PaperJsonUtils.toSubject(subject, text);
                // 客观题全体更新
                if (0 != questionService.countByExamIdAndSubjectAndObjectiveAndGroupNumberIsNull(examId, subjectCode,
                        true)) {
                    questionService.deleteByExamAndSubjectAndObjective(examId, subjectCode, true);
                }
                List<ExamQuestion> oList = map.get(Boolean.TRUE);
                questionService.save(oList);
                examService.updateObjectiveStatus(examId, ObjectiveStatus.WAITING);
                // 主观题有分组不更新
                List<MarkGroup> groups = groupService.findByExamAndSubject(examId, subject.getCode());
                if (groups == null || groups.size() == 0) {
                    if (0 != questionService.countByExamIdAndSubjectAndObjectiveAndGroupNumberIsNull(examId,
                            subjectCode, false)) {
                        questionService.deleteByExamAndSubjectAndObjective(examId, subjectCode, false);
                    }
                    List<ExamQuestion> sList = map.get(Boolean.FALSE);
                    questionService.save(sList);
                }
            }
            fileService.uploadPaper(file.getInputStream(), md5, examId, subjectCode, format);
            subject.setPaperFileType(format);
            subjectService.save(subject);
            subjectService.updateScore(examId, subjectCode, true,
                    questionService.sumTotalScore(examId, subjectCode, true));
            subjectService.updateScore(examId, subjectCode, false,
                    questionService.sumTotalScore(examId, subjectCode, false));
        } catch (Exception e) {
            log.error("paper upload error", e);
            throw ApiException.FILE_UPLOAD_ERROR.replaceMessage("paper upload error: " + e.getMessage());
        }
        return result(true, fileService.getFileServer().concat(fileService.getPaperUri(examId, subjectCode, format)));
    }

    @RequestMapping(value = "/answer/upload", method = RequestMethod.POST)
    @RoleRequire({ Role.SCHOOL_ADMIN, Role.SCHOOL_DEV, Role.SCANNER })
    @ResponseBody
    public JSONObject uploadAnswer(HttpServletRequest request, @RequestParam Integer examId,
            @RequestParam String subjectCode, @RequestParam FormatType format, @RequestParam String md5,
            @RequestParam MultipartFile file) {
        ApiUser au = RequestUtils.getApiUser(request);
        Exam exam = validateExam(au, examId);
        // ExamSubject subject = validateExamSubject(examId, subjectCode);
        ExamSubject subject = subjectService.find(examId, subjectCode);
        if (subject == null) {
            return result(false);
        }
        if (exam.getType() == ExamType.SCAN_IMAGE) {
            validateFormatType(format, FormatType.PDF);
        } else if (exam.getType() == ExamType.MULTI_MEDIA) {
            validateFormatType(format, FormatType.JSON);
        }
        try {
            fileService.uploadAnswer(file.getInputStream(), md5, examId, subjectCode, format);
            subject.setAnswerFileType(format);
            subjectService.save(subject);
        } catch (Exception e) {
            log.error("answer upload error", e);
            throw ApiException.FILE_UPLOAD_ERROR.replaceMessage("answer upload error: " + e.getMessage());
        }
        return result(true, fileService.getFileServer().concat(fileService.getAnswerUri(examId, subjectCode, format)));
    }

    @RequestMapping(value = "/card/upload", method = RequestMethod.POST)
    @RoleRequire({ Role.SCHOOL_ADMIN, Role.SCHOOL_DEV, Role.SCANNER })
    @ResponseBody
    public JSONObject uploadCard(HttpServletRequest request, @RequestParam Integer examId,
            @RequestParam(required = false) String subjectCode, @RequestParam FormatType format,
            @RequestParam String md5, @RequestParam MultipartFile file) {
        ApiUser au = RequestUtils.getApiUser(request);
        subjectCode = StringUtils.trimToNull(subjectCode);
        Exam exam = validateExam(au, examId, ExamType.SCAN_IMAGE);
        ExamSubject subject = subjectCode != null ? validateExamSubject(examId, subjectCode) : null;
        // FormatType current = subject != null ? subject.getCardType() :
        // exam.getCardType();
        // if (current != null) {
        // validateFormatType(format, current);
        // } else {
        validateFormatType(format, FormatType.JSON, FormatType.ZIP);
        // }
        String uri = "";
        InputStream ins = null;
        AnswerCard card = null;
        try {
            if (format.equals(FormatType.JSON)) {
                CardFile cardFile = CardFile.parse(file.getInputStream());
                validatePageCount(cardFile.getPages().size());
                byte[] out = cardFile.output();
                ins = new ByteArrayInputStream(out);
                md5 = BinaryUtil.encodeMD5(out);
                card = answerCardService.findByExamIdAndSubjectCode(examId, subjectCode, CardSource.WEB);
                if (card == null) {
                    card = new AnswerCard();
                    card.setExamId(examId);
                    card.setNumber(answerCardService.findMaxNumberByExamId(examId) + 1);
                }
                card.setMd5(md5);
                card.setNeedAdapte(cardFile.isAdapted());
                card.setSliceConfig(cardFile.getSliceConfig().toString());
                card.setSinglePage(cardFile.getPages().size() == 1);
                card.setPaperCount(card.getSinglePage() ? 1 : cardFile.getPages().size() / 2);
                card.setSource(CardSource.WEB);
                card.setUpdateTime(new Date());
                fileService.uploadAnswerCard(ins, md5, examId, card.getNumber());
                answerCardService.save(card, subjectCode);
                if (subject != null) {
                    subject.setCardType(format);
                    subjectService.save(subject);
                }
                uri = fileService.getAnswerCardUri(examId, card.getNumber());
            } else {
                ins = file.getInputStream();
                if (subject != null) {
                    fileService.uploadCard(ins, md5, examId, subjectCode, format);
                    subject.setCardType(format);
                    subjectService.save(subject);
                    uri = fileService.getCardUri(examId, subjectCode, format);
                } else {
                    fileService.uploadCard(ins, md5, examId, format);
                    exam.setCardType(format);
                    examService.save(exam);
                    uri = fileService.getCardUri(examId, format);
                }
            }
        } catch (Exception e) {
            log.error("card upload error", e);
            throw ApiException.FILE_UPLOAD_ERROR.replaceMessage("card upload error: " + e.getMessage());
        }
        return result(true, fileService.getFileServer().concat(uri));
    }

    @RequestMapping(value = "/exchange/upload", method = RequestMethod.POST)
    @RoleRequire({ Role.SCHOOL_ADMIN, Role.SCHOOL_DEV, Role.SCANNER })
    @ResponseBody
    public JSONObject uploadExchange(HttpServletRequest request, @RequestParam String name, @RequestParam String md5,
            @RequestParam MultipartFile file) {
        try {
            fileService.uploadExchange(file.getInputStream(), md5, name);
        } catch (Exception e) {
            log.error("exchange upload error", e);
            throw ApiException.FILE_UPLOAD_ERROR.replaceMessage("exchange upload error: " + e.getMessage());
        }
        return result(true, fileService.getFileServer().concat(fileService.getExchangeUri(name)));
    }

    @RequestMapping(value = "/card/url")
    @RoleRequire({ Role.SCHOOL_ADMIN, Role.SCHOOL_DEV, Role.SCANNER })
    @ResponseBody
    public JSONObject cardUrl(HttpServletRequest request, @RequestParam Integer examId,
            @RequestParam(required = false) String subjectCode) {
        subjectCode = StringUtils.trimToNull(subjectCode);
        Exam exam = validateExam(RequestUtils.getApiUser(request), examId, ExamType.SCAN_IMAGE);
        String uri = "";
        if (subjectCode != null) {
            FormatType type = validateFormatType(validateExamSubject(examId, subjectCode).getCardType());
            uri = fileService.getCardUri(examId, subjectCode, type);
        } else {
            FormatType type = validateFormatType(exam.getCardType());
            uri = fileService.getCardUri(examId, type);
        }
        return result("url", fileService.getFileServer().concat(uri));
    }

    @RequestMapping(value = "/sheet/url")
    @RoleRequire({ Role.SCHOOL_ADMIN, Role.SCHOOL_DEV, Role.SCANNER })
    @ResponseBody
    public JSONArray sheetUrl(HttpServletRequest request, @RequestParam Integer examId,
            @RequestParam String[] examNumbers) {
        validateExam(RequestUtils.getApiUser(request), examId);
        JSONArray array = new JSONArray();
        for (String examNumber : examNumbers) {
            ExamStudent student = studentService.findByExamIdAndExamNumber(examId, examNumber);
            if (student != null) {
                JSONObject obj = new JSONObject();
                obj.accumulate("examNumber", examNumber);
                JSONArray urls = new JSONArray();
                if (student.isUpload() && student.getSheetCount() != null && student.getSheetCount() > 0) {
                    for (int i = 1; i <= student.getSheetCount(); i++) {
                        urls.add(fileService.getFileServer().concat(fileService.getSheetUri(examId, examNumber, i)));
                    }
                }
                obj.accumulate("urls", urls);
            }
        }
        return array;
    }

    @RequestMapping(value = "/package/url")
    @RoleRequire({ Role.SCHOOL_ADMIN, Role.SCHOOL_DEV, Role.SCANNER })
    @ResponseBody
    public JSONArray packageUrl(HttpServletRequest request, @RequestParam Integer examId,
            @RequestParam String[] packageCodes) {
        validateExam(RequestUtils.getApiUser(request), examId);
        JSONArray array = new JSONArray();
        for (String code : packageCodes) {
            ExamPackage ep = packageService.find(examId, code);
            if (ep != null) {
                JSONObject obj = new JSONObject();
                obj.accumulate("packageCode", code);
                JSONArray urls = new JSONArray();
                if (ep.getPicCount() != null && ep.getPicCount() > 0) {
                    for (int i = 1; i <= ep.getPicCount(); i++) {
                        urls.add(fileService.getFileServer().concat(fileService.getPackageUri(examId, code, i)));
                    }
                }
                obj.accumulate("urls", urls);
            }
        }
        return array;
    }

    @RequestMapping(value = "/exchange/url")
    @RoleRequire({ Role.SCHOOL_ADMIN, Role.SCHOOL_DEV, Role.SCANNER })
    @ResponseBody
    public JSONObject exchangeUrl(HttpServletRequest request, @RequestParam String name) {
        return result("url", fileService.getFileServer().concat(fileService.getExchangeUri(name)));
    }

    @RequestMapping(value = "/incomplete/upload", method = RequestMethod.POST)
    @RoleRequire({ Role.SCHOOL_ADMIN, Role.SCHOOL_DEV, Role.SCANNER })
    @ResponseBody
    public JSONObject uploadIncomplete(HttpServletRequest request, @RequestParam Integer examId,
            @RequestParam String name, @RequestParam String md5, @RequestParam MultipartFile file) {
        try {
            fileService.uploadIncomplete(file.getInputStream(), md5, examId, name);
        } catch (Exception e) {
            log.error("incomplete upload error", e);
            throw ApiException.FILE_UPLOAD_ERROR.replaceMessage("incomplete upload error: " + e.getMessage());
        }
        return result(true, fileService.getFileServer().concat(fileService.getIncompleteUri(examId, name)));
    }

    @RequestMapping(value = "/incomplete/url")
    @RoleRequire({ Role.SCHOOL_ADMIN, Role.SCHOOL_DEV, Role.SCANNER })
    @ResponseBody
    public JSONObject incompleteUrl(HttpServletRequest request, @RequestParam Integer examId, @RequestParam String name) {
        return result("url", fileService.getFileServer().concat(fileService.getIncompleteUri(examId, name)));
    }
}
