package cn.com.qmth.stmms.biz.user.service.impl;

import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;

import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import cn.com.qmth.stmms.biz.exam.model.Exam;
import cn.com.qmth.stmms.biz.exam.model.ExamSubject;
import cn.com.qmth.stmms.biz.exam.model.MarkGroup;
import cn.com.qmth.stmms.biz.exam.model.Marker;
import cn.com.qmth.stmms.biz.exam.model.SubjectUser;
import cn.com.qmth.stmms.biz.exam.service.ExamService;
import cn.com.qmth.stmms.biz.exam.service.ExamStudentService;
import cn.com.qmth.stmms.biz.exam.service.ExamSubjectService;
import cn.com.qmth.stmms.biz.exam.service.MarkGroupService;
import cn.com.qmth.stmms.biz.exam.service.MarkerService;
import cn.com.qmth.stmms.biz.exam.service.SubjectUserService;
import cn.com.qmth.stmms.biz.user.dao.UserDao;
import cn.com.qmth.stmms.biz.user.model.User;
import cn.com.qmth.stmms.biz.user.service.UserService;
import cn.com.qmth.stmms.biz.user.service.query.UserSearchQuery;
import cn.com.qmth.stmms.common.enums.Role;
import cn.com.qmth.stmms.common.enums.UserSource;
import cn.com.qmth.stmms.common.utils.EncryptUtils;

/**
 * 用户Service
 *
 * @author LS
 */
@Service("userService")
public class UserServiceImpl implements UserService {

    public static final String USER_PASSWORD = "123456";

    @Autowired
    private UserDao userDao;

    @Autowired
    private SubjectUserService subjectUserService;

    @Autowired
    private MarkerService markerService;

    @Autowired
    private ExamService examService;

    @Autowired
    private MarkGroupService groupService;

    @Autowired
    private ExamSubjectService subjectService;

    @Autowired
    private ExamStudentService studentService;

    @Override
    @Transactional
    public User save(User user) {
        if (user.getId() != null && user.getId() > 0) {
            user.setUpdatedTime(new Date());
        } else {
            user.setCreatedTime(new Date());
        }
        return userDao.save(user);
    }

    @Override
    public User findByLoginName(String loginName) {
        return userDao.findFirstByLoginName(loginName);
    }

    @Override
    public User findByMarkerId(Integer markerId) {
        return userDao.findByMarkerId(markerId);
    }

    @Override
    public User findById(Integer id) {
        return userDao.findOne(id);
    }

    @Override
    public User findSchoolAdmin(int schoolId) {
        UserSearchQuery query = new UserSearchQuery();
        query.setSchoolId(schoolId);
        query.addIncludeRole(Role.SCHOOL_ADMIN);
        query.setPageNumber(1);
        query.setPageSize(1);
        query = findByQuery(query);
        if (query.getCurrentCount() > 0) {
            return query.getResult().get(0);
        } else {
            return null;
        }
    }

    @Override
    public User findSchoolViewer(Integer schoolId) {
        UserSearchQuery query = new UserSearchQuery();
        query.setSchoolId(schoolId);
        query.addIncludeRole(Role.SCHOOL_VIEWER);
        query.setPageNumber(1);
        query.setPageSize(1);
        query = findByQuery(query);
        if (query.getCurrentCount() > 0) {
            return query.getResult().get(0);
        } else {
            return null;
        }
    }

    @Override
    public UserSearchQuery findByQuery(final UserSearchQuery query) {
        if (query.getPageNumber() < 1) {
            query.setPageNumber(1);
        }
        if (query.getPageSize() < 1) {
            query.setPageSize(20);
        }
        query.orderByIdAsc();
        Page<User> result = userDao.findAll(new Specification<User>() {

            @Override
            public Predicate toPredicate(Root<User> root, CriteriaQuery<?> cQuery, CriteriaBuilder cb) {
                List<Predicate> predicates = new LinkedList<Predicate>();
                if (query.getSchoolId() > 0) {
                    predicates.add(cb.equal(root.get("schoolId"), query.getSchoolId()));
                }
                if (StringUtils.isNotBlank(query.getLoginName())) {
                    predicates.add(cb.like(root.get("loginName").as(String.class), query.getLoginName() + "%"));
                }
                if (StringUtils.isNotBlank(query.getName())) {
                    predicates.add(cb.like(root.get("name").as(String.class), query.getName() + "%"));
                }
                if (query.getEnable() != null) {
                    predicates.add(cb.equal(root.get("enable"), query.getEnable()));
                }
                if (query.getRole() != null) {
                    predicates.add(cb.equal(root.get("role"), query.getRole()));
                }
                if (query.getSource() != null) {
                    predicates.add(cb.equal(root.get("source"), query.getSource()));
                }
                if (query.getRelatedAccount() != null) {
                    predicates.add(cb.equal(root.get("relatedAccount"), query.getRelatedAccount()));
                }
                if (query.getIncludeRoles() != null && query.getIncludeRoles().size() > 0) {
                    for (Role role : query.getIncludeRoles()) {
                        predicates.add(cb.equal(root.get("role"), role));
                    }
                }
                return predicates.isEmpty() ? cb.conjunction() : cb.and(predicates.toArray(new Predicate[predicates
                        .size()]));
            }
        }, query);
        if (result != null) {
            query.setTotalCount(result.getTotalElements());
            query.setTotalPage(result.getTotalPages());
            query.setResult(result.getContent());
            query.setCurrentCount(result.getNumberOfElements());
        } else {
            query.setTotalCount(0);
            query.setTotalPage(0);
            query.setCurrentCount(0);
        }
        return query;
    }

