package cn.quantgroup.xyqb.service.user.impl;

import cn.quantgroup.tech.db.DSType;
import cn.quantgroup.tech.db.TargetDataSource;
import cn.quantgroup.xyqb.Constants;
import cn.quantgroup.xyqb.entity.User;
import cn.quantgroup.xyqb.entity.UserDetail;
import cn.quantgroup.xyqb.event.UserDetailUpdateEvent;
import cn.quantgroup.xyqb.model.Gender;
import cn.quantgroup.xyqb.model.IdCardInfo;
import cn.quantgroup.xyqb.model.IdType;
import cn.quantgroup.xyqb.repository.IUserDetailRepository;
import cn.quantgroup.xyqb.repository.IUserRepository;
import cn.quantgroup.xyqb.service.auth.IIdCardService;
import cn.quantgroup.xyqb.service.user.IUserDetailService;
import cn.quantgroup.xyqb.service.user.vo.UserDetailVO;
import cn.quantgroup.xyqb.util.AddressFilter;
import cn.quantgroup.xyqb.util.ValidationUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.persistence.criteria.Predicate;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;

/**
 * Created by 11 on 2016/12/29.
 */
@Slf4j
@Service
public class UserDetailServiceImpl implements IUserDetailService {
    @Autowired
    private IUserDetailRepository userDetailRepository;
    @Autowired
    private IUserRepository userRepository;
    @Autowired
    private IIdCardService idCardService;
    @Resource
    private ApplicationEventPublisher applicationEventPublisher;
    @Resource
    private JdbcTemplate jdbcTemplate;

    @Override
    public UserDetail findByUserId(Long userId) {
        UserDetail userDetail = userDetailRepository.findByUserId(userId);
        if (null == userDetail) {
            return userDetail;
        }
        userDetail.setEmail(AddressFilter.getEmail(userDetail.getPhoneNo(), userDetail.getEmail()));
        return userDetail;
    }
    
    @Override
    @TargetDataSource(type = DSType.SLAVE)//查询从库
    public UserDetail findSlaveByUserId(Long userId) {
    	UserDetail userDetail = userDetailRepository.findByUserId(userId);
    	if (null == userDetail) {
    		return userDetail;
    	}
    	userDetail.setEmail(AddressFilter.getEmail(userDetail.getPhoneNo(), userDetail.getEmail()));
    	return userDetail;
    }

    @Override
    public UserDetail saveUserDetail(UserDetail userDetail) throws DataIntegrityViolationException {
        String idNo = userDetail.getIdNo();
        if (StringUtils.isNotEmpty(idNo)) {
            userDetail.setIdNo(idNo.toUpperCase());
        }
        UserDetail userDetail1 = userDetailRepository.save(userDetail);
        // 发送实名登记统计消息
        applicationEventPublisher.publishEvent(new UserDetailUpdateEvent(this, userDetail1));
        return userDetail1;
    }

    @Override
    public UserDetail findByPhoneNo(String phoneNo) {
        return userDetailRepository.findByPhoneNo(phoneNo);
    }

    @Override
    @TargetDataSource(type = DSType.SLAVE)//查询从库
    public UserDetail findSlaveByPhoneNo(String phoneNo) {
        return userDetailRepository.findByPhoneNo(phoneNo);
    }

    @Override
    public void updateUserQq(Long userId, String qq) {
        userDetailRepository.updateUserQq(qq, userId);
    }

    @Override
    public void updateUserEmail(Long userId, String email) {
        userDetailRepository.updateUserEmail(email, userId);
    }

