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

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

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
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 org.springframework.transaction.interceptor.TransactionAspectSupport;
import org.springframework.web.multipart.MultipartFile;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;

import cn.com.qmth.examcloud.api.commons.security.bean.User;
import cn.com.qmth.examcloud.api.commons.security.bean.UserDataRule;
import cn.com.qmth.examcloud.commons.exception.StatusException;
import cn.com.qmth.examcloud.commons.helpers.poi.ExcelReader;
import cn.com.qmth.examcloud.commons.util.PathUtil;
import cn.com.qmth.examcloud.core.basic.api.CourseCloudService;
import cn.com.qmth.examcloud.core.basic.api.bean.CourseBean;
import cn.com.qmth.examcloud.core.basic.api.request.GetCourseReq;
import cn.com.qmth.examcloud.core.basic.api.response.GetCourseResp;
import cn.com.qmth.examcloud.core.questions.base.Constants;
import cn.com.qmth.examcloud.core.questions.base.Model;
import cn.com.qmth.examcloud.core.questions.base.converter.utils.FileUtil;
import cn.com.qmth.examcloud.core.questions.dao.CoursePropertyRepo;
import cn.com.qmth.examcloud.core.questions.dao.PropertyRepo;
import cn.com.qmth.examcloud.core.questions.dao.entity.Course;
import cn.com.qmth.examcloud.core.questions.dao.entity.CourseProperty;
import cn.com.qmth.examcloud.core.questions.dao.entity.Property;
import cn.com.qmth.examcloud.core.questions.dao.entity.dto.CoursePropertyDto;
import cn.com.qmth.examcloud.core.questions.service.CoursePropertyService;
import cn.com.qmth.examcloud.core.questions.service.bean.CoursePropertyImportInfo;
import cn.com.qmth.examcloud.support.cache.CacheHelper;
import cn.com.qmth.examcloud.support.cache.bean.CourseCacheBean;
import cn.com.qmth.examcloud.web.config.SystemProperties;

/**
 * @author weiwenhai
 * @describle 课程属性
 * @date 2017.11.2
 */
@Service("coursePropertyService")
public class CoursePropertyServiceImpl implements CoursePropertyService {

	private static final Logger log = LoggerFactory.getLogger(CoursePropertyServiceImpl.class);
	
	private static final String[] EXCEL_HEADER = new String[] { "课程代码", "课程名称", "属性名称", "一级属性编号", "一级属性内容", "二级属性编号",
	"二级属性内容" };

    @Autowired
    private CoursePropertyRepo coursePropertyRepo;
    
	@Autowired
	private PropertyRepo propertyRepo;
    
    @Autowired
    private CourseCloudService courseCloudService;

    @Autowired
    private MongoTemplate mongoTemplate;
    
    @Autowired
    private SystemProperties systemProperties;

    @Override
    public List<CourseProperty> findAllByOrgId(Long orgId,UserDataRule ud) {
    	if (ud.assertEmptyQueryResult()) {
            return Lists.newArrayList();
        }
    	if (ud.assertNeedQueryRefIds()) {
    		return coursePropertyRepo.findByOrgIdAndCourseIdIn(orgId,ud.getRefIds());
    	}else {
    		return coursePropertyRepo.findByOrgId(orgId);
    	}
    }

    @Override
    public void saveCourseProperty(CoursePropertyDto dto) {
        if (StringUtils.isBlank(dto.getName())) {
            throw new StatusException(Constants.SYS_CODE_400, "属性名不能为空！");
        }

        if (dto.getOrgId() == null) {
            throw new StatusException(Constants.SYS_CODE_400, "所属机构不能为空！");
        }

        if (dto.getCourseId() == null || StringUtils.isBlank(dto.getCourseCode())) {
            throw new StatusException(Constants.SYS_CODE_400, "所属课程不能为空！");
        }

        CourseProperty courseProperty = coursePropertyRepo.findByOrgIdAndName(dto.getOrgId(), dto.getName());
        if (courseProperty != null) {
            throw new StatusException(Constants.SYS_CODE_500, "课程属性名已经存在！");
        }

        if (StringUtils.isBlank(dto.getId())) {
            dto.setId(null);//剔除空字符串ID(导致异常数据)
        }

        courseProperty = new CourseProperty();
        courseProperty.setId(dto.getId());
        courseProperty.setName(dto.getName());
        courseProperty.setOrgId(dto.getOrgId());
        courseProperty.setCourseId(dto.getCourseId());
        courseProperty.setCourseCode(dto.getCourseCode());
        courseProperty.setEnable(true);
        courseProperty.setUpdateTime(new Date());

        coursePropertyRepo.save(courseProperty);
    }