    @Override
    @Transactional
    public User login(String loginName, String password, String ip) {
        User user = findByLoginName(loginName);
        if (user != null && user.getPassword().equals(EncryptUtils.md5(password))) {
            user.setLastLoginIp(StringUtils.trimToEmpty(ip));
            user.setLastLoginTime(new Date());
            user.refreshAccessToken();
            return userDao.save(user);
        }
        return null;
    }

    @Override
    @Transactional
    public User externalLogin(Integer schoolId, String account, String name, String password, Role role) {
        User user = externalSaveAndUpdate(schoolId, account, name, password, role);
        user.setLastLoginTime(new Date());
        user.refreshAccessToken();
        return userDao.save(user);
    }

    @Override
    public User externalSaveAndUpdate(Integer schoolId, String account, String name, String password, Role role) {
        User user = userDao.findFirstBySchoolIdAndRelatedAccount(schoolId, account);
        if (user == null) {
            user = new User();
            user.setLoginName(schoolId + "-X-" + account);
            user.setName(account);
            user.setPassword(RandomStringUtils.random(16, true, true));
            user.setRole(role);
            user.setSource(UserSource.EXTERNAL);
            user.setEnable(true);
            user.setSchoolId(schoolId);
            user.setRelatedAccount(account);
            user.setCreatedTime(new Date());
            user.setUpdatedTime(new Date());
        }
        if (StringUtils.isNotBlank(name)) {
            user.setName(name);
        }
        if (StringUtils.isNotBlank(password)) {
            user.setPassword(EncryptUtils.md5(password));
        }
        return userDao.save(user);
    }

    @Override
    public User findByAccount(Integer schoolId, String account) {
        return userDao.findFirstBySchoolIdAndRelatedAccount(schoolId, account);
    }

    @Transactional
    @Override
    public int batchSaveMarker(int examId, Set<String> subjectCodes, Integer number, String password, boolean random) {
        int successNum = 0;
        if (subjectCodes.size() == 0) {
            List<ExamSubject> subjects = subjectService.list(examId);
            for (ExamSubject subject : subjects) {
                successNum = successNum + saveMarkerBySubject(examId, number, password, subject.getCode(), random);
            }
        } else {
            for (String subjectCode : subjectCodes) {
                successNum = successNum + saveMarkerBySubject(examId, number, password, subjectCode, random);
            }
        }
        return successNum;
    }

    private int saveMarkerBySubject(int examId, Integer number, String password, String subjectCode, boolean random) {
        Exam exam = examService.findById(examId);
        List<MarkGroup> groups = groupService.findByExamAndSubject(examId, subjectCode);
        int successNum = 0;
        for (MarkGroup group : groups) {
            List<Marker> saveList = new ArrayList<Marker>();
            String prefix = exam.getSchoolId() + "-" + subjectCode + "-" + group.getNumber() + "-";
            int maxNumber = this.findMaxNumberByLoginNameStart(prefix);
            int createNumber = 1;
            while (saveList.size() < number) {
                String loginName = prefix + (maxNumber + createNumber);
                User user = userDao.findFirstByLoginName(loginName);
                if (user == null) {
                    user = new User();
                    user.setLoginName(loginName);
                    user.setName(loginName);
                    user.setRole(Role.MARKER);
                    user.setSource(UserSource.INTERNAL);
                    user.setEnable(true);
                    user.setSchoolId(exam.getSchoolId());
                    user.setCreatedTime(new Date());
                    if (random) {
                        int i = (int) (Math.random() * 9000);
                        password = String.valueOf(i + 1000);
                        user.setPassword(EncryptUtils.md5(password));
                        user.setRandomPassword(password);
                    } else {
                        if (StringUtils.isNotBlank(password)) {
                            user.setPassword(EncryptUtils.md5(password));
                        } else {
                            user.setPassword(EncryptUtils.md5(USER_PASSWORD));
                        }
                    }
                    userDao.save(user);
                    Marker marker = new Marker();
                    marker.setSubjectCode(subjectCode);
                    marker.setGroupNumber(group.getNumber());
                    marker.setUserId(user.getId());
                    marker.setExamId(examId);
                    marker.setEnable(true);
                    saveList.add(marker);
                    successNum++;
                    createNumber++;
                }
            }
            markerService.batchSave(saveList);
        }
        return successNum;
    }

