package cn.com.qmth.examcloud.core.questions.service.consumer;

import java.io.File;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;

import javax.annotation.Resource;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.bson.types.ObjectId;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import cn.com.qmth.examcloud.api.commons.security.bean.User;
import cn.com.qmth.examcloud.commons.exception.StatusException;
import cn.com.qmth.examcloud.core.basic.api.bean.CourseBean;
import cn.com.qmth.examcloud.core.questions.base.CommonUtils;
import cn.com.qmth.examcloud.core.questions.base.IoUtils;
import cn.com.qmth.examcloud.core.questions.base.StringSimilarityUtils;
import cn.com.qmth.examcloud.core.questions.base.enums.PaperStatus;
import cn.com.qmth.examcloud.core.questions.base.enums.PaperType;
import cn.com.qmth.examcloud.core.questions.base.multithread.Consumer;
import cn.com.qmth.examcloud.core.questions.base.question.enums.QuesStructType;
import cn.com.qmth.examcloud.core.questions.dao.PaperDetailRepo;
import cn.com.qmth.examcloud.core.questions.dao.PaperDetailUnitRepo;
import cn.com.qmth.examcloud.core.questions.dao.PaperRepo;
import cn.com.qmth.examcloud.core.questions.dao.QuesRepo;
import cn.com.qmth.examcloud.core.questions.dao.QuestionAudioRepo;
import cn.com.qmth.examcloud.core.questions.dao.entity.Course;
import cn.com.qmth.examcloud.core.questions.dao.entity.Paper;
import cn.com.qmth.examcloud.core.questions.dao.entity.PaperDetail;
import cn.com.qmth.examcloud.core.questions.dao.entity.PaperDetailUnit;
import cn.com.qmth.examcloud.core.questions.dao.entity.QuesOption;
import cn.com.qmth.examcloud.core.questions.dao.entity.Question;
import cn.com.qmth.examcloud.core.questions.dao.entity.QuestionAudio;
import cn.com.qmth.examcloud.core.questions.service.QuesTypeNameService;
import cn.com.qmth.examcloud.core.questions.service.QuestionAudioService;
import cn.com.qmth.examcloud.core.questions.service.UpYunService;
import cn.com.qmth.examcloud.core.questions.service.bean.CopyDataNoReduplicateDto;
import cn.com.qmth.examcloud.core.questions.service.config.SysProperty;
import cn.com.qmth.examcloud.support.filestorage.FileStorageUtil;
import cn.com.qmth.examcloud.web.filestorage.FileStoragePathEnvInfo;
import cn.com.qmth.examcloud.web.filestorage.YunPathInfo;
import cn.com.qmth.examcloud.web.redis.RedisClient;

@Scope("prototype")
@Service
public class CopyDataNoReduplicateConsumer extends Consumer<CopyDataNoReduplicateDto> {
	private static int cacheLockTimeout = 60 * 10;
	private static String withoutReduplicateLock = "$_COPY_QUESTION_DATA_NO_REDUPLICATE_LOCK";
	@Autowired
	private RedisClient redisClient;
	@Autowired
	private PaperRepo paperRepo;
	@Autowired
	private QuestionAudioRepo questionAudioRepo;
	@Autowired
	private SysProperty sysProperty;

	@Autowired
	private PaperDetailRepo paperDetailRepo;

	@Autowired
	private PaperDetailUnitRepo paperDetailUnitRepo;

	@Autowired
	private QuestionAudioService questionAudioService;

	@Autowired
	private QuesRepo quesRepo;

	@Resource(name = "mongoTemplate")
	private MongoTemplate mongoTemplate;
	
	@Resource(name = "mongoTemplate2")
	private MongoTemplate mongoTemplate2;
	
	@Autowired
	private QuesTypeNameService quesTypeNameService;
	