    @Override
    public Page<CourseProperty> findList(CoursePropertyDto dto, Pageable pageable, UserDataRule userDataRule) {
        if (userDataRule.assertEmptyQueryResult()) {
            return Page.empty();
        }

        Query query = new Query();

        if (dto.getOrgId() != null) {
            query.addCriteria(Criteria.where("orgId").is(dto.getOrgId()));
        }

        if (dto.getCourseId() != null) {
            if (userDataRule.assertNeedQueryRefIds()&&!userDataRule.getRefIds().contains(dto.getCourseId())) {
                return Page.empty();
            }
            query.addCriteria(Criteria.where("courseId").is(dto.getCourseId()));
        } else {
            if (userDataRule.assertNeedQueryRefIds()) {
                query.addCriteria(Criteria.where("courseId").in(userDataRule.getRefIds()));
            }
        }

        if (StringUtils.isNotBlank(dto.getName())) {
            query.addCriteria(Criteria.where("name").regex(dto.getName().trim()));
        }

        if (dto.getEnable() != null) {
            query.addCriteria(Criteria.where("enable").is(dto.getEnable()));
        }

        long total = mongoTemplate.count(query, CourseProperty.class);
        if (total == 0) {
            return Page.empty();
        }

        query.with(Sort.by(new Sort.Order(Sort.Direction.DESC, "updateTime")));
        query.skip(pageable.getOffset());
        query.limit(pageable.getPageSize());
        List<CourseProperty> courseProperties = mongoTemplate.find(query, CourseProperty.class);
        if (CollectionUtils.isEmpty(courseProperties)) {
            return Page.empty();
        }

        for (CourseProperty courseProperty : courseProperties) {
            CourseCacheBean course = CacheHelper.getCourse(courseProperty.getCourseId());
            if (course != null) {
                courseProperty.setCourseName(course.getName());
            }
        }

        return new PageImpl<>(courseProperties, pageable, total);
    }

    @Override
    public List<CourseProperty> findAllByCourseId(Long courseId) {
        return coursePropertyRepo.findByCourseId(courseId);
    }

    @Override
    public List<CourseProperty> findAllByCourseCode(String courseCode) {
        return coursePropertyRepo.findByCourseCode(courseCode);
    }

    @Override
    public List<CourseProperty> findByEnable(String courseCode, Boolean enable,User user,UserDataRule ud) {
        
        if (ud.assertEmptyQueryResult()) {
			return new ArrayList<>();
		}
		Query query = new Query();
		if (ud.assertNeedQueryRefIds()) {
			query.addCriteria(Criteria.where("courseId").in(ud.getRefIds()));
		}
		query.addCriteria(Criteria.where("orgId").is(user.getRootOrgId()));
		query.addCriteria(Criteria.where("courseCode").is(courseCode));
		
		query.addCriteria(Criteria.where("enable").is(true));
		List<CourseProperty> paperList = this.mongoTemplate.find(query, CourseProperty.class);
		return paperList;
    }

    @Override
    public void updateCoursePropertyStatus(List<String> coursePropertyIds, Boolean enable) {
        if (CollectionUtils.isEmpty(coursePropertyIds)) {
            return;
        }

        List<CourseProperty> courseProperties = coursePropertyRepo.findByIdIn(coursePropertyIds);
        if (CollectionUtils.isEmpty(courseProperties)) {
            return;
        }

        for (CourseProperty courseProperty : courseProperties) {
            courseProperty.setEnable(enable);
            courseProperty.setUpdateTime(new Date());
        }

        coursePropertyRepo.saveAll(courseProperties);
    }

    @Override
    public void updateCoursePropertyStatus(String coursePropertyId, Boolean enable) {
        CourseProperty courseProperty = Model.of(coursePropertyRepo.findById(coursePropertyId));
        if (courseProperty == null) {
            throw new StatusException(Constants.SYS_CODE_500, "课程属性已不存在！");
        }

        courseProperty.setEnable(enable);
        courseProperty.setUpdateTime(new Date());
        coursePropertyRepo.save(courseProperty);
    }
    