    @Override
    public List<UserDetailVO> searchUserDetailList(String name, String phoneNo, String idNo) {
        return jdbcTemplate.query(buildSearchUserDetailListSql(name, phoneNo, idNo), (resultSet, i) -> {
            UserDetailVO userDetailVO = new UserDetailVO();
            userDetailVO.setId(resultSet.getLong("id"));
            userDetailVO.setUserId(resultSet.getLong("user_id"));
            userDetailVO.setPhoneNo(resultSet.getString("phone_no"));
            userDetailVO.setName(resultSet.getString("name"));
            userDetailVO.setIdNo(resultSet.getString("id_no"));
            userDetailVO.setIdType(IdType.values()[resultSet.getInt("id_type")]);
            userDetailVO.setIsAuthenticated(resultSet.getInt("is_authenticated") == 1);
            userDetailVO.setGender(Gender.values()[resultSet.getInt("gender")]);
            userDetailVO.setEmail(resultSet.getString("email"));
            userDetailVO.setQq(resultSet.getString("qq"));
            userDetailVO.setEnable(resultSet.getObject("enable") == null ? null : resultSet.getBoolean("enable"));
            userDetailVO.setCreatedAt(resultSet.getTimestamp("created_at").getTime());
            userDetailVO.setUpdatedAt(resultSet.getTimestamp("updated_at").getTime());
            return userDetailVO;
        });
    }

    private String buildSearchUserDetailListSql(String name, String phoneNo, String idNo) {
        /* 优化两次sql查询耗时问题，示例name=王斌*/
        // TODO: 2019/12/24 by rui 暂未找到jpa data同时满足join，动态传参方式。若日后发现，应优化该段代码。
        /* 高层模块已校验至少必须满足一个条件不为空 */
        StringBuilder sqlBuilder = new StringBuilder("select ud.*, u.enable " +
                " from user_detail ud" +
                " left join user u on (u.id = ud.user_id)" +
                " where");
        if (org.apache.commons.lang3.StringUtils.isNotBlank(name)) {
            sqlBuilder.append(" name = ").append("'").append(name).append("'").append(" and");
        }
        if (org.apache.commons.lang3.StringUtils.isNotBlank(phoneNo)) {
            sqlBuilder.append(" ud.phone_no = ").append("'").append(phoneNo).append("'").append(" and");
        }
        if (org.apache.commons.lang3.StringUtils.isNotBlank(idNo)) {
            sqlBuilder.append(" id_no = ").append("'").append(idNo).append("'").append(" and");
        }
        String sql = sqlBuilder.toString();
        if (sql.endsWith("and")) {
            sql = sql.substring(0, sql.length() - 3);
        }
        sql = sql + " limit 500";
        return sql;
    }

    @Override
    public List<UserDetail> findByUserIdIn(List<Long> userIds) {

        return userDetailRepository.findAll((root, query, cb) -> {
            query.where(root.get("userId").in(userIds));
            return query.getRestriction();
        });

    }

    @Override
    public List<UserDetail> findByPhoneNos(List<String> phoneNos) {
        return userDetailRepository.findAll((root, query, cb) -> {
            query.where(root.get(Constants.PHONE_NO).in(phoneNos));
            return query.getRestriction();
        });
    }

    @Override
    public List<UserDetail> findByIdnos(List<String> idnos) {
        return userDetailRepository.findAll((root, query, cb) -> {
            query.where(root.get("idNo").in(idnos));
            return query.getRestriction();
        });
    }

    @Override
    public List<UserDetail> findByIdNoStartingWith(String idNo) {
        return userDetailRepository.findTop20ByIdNoStartingWith(idNo);
    }

    @Override
    public List<UserDetail> findByPhoneNoStartingWith(String phoneNo) {
        return userDetailRepository.findTop20ByPhoneNoStartingWith(phoneNo);
    }

    private Specification<UserDetail> getSpecification(String name, String phoneNo, String idNo) {
        List<Predicate> list = new ArrayList<>();
        Specification<UserDetail> specification = (root, criteriaQuery, criteriaBuilder) -> {
            if (!StringUtils.isEmpty(name)) {
                list.add(criteriaBuilder.equal(root.get("name").as(String.class), name));
            }
            if (!StringUtils.isEmpty(phoneNo)) {
                list.add(criteriaBuilder.equal(root.get(Constants.PHONE_NO).as(String.class), phoneNo));
            }
            if (!StringUtils.isEmpty(idNo)) {
                list.add(criteriaBuilder.equal(root.get("idNo").as(String.class), idNo));
            }
            Predicate[] p = new Predicate[list.size()];
            return criteriaBuilder.and(list.toArray(p));
        };
        return specification;
    }