	@Transactional
	@Override
	public int consume(CopyDataNoReduplicateDto dto) {
		List<Question> qs = findQuestion(dto.getUser().getRootOrgId(), dto.getCourse().getCode());
		if (CollectionUtils.isEmpty(qs)) {
			return 1;
		}
		Map<QuesStructType, List<Question>> map = new HashMap<>();
		for (Question q : qs) {
			List<Question> tem = map.get(q.getQuestionType());
			if (tem == null) {
				tem = new ArrayList<>();
				map.put(q.getQuestionType(),tem);
			}
			tem.add(q);
		}
		for (QuesStructType qt : map.keySet()) {
			removeReduplicate(qt, map, dto);
		}
		return 1;
	}

	private void removeReduplicate(QuesStructType qt, Map<QuesStructType, List<Question>> map,
			CopyDataNoReduplicateDto dto) {
		List<Question> qs = checkQuestion(map.get(qt));
		if (CollectionUtils.isEmpty(qs)) {
			return;
		}
		if(qt.isObjective()) {
			String detailName=qt.getName()+"题";
			quesTypeNameService.saveQuesTypeName(dto.getUser().getRootOrgId(), dto.getCourse().getCode(), qt, detailName);
			createPapers(detailName,qs, qt, dto);
		}else {
			Map<String,List<Question>> details=details(qs);
			for (String detailName : details.keySet()) {
				createPapers(detailName,details.get(detailName), qt, dto);
			}
		}
	}
	
	private List<PaperDetailUnit> findUnits(List<Question> qs) {
		List<ObjectId> ids=new ArrayList<>();
		for(Question q:qs) {
			ids.add(new ObjectId(q.getId()));
		}
		Query query = new Query();
		query.addCriteria(Criteria.where("question.$id").in(ids));
		query.addCriteria(Criteria.where("paperType").is(PaperType.IMPORT.name()));
		List<PaperDetailUnit> ret = this.mongoTemplate2.find(query, PaperDetailUnit.class, "paperDetailUnit");
		return ret;
	}
	
	private List<PaperDetail> findDetails(List<PaperDetailUnit> us) {
		List<ObjectId> ids=new ArrayList<>();
		for(PaperDetailUnit u:us) {
			ids.add(new ObjectId(u.getPaperDetail().getId()));
		}
		Query query = new Query();
		query.addCriteria(Criteria.where("_id").in(ids));
		List<PaperDetail> ret = this.mongoTemplate2.find(query, PaperDetail.class, "paperDetail");
		return ret;
	}
	
	private Map<String,List<Question>> details(List<Question> qs){
		Map<String,List<Question>> ret=new HashMap<>();
		if (CollectionUtils.isEmpty(qs)) {
			return ret;
		}
		Map<String,Question> qmap=new HashMap<>();
		for(Question q:qs) {
			qmap.put(q.getId(), q);
		}
		List<PaperDetailUnit> us=findUnits(qs);
		
		List<PaperDetail> ds =findDetails(us);
		Map<String,PaperDetail> dmap=new HashMap<>();
		for(PaperDetail d:ds) {
			dmap.put(d.getId(), d);
		}
		
		for(PaperDetailUnit u:us) {
			String detailName=dmap.get(u.getPaperDetail().getId()).getName();
			List<Question> tem=ret.get(detailName);
			if(tem==null) {
				tem=new ArrayList<>();
				ret.put(detailName, tem);
			}
			tem.add(qmap.get(u.getQuestion().getId()));
		}
		
		return ret;
	}

	private void createPapers(String detailName,List<Question> qs, QuesStructType qt, CopyDataNoReduplicateDto dto) {
		if (CollectionUtils.isEmpty(qs)) {
			return;
		}
		if (qs.size() <= 200) {
			createPaper(detailName,qs, qt, dto, 1);
			redisClient.expire(withoutReduplicateLock, cacheLockTimeout);
		} else {
			int size = qs.size();
			int len = 200;
			int count = (size + len - 1) / len;

			for (int i = 0; i < count; i++) {
				List<Question> subList = qs.subList(i * len, ((i + 1) * len > size ? size : len * (i + 1)));
				createPaper(detailName,subList, qt, dto, i + 1);
				redisClient.expire(withoutReduplicateLock, cacheLockTimeout);
			}
		}

	}