    @Transactional
	@Override
	public List<Map<String, Object>> importCourseProperty(UserDataRule ud, User user, Long rootOrgId, MultipartFile dataFile) {
    	File file = new File(systemProperties.getTempDataDir() + File.separator + UUID.randomUUID() + ".xlsx");
        try {
            file.createNewFile();
            dataFile.transferTo(file);
            return disposeImportCourseProperty(ud, user, rootOrgId, file);
        } catch (Exception e) {
            throw new StatusException("500", "导入课程属性失败:" + e.getMessage(), e);
        } finally {
            FileUtil.deleteFile(file.getAbsolutePath());
        }
    }
    
	private List<Map<String, Object>> disposeImportCourseProperty(UserDataRule ud, User user, Long rootOrgId, File file) {
		List<String[]> lineList = null;
		try {
			lineList = ExcelReader.readSheetBySax(PathUtil.getCanonicalPath(file), 1, 7);
		} catch (Exception e) {
			throw new StatusException("100110", "Excel 解析失败",e );
		}

		if (CollectionUtils.isEmpty(lineList)) {
			throw new StatusException("100111", "Excel无内容");
		}

		if (10001 < lineList.size()) {
			throw new StatusException("100112", "数据行数不能超过10000");
		}

		List<Map<String, Object>> failRecords = Collections.synchronizedList(new ArrayList<Map<String, Object>>());

		List<CoursePropertyImportInfo> courseList = Lists.newArrayList();
		Map<String, Course> cousreMap = new HashMap<>();
		for (int i = 0; i < lineList.size(); i++) {
			String[] line = lineList.get(i);
			if (0 == i) {
				if (headerError(line)) {
					throw new StatusException("100111", "Excel表头错误");
				}
				continue;
			}

			boolean hasError = false;
			StringBuilder msg = new StringBuilder();

			CoursePropertyImportInfo courseInfo = new CoursePropertyImportInfo();

			String courseCode = trimAndNullIfBlank(line[0]);
			if (StringUtils.isBlank(courseCode)) {
				msg.append("  课程代码不能为空");
				hasError = true;
			} else if (!checkCourseExist(cousreMap, courseCode, rootOrgId)) {
				msg.append("  课程代码不存在");
				hasError = true;
			} else if ("false".equals(cousreMap.get(courseCode).getEnable())) {
				msg.append("  课程已禁用");
				hasError = true;
			}
			
			courseInfo.setCourseCode(courseCode);
			
			if(cousreMap.get(courseCode)!=null) {
				courseInfo.setCourseId(Long.valueOf(cousreMap.get(courseCode).getId()));
			}

			String propertyName = trimAndNullIfBlank(line[2]);
			if (StringUtils.isBlank(propertyName)) {
				msg.append("  属性名称不能为空");
				hasError = true;
			} else if (propertyName.length() > 100) {
				msg.append("  属性名称不能超过100个字符");
				hasError = true;
			}
			courseInfo.setPropertyName(propertyName);

			String firstCode = trimAndNullIfBlank(line[3]);
			if (StringUtils.isBlank(firstCode)) {
				msg.append("  一级属性编号不能为空");
				hasError = true;
			} else if (firstCode.length() > 100) {
				msg.append("  一级属性编号不能超过100个字符");
				hasError = true;
			}
			courseInfo.setFirstPropertyCode(firstCode);

			String firstName = trimAndNullIfBlank(line[4]);
			if (StringUtils.isNotBlank(firstName) && firstName.length() > 100) {
				msg.append("  一级属性内容不能超过100个字符");
				hasError = true;
			}
			courseInfo.setFirstPropertyName(firstName);

			String secCode = trimAndNullIfBlank(line[5]);
			if (StringUtils.isNotBlank(secCode) && secCode.length() > 100) {
				msg.append("  二级属性编号不能超过100个字符");
				hasError = true;
			}
			courseInfo.setSecondPropertyCode(secCode);

			String secName = trimAndNullIfBlank(line[6]);
			if (StringUtils.isNotBlank(secName) && secName.length() > 100) {
				msg.append("  二级属性内容不能超过100个字符");
				hasError = true;
			}
			courseInfo.setSecondPropertyName(secName);

			if (hasError) {
				failRecords.add(newError(i + 1, msg.toString()));
			} else {
				courseList.add(courseInfo);
			}

		}

		if (CollectionUtils.isNotEmpty(failRecords)) {
			return failRecords;
		}

		for (int i = 0; i < courseList.size(); i++) {
			CoursePropertyImportInfo cur = courseList.get(i);
			try {
				saveCoursePropertyInfo(user, ud, cur);
			} catch (StatusException e) {
				failRecords.add(newError(i + 2, e.getDesc()));
			} catch (Exception e) {
				failRecords.add(newError(i + 2, "系统异常"));
				log.error("课程属性导入系统异常", e);
			}
		}
		if (CollectionUtils.isNotEmpty(failRecords)) {
			TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
		}

		return failRecords;
	}