    private Specification<UserDetail> valueInSpecification(List<Long> userId, List<String> phoneNo, List<String> idNo) {
        List<Predicate> list = new ArrayList<>();
        Specification<UserDetail> specification = (root, criteriaQuery, criteriaBuilder) -> {
            if (userId != null && userId.size() > 0) {
                criteriaQuery.where(root.get("userId").in(userId));
                list.add(criteriaQuery.getRestriction());
            }
            if (phoneNo != null && phoneNo.size() > 0) {
                criteriaQuery.where(root.get(Constants.PHONE_NO).in(phoneNo));
                list.add(criteriaQuery.getRestriction());
            }
            if (idNo != null && idNo.size() > 0) {
                criteriaQuery.where(root.get("idNo").in(idNo));
                list.add(criteriaQuery.getRestriction());
            }
            Predicate[] p = new Predicate[list.size()];
            return criteriaBuilder.and(list.toArray(p));
        };
        return specification;
    }

    private PageRequest buildPageRequest(int pageNumber, int pagzSize, String sortType) {
        Sort sort = null;
        if (Constants.AUTO_SORT_TYPE.equals(sortType)) {
            sort = new Sort(Sort.Direction.ASC, "userId");
        }

        return new PageRequest(pageNumber - 1, pagzSize, sort);
    }

    @Override
    public Page<UserDetail> getUserDetailsPage(List<Long> userId, List<String> phoneNos, List<String> idNos, int pageNumber, int pageSize,
                                               String sortType) {
        PageRequest pageRequest = buildPageRequest(pageNumber, pageSize, sortType);
        Specification<UserDetail> spec = valueInSpecification(userId, phoneNos, idNos);
        return userDetailRepository.findAll(spec, pageRequest);
    }

    @Override
    public int updateNameAndIdCard(String name, String idNo, String phoneNo) {
        UserDetail userDetail = userDetailRepository.findByPhoneNo(phoneNo);
        if (null != userDetail) {
            String newName = userDetail.getName();
            String newIdNo = userDetail.getIdNo();
            Gender newGender = Gender.UNKNOWN;
            if (ValidationUtil.validateChinese(name)) {
                newName = name;
            }
            if (StringUtils.isNotBlank(idNo)) {
                IdCardInfo idCardInfo = idCardService.getIdCardInfo(idNo);
                if (Objects.nonNull(idCardInfo) && idCardInfo.isValid()) {
                    newIdNo = idCardInfo.getIdNo();
                    newGender = idCardInfo.getGender();
                } else {
                    log.error("用户的身份证错误，phoneNo:{},idNo:{}", phoneNo, idNo);
                }
            }
            if (!Objects.equals(newName, userDetail.getName()) || !Objects.equals(newIdNo, userDetail.getIdNo())) {
                return userDetailRepository.updateNameAndIdNoByPhoneNo(newName, newIdNo, Optional.ofNullable(newGender).orElse(Gender.UNKNOWN).ordinal(), phoneNo);
            }
        }
        return 0;
    }

    @Override
    public List<UserDetail> findByPhones(List<String> phoneNos) {
        List<UserDetail> userDetails = userDetailRepository.findAll((root, query, cb) -> {
            query.where(root.get("phoneNo").as(String.class).in(phoneNos));
            return query.getRestriction();
        });
        return userDetails;
    }

    @Override
    public List<UserDetail> fuzzyQueryByPhoneNoAndIdNo(String phoneNo, String idNo) {
        return userDetailRepository.fuzzyQueryByPhoneNoAndIdNo(phoneNo.concat("%"), idNo.concat("%"));
    }

}