	private void createPaper(String detailName,List<Question> qs, QuesStructType qt, CopyDataNoReduplicateDto dto, int indx) {
		if (qs.size() == 0) {
			return;
		}
		Course course = of(dto.getCourse());
		Paper paper = new Paper();
		paper.setName("(" + dto.getBatch() + ")" + detailName + "_" +dto.getCourse().getCode()+"_"+ indx);
		paper.setTitle(paper.getName());
		paper.setTotalScore((double) qs.size());
		paper.setCreator(dto.getUser().getDisplayName());
		paper.setPaperDetailCount(1);
		paper.setUnitCount(qs.size());
		paper.setLastModifyName(dto.getUser().getDisplayName());
		paper.setPaperStatus(PaperStatus.DRAFT);
		paper.setPaperType(PaperType.IMPORT);
		paper.setCourseNo(dto.getCourse().getCode());
		paper.setCourseName(dto.getCourse().getName());
		paper.setCourse(course);
		paper.setOrgId(dto.getUser().getRootOrgId().toString());
		paper.setDifficultyDegree(0.5);
		paper.setAuditStatus(false);
		paper.setCreationBy(dto.getUser().getUserId());
		paperRepo.save(paper);

		PaperDetail detail = new PaperDetail();
		detail.setPaper(paper);
		detail.setName(detailName);
		detail.setNumber(1);
		detail.setScore((double) qs.size());
		detail.setUnitCount(qs.size());
		detail.setCreator(dto.getUser().getDisplayName());
		detail.setCreateTime(CommonUtils.getCurDateTime());
		detail.setCreationBy(dto.getUser().getUserId());
		paperDetailRepo.save(detail);
		createUnit(paper, detail, qs, qt, dto);
	}

	private void createUnit(Paper paper, PaperDetail detail, List<Question> qs, QuesStructType qt,
			CopyDataNoReduplicateDto dto) {
		List<PaperDetailUnit> units = new ArrayList<>();
		int index = 0;
		List<QuestionAudio> audios = new ArrayList<>();
		for (Question q : qs) {
			index++;
			String oldId = q.getId();
			q.setId(new ObjectId().toString());
			q.setCreateTime(CommonUtils.getCurDateTime());
			q.setUpdateBy(null);
			q.setUpdateDate(null);
			q.setUpdateTime(null);
			q.setScore(1.0);
			q.setCreationBy(dto.getUser().getUserId());
			// 复制试题的音频
			List<QuestionAudio> audioList = questionAudioService.findQuestionAudiosByQuestionId(oldId);
			if (audioList != null && audioList.size() > 0) {
				for (QuestionAudio oldAudio : audioList) {
					QuestionAudio audio = this.copyQuestionAudio(oldAudio, q, dto.getUser());
					q.addAudio(audio);
				}
				audios.addAll(q.getAudioList());
			}
			//设置音频新路径
			dealQuestionAudios(q);
			//替换试题音频id
			updateQuestionBody(q, q.getAudioList());
			PaperDetailUnit unit = new PaperDetailUnit();
			units.add(unit);
			unit.setPaper(paper);
			unit.setNumber(index);
			unit.setScore(1.0);
			unit.setPaperDetail(detail);
			unit.setQuestionType(q.getQuestionType());
			unit.setQuestion(q);
			unit.setCreator(dto.getUser().getDisplayName());
			unit.setCreateTime(CommonUtils.getCurDateTime());
			unit.setCreationBy(dto.getUser().getUserId());
			unit.setPaperType(PaperType.IMPORT);
		}
		quesRepo.saveAll(qs);
		if (audios.size() > 0) {
			questionAudioRepo.saveAll(audios);
		}
		paperDetailUnitRepo.saveAll(units);
	}