	private void saveCoursePropertyInfo(User user, UserDataRule ud, CoursePropertyImportInfo cur) {
		if(ud.assertEmptyQueryResult()) {
			throw new StatusException(" 课程不在该账号负责的课程中");
		}
		if (ud.assertNeedQueryRefIds() && !ud.getRefIds().contains(cur.getCourseId())) {
			throw new StatusException(" 课程不在该账号负责的课程中");
		}
		CourseProperty c = coursePropertyRepo.findByOrgIdAndCourseIdAndName(user.getRootOrgId(),cur.getCourseId(), cur.getPropertyName());
		if(c==null) {
			c=new CourseProperty();
			c.setCourseId(cur.getCourseId());
			c.setCourseCode(cur.getCourseCode());
			c.setName(cur.getPropertyName());
			c.setOrgId(user.getRootOrgId());
			c.setEnable(true);
		}
		c.setUpdateTime(new Date());
		coursePropertyRepo.save(c);
		
		Property first=propertyRepo.findByOrgIdAndCoursePropertyIdAndParentIdAndCode(user.getRootOrgId(), c.getId(), Property.ROOT_PARENT_ID, cur.getFirstPropertyCode());
		if(first!=null) {
			first.setName(cur.getFirstPropertyName());
		}else {
			if(StringUtils.isBlank(cur.getFirstPropertyName())) {
				throw new StatusException(" 一级属性内容不能为空");
			}
			first=new Property();
			first.setParentId(Property.ROOT_PARENT_ID);
			first.setOrgId(user.getRootOrgId());
			first.setCoursePropertyId(c.getId());
			first.setCode(cur.getFirstPropertyCode());
			first.setName(cur.getFirstPropertyName());
			Integer number = 0;
            List<Property> parentProperties = propertyRepo.findByOrgIdAndCoursePropertyIdAndParentIdOrderByNumber(first.getOrgId(), first.getCoursePropertyId(), first.getParentId());
            if (parentProperties != null && parentProperties.size() > 0) {
                number = parentProperties.get(parentProperties.size() - 1).getNumber();
            }

            first.setNumber(number + 1);
		}
		propertyRepo.save(first);
		
		if(StringUtils.isNotBlank(cur.getSecondPropertyCode())&&StringUtils.isBlank(cur.getSecondPropertyName())) {
			throw new StatusException(" 二级属性内容不能为空");
		}
		if(StringUtils.isNotBlank(cur.getSecondPropertyCode())){
			Property sec=propertyRepo.findByOrgIdAndCoursePropertyIdAndParentIdAndCode(user.getRootOrgId(), c.getId(), first.getId(), cur.getSecondPropertyCode());
			if(sec!=null) {
				sec.setName(cur.getSecondPropertyName());
			}else {
				sec=new Property();
				sec.setParentId(first.getId());
				sec.setOrgId(user.getRootOrgId());
				sec.setCoursePropertyId(c.getId());
				sec.setCode(cur.getSecondPropertyCode());
				sec.setName(cur.getSecondPropertyName());
				Integer number = 0;
	            List<Property> parentProperties = propertyRepo.findByOrgIdAndCoursePropertyIdAndParentIdOrderByNumber(sec.getOrgId(), sec.getCoursePropertyId(), sec.getParentId());
	            if (parentProperties != null && parentProperties.size() > 0) {
	                number = parentProperties.get(parentProperties.size() - 1).getNumber();
	            }

	            sec.setNumber(number + 1);
			}
			propertyRepo.save(sec);
		}
		
	}