    @Transactional
    @Override
    public int batchSaveSubjectUser(int examId, Role role, Set<String> subjectCodes, Integer number, String password,
            boolean random) {
        int successNum = 0;
        if (subjectCodes.size() == 0) {
            List<ExamSubject> subjects = subjectService.list(examId);
            for (ExamSubject subject : subjects) {
                successNum = successNum
                        + saveSubjectUserBySubject(examId, number, role, subject.getCode(), password, random);
            }
        } else {
            for (String subjectCode : subjectCodes) {
                successNum = successNum + saveSubjectUserBySubject(examId, number, role, subjectCode, password, random);
            }
        }
        return successNum;
    }

    private int saveSubjectUserBySubject(int examId, Integer number, Role role, String subjectCode, String password,
            boolean random) {
        Exam exam = examService.findById(examId);
        List<SubjectUser> saveList = new ArrayList<SubjectUser>();
        int successNum = 0;
        int createNumber = 1;
        String prefix = exam.getSchoolId() + "-" + subjectCode + "-";
        if (role.equals(Role.INSPECTOR)) {
            prefix = exam.getSchoolId() + "-FH-" + subjectCode + "-";
        }
        int maxNumber = this.findMaxNumberByLoginNameStart(prefix);
        while (saveList.size() < number) {
            String loginName = prefix + (maxNumber + createNumber);
            User user = userDao.findFirstByLoginName(loginName);
            if (user == null) {
                user = new User();
                user.setLoginName(loginName);
                user.setName(loginName);
                if (random) {
                    int i = (int) (Math.random() * 9000);
                    password = String.valueOf(i + 1000);
                    user.setPassword(EncryptUtils.md5(password));
                    user.setRandomPassword(password);
                } else {
                    if (StringUtils.isNotBlank(password)) {
                        user.setPassword(EncryptUtils.md5(password));
                    } else {
                        user.setPassword(EncryptUtils.md5(USER_PASSWORD));
                    }
                }
                user.setRole(role);
                user.setSource(UserSource.INTERNAL);
                user.setEnable(true);
                user.setSchoolId(exam.getSchoolId());
                user.setCreatedTime(new Date());
                user = userDao.save(user);
                SubjectUser subjectUser = new SubjectUser();
                subjectUser.setSubjectCode(subjectCode);
                subjectUser.setUserId(user.getId());
                saveList.add(subjectUser);
                successNum++;
                createNumber++;
            }
        }
        subjectUserService.batchSave(saveList);
        return successNum;
    }

    // @Override
    // public void batchSaveCollegeUser(int examId, Integer number) {
    // Exam exam = examService.findById(examId);
    // List<String> collegeList = studentService.findDistinctCollege(examId);
    // List<SubjectUser> saveList = new ArrayList<SubjectUser>();
    // for (String college : collegeList) {
    // List<String> subjectList =
    // studentService.findDistinctSubjectCodeByCollege(examId, college);
    // for (int i = 0; i < number; i++) {
    // String prefix = exam.getSchoolId() + "-XY-";
    // int maxNumber = this.findMaxNumberByLoginNameStart(prefix);
    // String loginName = prefix + "-" + (maxNumber + i + 1);
    // User user = userDao.findFirstByLoginName(loginName);
    // if (user == null) {
    // user = new User();
    // user.setLoginName(loginName);
    // user.setName(loginName);
    // user.setPassword(EncryptUtils.md5(USER_PASSWORD));
    // user.setRole(Role.COLLEGE_ADMIN);
    // user.setSource(UserSource.INTERNAL);
    // user.setEnable(true);
    // user.setSchoolId(exam.getSchoolId());
    // user.setCreatedTime(new Date());
    // userDao.save(user);
    // }
    // for (String subjectCode : subjectList) {
    // SubjectUser subjectUser =
    // subjectUserService.findBySubjectCodeAndUserId(subjectCode, user.getId());
    // if (subjectUser == null) {
    // subjectUser = new SubjectUser();
    // subjectUser.setSubjectCode(subjectCode);
    // subjectUser.setUserId(user.getId());
    // saveList.add(subjectUser);
    // }
    // }
    // subjectUserService.batchSave(saveList);
    // }
    // }
    //
    // }

    @Override
    public int findMaxNumberByLoginNameStart(String prefix) {
        List<User> list = userDao.findStartWithLoginName(prefix);
        int number = 0;
        for (User user : list) {
            try {
                String[] str = user.getLoginName().split("-");
                int no = Integer.parseInt(str[str.length - 1]);
                if (number < no) {
                    number = no;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return number;
    }

}