	private Course of(CourseBean c) {
		Course ret = new Course();
		ret.setCode(c.getCode());
		ret.setEnable(c.getEnable() == null ? null : c.getEnable().toString());
		ret.setId(c.getId().toString());
		ret.setLevel(c.getLevel());
		ret.setName(c.getName());
		ret.setOrgId(c.getRootOrgId().toString());
		return ret;
	}

	private List<Question> checkQuestion(List<Question> dtos) {
		List<Question> ret = new ArrayList<>();
		Set<String> checkIds = new HashSet<>();
		Map<String, String> extractTextMap = new HashMap<>();
		for (Question dto : dtos) {
			String text = getExtractText(dto);
			if (StringUtils.isBlank(text)) {
				checkIds.add(dto.getId());
				ret.add(dto);
			} else {
				extractTextMap.put(dto.getId(), text);
			}
		}
		int length = dtos.size();
		for (int i = 0; i < length; i++) {
			Question dto = dtos.get(i);
			if (checkIds.contains(dto.getId())) {
				continue;
			}
			ret.add(dto);
			String quesText1 = extractTextMap.get(dto.getId());
			for (int j = i + 1; j < length; j++) {
				Question subdto = dtos.get(j);
				if (checkIds.contains(subdto.getId())) {
					continue;
				}
				String quesText2 = extractTextMap.get(subdto.getId());
				double similarity = StringSimilarityUtils.getSimilarityWithCosinesBySeg(quesText1, quesText2);
				if (similarity > 0.94) {
					checkIds.add(dto.getId());
					checkIds.add(subdto.getId());
				}
			}
		}
		return ret;
	}

	private String getExtractText(Question question) {
		StringBuilder quesText = new StringBuilder();
		if (question == null) {
			return quesText.toString();
		}

		if (!StringUtils.isEmpty(question.getQuesBody())) {
			quesText.append(getTextInHtml(question.getQuesBody()));
		}

		List<QuesOption> quesOptionList = question.getQuesOptions();
		if (quesOptionList != null && quesOptionList.size() > 0) {
			for (QuesOption quesOption : quesOptionList) {
				if (!StringUtils.isEmpty(quesOption.getOptionBody())) {
					quesText.append(getTextInHtml(quesOption.getOptionBody()));
				}
			}
		}

		return quesText.toString();
	}