	private boolean checkCourseExist(Map<String, Course> cousre, String courseCode, Long rootOrgId) {
		if (cousre.get(courseCode) != null) {
			return true;
		}
		GetCourseReq req=new GetCourseReq();
		req.setRootOrgId(rootOrgId);
		req.setCode(courseCode);
		GetCourseResp res=courseCloudService.getCourse(req);
		if (res.getCourseBean() == null) {
			return false;
		} else {
			cousre.put(courseCode, of(res.getCourseBean()));
			return true;
		}
	}
	
	private Course of(CourseBean b) {
		Course c=new Course();
		c.setCode(b.getCode());
		c.setEnable(b.getEnable()!=null?null:b.getEnable().toString());
		c.setId(b.getId()+"");
		c.setLevel(b.getLevel());
		c.setName(b.getName());
		c.setOrgId(b.getRootOrgId()+"");
		return c;
	}

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

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

	/**
	 * 方法注释
	 *
	 * @param header
	 * @author WANGWEI
	 */
	private boolean headerError(String[] header) {
		for (int i = 0; i < EXCEL_HEADER.length; i++) {
			if (null == header[i]) {
				return true;
			}
			if (!EXCEL_HEADER[i].equals(header[i].trim())) {
				return true;
			}
		}
		return false;
	}

	@Override
	public List<CoursePropertyImportInfo> exportCourseProperty(UserDataRule userDataRule, User user, String name, Long courseId) {
		if (userDataRule.assertEmptyQueryResult()) {
            return Lists.newArrayList();
        }

        Query query = new Query();

        query.addCriteria(Criteria.where("orgId").is(user.getRootOrgId()));

        if (courseId != null) {
            if (!userDataRule.getRefIds().contains(courseId)) {
                return Lists.newArrayList();
            }
            query.addCriteria(Criteria.where("courseId").is(courseId));
        } else {
            if (userDataRule.assertNeedQueryRefIds()) {
                query.addCriteria(Criteria.where("courseId").in(userDataRule.getRefIds()));
            }
        }

        if (StringUtils.isNotBlank(name)) {
            query.addCriteria(Criteria.where("name").regex(name.trim()));
        }


        query.with(Sort.by(new Sort.Order(Sort.Direction.DESC, "updateTime")));
        List<CourseProperty> coursePros = mongoTemplate.find(query, CourseProperty.class);

		List<CoursePropertyImportInfo> ret=new ArrayList<>();
		if(!CollectionUtils.isEmpty(coursePros)) {
			for(CourseProperty cp:coursePros) {
				fillProperty(ret, cp);
			}
		}
		return ret;
	}
	
	private void fillProperty(List<CoursePropertyImportInfo> ret,CourseProperty cp) {
		List<Property> ps=propertyRepo.findByCoursePropertyIdOrderByNumber(cp.getId());
		if(!CollectionUtils.isEmpty(ps)) {
			Map<String,List<Property>> secPs=new HashMap<>();
			for(Property p:ps) {
				if(!Property.ROOT_PARENT_ID.equals(p.getParentId())) {
					List<Property> list=secPs.get(p.getParentId());
					if(list==null) {
						list=new ArrayList<>();
						secPs.put(p.getParentId(), list);
					}
					list.add(p);
				}
			}
			
			for(Property p:ps) {
				if(Property.ROOT_PARENT_ID.equals(p.getParentId())) {
					CoursePropertyImportInfo info=new CoursePropertyImportInfo();
					info.setCourseCode(cp.getCourseCode());
					info.setCourseName(CacheHelper.getCourse(cp.getCourseId()).getName());
					info.setPropertyName(cp.getName());
					info.setFirstPropertyCode(p.getCode());
					info.setFirstPropertyName(p.getName());
					ret.add(info);
					List<Property> secPsList=secPs.get(p.getId());
					if(!CollectionUtils.isEmpty(secPsList)) {
						for(Property sec:secPsList) {
							CoursePropertyImportInfo secinfo=new CoursePropertyImportInfo();
							secinfo.setCourseCode(cp.getCourseCode());
							secinfo.setCourseName(CacheHelper.getCourse(cp.getCourseId()).getName());
							secinfo.setPropertyName(cp.getName());
							secinfo.setFirstPropertyCode(p.getCode());
							secinfo.setFirstPropertyName(p.getName());
							secinfo.setSecondPropertyCode(sec.getCode());
							secinfo.setSecondPropertyName(sec.getName());
							ret.add(secinfo);
						}
					}
				}
			}
		}
	}

}