	private String getTextInHtml(String htmlStr) {
		htmlStr = htmlStr.replaceAll("\\&[a-zA-Z]{1,10};", "").trim();
		if (!htmlStr.startsWith("<p>")) {
			return htmlStr;
		}

		try {
			org.jsoup.nodes.Document doc = Jsoup.parse(htmlStr);
			StringBuilder textStr = new StringBuilder();
			Elements links = doc.select("p").removeAttr("img");

			for (Element link : links) {
				textStr.append(link.text().trim());
			}

			return textStr.toString();
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	private List<Question> findQuestion(Long rootOrgId, String courseCode) {
		Query query = new Query();
		query.addCriteria(Criteria.where("orgId").is(rootOrgId.toString()));
		query.addCriteria(Criteria.where("course.code").is(courseCode));
		List<Question> ret = this.mongoTemplate.find(query, Question.class, "question");
		return ret;
	}

	/**
	 * 更新试题题干或选项中的音频存储ID
	 */
	private void updateQuestionBody(Question question, List<QuestionAudio> audioList) {
		if(CollectionUtils.isEmpty(audioList)) {
			return;
		}
		Map<String, String> audioMaps = new HashMap<>();
		for (QuestionAudio audio : audioList) {
			audioMaps.put(audio.getFileName(), audio.getId());
		}

		// 在题干html片段中替换音频存储ID
		String body = question.getQuesBody();
		List<String> ids = CommonUtils.getTagANames(body);
		List<String> names = CommonUtils.getTagANames2(body);
		Map<String, String> oldBodyMap = new HashMap<>();
		for (int i = 0; i < ids.size(); i++) {
			oldBodyMap.put(names.get(i), ids.get(i));
		}
		for (String key : oldBodyMap.keySet()) {
			body = body.replace(oldBodyMap.get(key), audioMaps.get(key));
		}
		question.setQuesBody(body);

		if (question.getQuesOptions() == null || question.getQuesOptions().size() == 0) {
			return;
		}

		// 替换选项中的音频存储ID
		for (QuesOption option : question.getQuesOptions()) {
			String newOptionBody = option.getOptionBody();
			List<String> optionIds = CommonUtils.getTagANames(newOptionBody);
			List<String> optionNames = CommonUtils.getTagANames2(newOptionBody);

			Map<String, String> oldOptionMap = new HashMap<>();
			for (int i = 0; i < optionIds.size(); i++) {
				oldOptionMap.put(optionNames.get(i), optionIds.get(i));
			}
			for (String key : oldOptionMap.keySet()) {
				newOptionBody = newOptionBody.replace(oldOptionMap.get(key), audioMaps.get(key));
			}
			option.setOptionBody(newOptionBody);
		}
	}

	/**
	 * 处理试题的音频(下载上传到云存储)
	 */
	private void dealQuestionAudios(Question question) {
		String copyAudioPath = UpYunService.TEMP_FILE_EXP + File.separator + randomUUID();
		File copyAudioDir = new File(copyAudioPath);
		if (!copyAudioDir.exists()) {
			copyAudioDir.mkdirs();
		}
		List<QuestionAudio> audioList = question.getAudioList();
		if (audioList == null || audioList.size() == 0) {
			return;
		}
		for (QuestionAudio audio : audioList) {
			// 定义文件下载名称，并下载音频文件
			String newAudioFileName = randomUUID() + "_" + audio.getFileName();
			File audioFile = new File(copyAudioPath + File.separator + newAudioFileName);

//                    upYun.readFile(audio.getFileUrl(), audioFile);
			// 通用存储
			FileStorageUtil.saveUrlAs(FileStorageUtil.realPath(audio.getFileUrl()), audioFile);

			// 重新上传新的音频文件
			String newPath = sysProperty.getRadioUploadPath() + newAudioFileName;
			try {
//                        upYun.writeFile(newPath, audioFile, true);
				// 通用存储
				FileStoragePathEnvInfo env = new FileStoragePathEnvInfo();
				env.setRelativePath(newPath);
				YunPathInfo pi = FileStorageUtil.saveFile("audioFile", env, audioFile, null);
				audio.setFileUrl(pi.getRelativePath());// 设置新路径
			} catch (Exception e) {
				throw new StatusException("上传音频文件失败！");
			} finally {
				IoUtils.removeFile(audioFile);
			}
		}

		IoUtils.removeFile(copyAudioDir);
	}

	/**
	 * 复制试题的音频
	 */
	private QuestionAudio copyQuestionAudio(QuestionAudio oldAudio, Question q, User user) {
		QuestionAudio audio = new QuestionAudio();
		audio.setId(new ObjectId().toString());
		audio.setQuestionId(q.getId());
		audio.setFileUrl(oldAudio.getFileUrl());
		audio.setFileName(oldAudio.getFileName());
		audio.setFileSuffixes(oldAudio.getFileSuffixes());
		audio.setCreateTime(new Date());
		audio.setCreateUser(user.getDisplayName());
		audio.setCreationBy(user.getUserId());
		audio.setUpdateBy(null);
		audio.setUpdateDate(null);
		return audio;
	}

	private String randomUUID() {
		return UUID.randomUUID().toString().replaceAll("-", "");
	}
}
