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

import cn.quantgroup.tech.db.DSType;
import cn.quantgroup.tech.db.TargetDataSource;
import cn.quantgroup.user.enums.BizType;
import cn.quantgroup.user.enums.IncomeRangeEnum;
import cn.quantgroup.xyqb.Constants;
import cn.quantgroup.xyqb.aspect.lock.RedisLock;
import cn.quantgroup.xyqb.controller.IBaseController;
import cn.quantgroup.xyqb.controller.internal.user.resp.UserFullResp;
import cn.quantgroup.xyqb.entity.*;
import cn.quantgroup.xyqb.event.PhoneNoUpdateEvent;
import cn.quantgroup.xyqb.exception.DataException;
import cn.quantgroup.xyqb.exception.UserNotExistException;
import cn.quantgroup.xyqb.exception.UserRegisterLoginException;
import cn.quantgroup.xyqb.model.*;
import cn.quantgroup.xyqb.repository.*;
import cn.quantgroup.xyqb.service.register.IUserDeregisterService;
import cn.quantgroup.xyqb.service.register.IUserRegisterService;
import cn.quantgroup.xyqb.service.session.ISessionService;
import cn.quantgroup.xyqb.service.user.*;
import cn.quantgroup.xyqb.service.wechat.IWechatService;
import cn.quantgroup.xyqb.util.*;
import com.alibaba.fastjson.JSON;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.sensorsdata.analytics.javasdk.ISensorsAnalytics;
import com.sensorsdata.analytics.javasdk.bean.EventRecord;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Caching;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * Created by Miraculous on 15/7/5.
 */
@Service
@Slf4j
public class UserServiceImpl implements IUserService, IBaseController {

    @Autowired
    private RedisTemplate<String, String> stringRedisTemplate;
    @Autowired
    private IUserRepository userRepository;

    @Autowired
    private IUserHashMappingRepository userHashMappingRepository;

    @Resource
    private IUserHashPhoneNoIdNoMappingRepository userHashPhoneNoIdNoMappingRepository;

    @Autowired
    private IUserDetailService userDetailService;

    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;

    @Resource
    private IUserRegisterService userRegisterService;

    @Autowired
    private IWechatService wechatService;

    @Autowired
    private ISessionService sessionService;

    @Resource
    private IUserExtInfoService userExtInfoService;
    @Resource
    private IAddressService addressService;
    @Resource
    private IContactService contactService;
    @Resource
    private ILockIpv4Service lockIpv4Service;

    @Autowired
    private IOauthLoginInfoService oauthLoginInfoService;

    @Autowired
    private ITenantService tenantService;

    @Autowired
    private IUserDeregisterService userDeregisterService;

    @Autowired
    private IUserAttachedRepository userAttachedRepository;

    @Autowired
    private IUserBtRegisterRepository userBtRegisterRepository;

    @Autowired
    private IUuidPhoneMappingRepository uuidPhoneMappingRepository;

    @Resource
    private IWeChatUserRepository weChatUserRepository;

    @Resource
    private IProductLoginRepository productLoginRepository;

    @Override
//    @Cacheable(value = "usercache", key = "'xyqbuser' + #phone", unless = "#result == null", cacheManager = "cacheManager")
    public User findByPhoneInDb(String phone) {
        User user = userRepository.findByEncryptedPhoneNo(phone);
        if (user == null) {
            user = userRepository.findByPhoneNo(phone);
        }
        return user;
    }

    @Override
    @TargetDataSource(type = DSType.SLAVE)//查询从库
//    @Cacheable(value = "usercache", key = "'xyqbuser' + #phone", unless = "#result == null", cacheManager = "cacheManager")
    public User findSlaveByPhoneInDb(String phone) {
        return userRepository.findByPhoneNo(phone);
    }

    @Override
    @TargetDataSource(type = DSType.SLAVE)//查询从库
    public Map<Long, String> findPhoneByIdsInDb(List<Long> userIds, Integer tenantId) {
        if (CollectionUtils.isEmpty(userIds)) {
            return Maps.newHashMap();
        }
        Map<Long, String> userIdAndPhoneMap = Maps.newHashMap();
        List<User> users = userRepository.findByIdIn(userIds);
        //校验租户ID
        if (!TenantUtil.TENANT_DEFAULT.equals(tenantId)) {
            users = tenantService.selectUsersByTenantId(users, tenantId);
        } else {
            users = tenantService.validationTentIdByTentId(users, tenantId);
        }
        users.forEach(user -> userIdAndPhoneMap.put(user.getId(), user.getPhoneNo()));
        return userIdAndPhoneMap;
    }

    @Override
    public User findByUuidInDb(String uuid) {
        return userRepository.findByUuid(uuid);
    }

    @Override
    public String findUuid(String phoneNo, String idNo) {
        // 优先按手机号查
        if (ValidationUtil.validatePhoneNo(phoneNo)) {
            return userRepository.findUuidByPhoneNo(phoneNo);
        }
        // 按身份证号查
        if (StringUtils.isNotBlank(idNo)) {
            return userRepository.findUuidByIdNo(idNo);
        }
        return null;
    }

    @Override
    @CacheEvict(value = "usercache", key = "'xyqbuser' + #user.phoneNo", cacheManager = "cacheManager")
    public User saveUser(User user) {
        return userRepository.saveAndFlush(user);
    }

    @Override
    public User findById(Long userId) {
        return userRepository.findById(userId);
    }

    @Override
    public List<User> findByPhones(List<String> phones) {
        List<User> userList = userRepository.findAll((root, query, cb) -> {
            query.where(root.get(Constants.ENCRYPTED_PHONE_NO).as(String.class).in(phones));
            return query.getRestriction();
        });
        if (userList.isEmpty()) {
            userList = userRepository.findAll((root, query, cb) -> {
                query.where(root.get(Constants.PHONE_NO).as(String.class).in(phones));
                return query.getRestriction();
            });
        }
        return userList;
    }

    @Override
//    @Cacheable(value = "usercache", key = "'xyqbuser' + #phone", unless = "#result == null", cacheManager = "cacheManager")
    public User findByPhoneWithCache(String phone) {
        User user = userRepository.findByEncryptedPhoneNo(phone);
        if (user == null) {
            user = userRepository.findByPhoneNo(phone);
        }
        return user;
    }

    @Override
//    @Cacheable(value = "usercache", key = "'xyqbuser' + #uuid", unless = "#result == null", cacheManager = "cacheManager")
    public User findByUuidWithCache(String uuid) {
        return userRepository.findByUuid(uuid);
    }

    @Override
    public boolean exist(String phoneNo) {
        return userRepository.findByPhoneNo(phoneNo) != null;
    }

    /**
     * 修改用户密码
     *
     * @param phoneNo
     * @param password
     * @return
     * @date 2017-02-15 修改用户修改密码时，更新updatedAt时间
     */
    @Override
    @CacheEvict(value = "usercache", key = "'xyqbuser' + #phoneNo", cacheManager = "cacheManager")
    public boolean resetPassword(String phoneNo, String password) {
        User user = findByPhoneInDb(phoneNo);
        if (user == null) {
            throw new RuntimeException("用户[" + phoneNo + "]不存在");
        }
        user.setPassword(PasswordUtil.MD5WithSalt(password));
        user = userRepository.save(user);
        stringRedisTemplate.expire("usercache:xyqbuser" + phoneNo, 1L, TimeUnit.MILLISECONDS);
        return PasswordUtil.validatePassword(password, user.getPassword());
    }

    @Override
    public List<User> findRegisterUserByTime(String beginTime, String endTime) {
        return userRepository.findRegisterUserByTime(beginTime, endTime);
    }

    @Override
    @CacheEvict(value = "usercache", key = "'xyqbuser' + #phoneNo", cacheManager = "cacheManager")
    public int forbiddenUser(Boolean enable, String phoneNo) {
        return userRepository.forbiddenUser(enable, phoneNo);
    }


    @Override
    public List<UserInfo> findUserInfosByPhones(List<String> phones) {

        List<User> users = findByPhones(phones);
        if (CollectionUtils.isEmpty(phones)) {
            return Collections.emptyList();
        }

        List<UserDetail> userDetails = userDetailService.findByPhones(phones);

        if (!CollectionUtils.isEmpty(users)) {
            Map<Long, User> userMap = Maps.newHashMapWithExpectedSize(users.size());
            users.forEach(user -> {
                userMap.put(user.getId(), user);
            });

            if (!CollectionUtils.isEmpty(userDetails)) {
                List<UserInfo> userInfos = Lists.newArrayListWithExpectedSize(userDetails.size());
                userDetails.forEach(userDetail -> {
                    UserInfo userInfo = new UserInfo(userMap.get(userDetail.getUserId()), userDetail);
                    userInfos.add(userInfo);
                });
                return userInfos;
            }

        }
        return Collections.emptyList();
    }

    @Override
    public User findByMd5(Integer type, String md5Value) {
        md5Value = md5Value.toLowerCase();
        long value = HashUtil.crc32(md5Value);
        UserHashMapping userHashMapping;
        if (FindByMd5Enum.PHONENO.getType() == type) {
            userHashMapping = userHashMappingRepository.findByPhoneNoMd5ShortAndPhoneNoMd5(value, md5Value);
        } else if (FindByMd5Enum.IDNO.getType() == type) {
            userHashMapping = userHashMappingRepository.findByIdNoMd5ShortAndIdNoMd5(value, md5Value);
        } else {
            userHashMapping = null;
            UserHashPhoneNoIdNoMapping userHashPhoneNoIdNoMapping = userHashPhoneNoIdNoMappingRepository.findFirstByPhoneNoIdNoMd5(md5Value);
            if (userHashPhoneNoIdNoMapping != null) {
                userHashMapping = new UserHashMapping();
                userHashMapping.setUserId(userHashPhoneNoIdNoMapping.getUserId());
            }
        }
        if (userHashMapping == null) {
            return null;
        }
        Long userId = userHashMapping.getUserId();
        return userRepository.findById(userId);
    }

    @Override
    public User findByMd5New(Integer type, String md5Value) {
        md5Value = md5Value.toLowerCase();
        long value = HashUtil.crc32(md5Value);
        UserHashMapping userHashMapping = null;
        if (FindByMd5Enum.PHONENO.getType() == type) {
            userHashMapping = userHashMappingRepository.findByPhoneNoMd5ShortAndPhoneNoMd5(value, md5Value);
        } else if (FindByMd5Enum.IDNO.getType() == type) {
            List<UserHashMapping> userHashMappings = userHashMappingRepository.findByIdNoMd5AndIdNoMd5Short(md5Value, value);
            if (!CollectionUtils.isEmpty(userHashMappings)) {
                //如果多个只返回最新的
                userHashMapping = userHashMappings.stream().max(Comparator.comparing(UserHashMapping::getId)).orElse(null);
            }
        } else {
            userHashMapping = null;
            UserHashPhoneNoIdNoMapping userHashPhoneNoIdNoMapping = userHashPhoneNoIdNoMappingRepository.findFirstByPhoneNoIdNoMd5(md5Value);
            if (userHashPhoneNoIdNoMapping != null) {
                userHashMapping = new UserHashMapping();
                userHashMapping.setUserId(userHashPhoneNoIdNoMapping.getUserId());
            }
        }
        if (userHashMapping == null) {
            return null;
        }
        Long userId = userHashMapping.getUserId();
        return userRepository.findById(userId);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public User modifyPhoneNo(String oldPhoneNo, String newPhoneNo) {
        //1. 判断新手机号是否存在
        User newPhoneUser = userRepository.findByPhoneNo(newPhoneNo);
        if (Objects.nonNull(newPhoneUser)) {
            //新手机号已存在
            throw new DataException("新手机号存在, 用户修改手机号后新手机号注册了。");
        }
        User oldPhoneUser = userRepository.findByPhoneNo(oldPhoneNo);
        if (Objects.isNull(oldPhoneUser)) {
            //这不是扯了.旧手机号不存在.
            throw new DataException("旧手机号不存在, 可能已经修改成功了。");
        }
        //2. 执行修改
        //2.1 修改 user 表
        oldPhoneUser.setPhoneNo(newPhoneNo);
        User user = userRepository.saveAndFlush(oldPhoneUser);

        //2.2 修改 user_detail 表
        UserDetail oldPhoneUserDetail = userDetailService.findByPhoneNo(oldPhoneNo);
        oldPhoneUserDetail.setPhoneNo(newPhoneNo);
        userDetailService.saveUserDetail(oldPhoneUserDetail);

        //3. 发送事件
        PhoneNoUpdateEvent phoneNoUpdateEvent = new PhoneNoUpdateEvent(this, user, oldPhoneNo);
        applicationEventPublisher.publishEvent(phoneNoUpdateEvent);
        return oldPhoneUser;
    }

    @Override
    @Caching(evict = {
            @CacheEvict(value = "usercache", key = "'xyqbuser' + #phoneNo", cacheManager = "cacheManager"),
            @CacheEvict(value = "usercache", key = "'xyqbuser' + #uuid", cacheManager = "cacheManager")
    })
    public void userCacheEvict(String uuid, String phoneNo) {
        log.info("清理用户缓存成功,uuid:{},phoneNo:{}", uuid, phoneNo);
    }

    @Autowired
    private ISensorsAnalytics iSensorsAnalytics;

    @Override
    @RedisLock(prefix = "lock:login:fast:", key = "#this[8]")
    public JsonResult loginFast(Long channelId, String appChannel, Long createdFrom, Long btRegisterChannelId,
                                String dimension, String clickId, HttpServletRequest request, Merchant merchant, String phoneNo, Integer tenantId) {
        Boolean register = false;
        User user = findByPhoneWithCache(phoneNo);
        if (user != null && !user.getEnable()) {
            log.info("用户不存在，或者已经注销，phoneNo:{}", phoneNo);
            return JsonResult.buildErrorStateResult("登录失败", null);
        }
        if (user == null) {
            // Service层会负责发送注册消息到LoanVest
            user = userRegisterService.register(phoneNo, channelId, createdFrom, appChannel, btRegisterChannelId, dimension);
            if (user == null) {
                throw new UserNotExistException("用户未找到");
            }
            //广点通转化注册 - 发送消息 - 方法内过滤
            MqUtils.sendRegisterMessageForGdt(phoneNo, clickId);
            register = true;
        }
        if (!wechatRelateUserIfNecessary(user, request)) {
            return JsonResult.buildErrorStateResult("登录时微信关联失败", null);
        }
        try {
            String scDeviceId = request.getHeader("scDeviceId");
            if (!StringUtils.isEmpty(scDeviceId)) {
                iSensorsAnalytics.trackSignUp(user.getUuid(), scDeviceId);
            }
            String terminal = request.getHeader("terminal");
            String channel = request.getHeader("channel");
            log.info("--------手机号{}-------scDeviceId{},terminal{},channel{},是否注册{}", phoneNo, scDeviceId, terminal, channel, register);
            if (!StringUtils.isEmpty(terminal) && "APP".equals(terminal) || !StringUtils.isEmpty(channel) && ("214".equals(channel) || "217".equals(channel))) {
                if (register) {
                    EventRecord userRecord = EventRecord.builder().setDistinctId(user.getUuid()).isLoginId(Boolean.TRUE)
                            .setEventName("App_RegisterEvent")
                            .build();
                    iSensorsAnalytics.track(userRecord);
                } else {
                    EventRecord userRecord = EventRecord.builder().setDistinctId(user.getUuid()).isLoginId(Boolean.TRUE)
                            .setEventName("App_LoginEvent")
                            .build();
                    iSensorsAnalytics.track(userRecord);
                }
                iSensorsAnalytics.flush();
            } else {
                log.info("老神策埋点{},{}------------", user.getRegisteredFrom(), user.getUuid());
                EventRecord userRecord = EventRecord.builder().setDistinctId(user.getUuid()).isLoginId(Boolean.TRUE)
                        .setEventName("PD_WUXIEC_UserLoginVccorCash")
                        .addProperty("son_channel_id", user.getRegisteredFrom())
                        .addProperty("parent_channel_id", -1L)
                        .addProperty("vcccash_uuid", user.getUuid())
                        .build();
                iSensorsAnalytics.track(userRecord);
                iSensorsAnalytics.flush();
            }
        } catch (Exception e) {
            log.info("神策埋点出现问题", e);
        }

        oauthLoginInfoService.addLoginInfo(user, tenantId);
        LoginProperties loginProperties = new LoginProperties("", 3, channelId, createdFrom, appChannel, merchant.getId(), merchant.getName(), tenantId);
        AuthBean session = sessionService.createSession(user, loginProperties);
        session.setRegister(register);
        lockIpv4Service.unLockPhone(phoneNo);
        return new JsonResult(session);
    }

    /**
     * 如果必要的话，关联用户和微信
     *
     * @param user    - 用户标识
     * @param request - 当前请求
     * @return true - 继续登录，false - 微信关联失败，重新登录
     */
    private boolean wechatRelateUserIfNecessary(User user, HttpServletRequest request) {
        Objects.requireNonNull(request, "无效请求");
        String clientIp = IpUtil.getRemoteIP(request);
        Set<String> paramKeys = request.getParameterMap().keySet();
        boolean ready = paramKeys.contains(Constants.WECHAT_OPEN_ID);
        if (!ready) {
            return true;
        } else if (Objects.isNull(user) || Objects.isNull(user.getId()) || StringUtils.isBlank(request.getParameter(Constants.WECHAT_OPEN_ID))) {
            log.warn("微信关联失败，user:{}, request-Header:{}", user, JSON.toJSONString(getRequestHeaderMap(request)));
            return false;
        }
        Long userId = user.getId();
        String phoneNo = user.getPhoneNo();
        try {
            int rows = wechatService.relateUser(userId, phoneNo, request.getParameter(Constants.WECHAT_OPEN_ID));
            return rows > 0;
        } catch (Exception e) {
            log.error("微信关联失败，user:{}, request-Header:{}", user, JSON.toJSONString(getRequestHeaderMap(request)), e);
        }
        return false;
    }

    /**
     * 查询用户全量信息
     *
     * @param userId 用户id
     * @return
     */
    @Override
    public UserFullResp findUserFullSearchByUserId(Long userId) {
        User user = userRepository.findById(userId);
        UserDetail userDetail = userDetailService.findByUserId(userId);
        UserExtInfo userExtInfo = userExtInfoService.findByUserId(userId);
        Address address = addressService.findByUserId(userId);

        UserFullResp userFullResp = UserFullResp.builder().userId(userId).build();
        if (user != null) {
            userFullResp.setUuid(user.getUuid());
        }
        if (userDetail != null) {
            userFullResp.setPhoneNo(userDetail.getPhoneNo());
            userFullResp.setName(userDetail.getName());
            userFullResp.setIdNo(userDetail.getIdNo());
            userFullResp.setIdType(userDetail.getIdType().ordinal());
            userFullResp.setGender(userDetail.getGender().ordinal());
            userFullResp.setEmail(userDetail.getEmail());
            userFullResp.setQq(userDetail.getQq());
        }
        if (userExtInfo != null) {
            userFullResp.setIncomeType(userExtInfo.getIncomeEnum().ordinal());
            userFullResp.setIncomeRange(userExtInfo.getIncomeRangeEnum() == null ?
                    IncomeRangeEnum.UNKNOWN.ordinal() :
                    userExtInfo.getIncomeRangeEnum().ordinal());
            userFullResp.setOccupation(userExtInfo.getOccupationEnum().ordinal());
            userFullResp.setEducation(userExtInfo.getEducationEnum().ordinal());
            userFullResp.setHasCar(userExtInfo.getHasCar() == null ? 0 : userExtInfo.getHasCar() ? 1 : 0);
            userFullResp.setHasSocialSecurity(userExtInfo.getHasSocialSecurity() == null ? 0 : userExtInfo.getHasSocialSecurity() ? 1 : 0);
            userFullResp.setHasHouse(userExtInfo.getHasHouse() == null ? 0 : userExtInfo.getHasHouse() ? 1 : 0);
            userFullResp.setHasCreditCard(userExtInfo.getHasCreditCard() == null ? 0 : userExtInfo.getHasCreditCard() ? 1 : 0);
            userFullResp.setMarryStatus(userExtInfo.getMarryStatus() != null ? userExtInfo.getMarryStatus().ordinal() : null);
        }
        if (address != null) {
            userFullResp.setProvinceCode(address.getProvinceCode());
            userFullResp.setProvince(address.getProvince());
            userFullResp.setCityCode(address.getCityCode());
            userFullResp.setCity(address.getCity());
            userFullResp.setDistrictCode(address.getDistrictCode());
            userFullResp.setDistrict(address.getDistrict());
            userFullResp.setAddress(address.getAddress());
        }
        List<UserFullResp.Contact> contacts = Lists.newArrayList();
        contactService.findByUserIdAndBizType(userId, BizType.CASH, true)
                .forEach(contact -> {
                    contacts.add(UserFullResp.Contact.builder()
                            .bizType(contact.getBizType())
                            .userId(userId)
                            .name(contact.getName())
                            .mobile(contact.getPhoneNo())
                            .relation(contact.getRelation()
                                    .ordinal())
                            .build());
                });
        userFullResp.setContacts(contacts);
        return userFullResp;
    }

    @Override
    public List<User> findByUuidsOrUserIds(List<String> vals, Integer type, Integer tenantId) {
        if (CollectionUtils.isEmpty(vals)) {
            return Collections.EMPTY_LIST;
        }

        if (type == 1) {//1是userids

            List<Long> collect = vals.stream()
                    .map(Long::valueOf)
                    .collect(Collectors.toList());
            List<User> users = userRepository.findByIdIn(collect);
            if (!tenantId.equals(TenantUtil.TENANT_DEFAULT)) {
                return tenantService.selectUsersByTenantId(users, tenantId);
            } else {
                return tenantService.validationTentIdByTentId(users, tenantId);
            }
        } else { //不是1 就是  uuids
            List<User> users = userRepository.findByUuidIn(vals);
            if (!tenantId.equals(TenantUtil.TENANT_DEFAULT)) {
                return tenantService.selectUsersByTenantId(users, tenantId);
            } else {
                return tenantService.validationTentIdByTentId(users, tenantId);
            }
        }
    }

    @Override
    public void logout(String token) {

        sessionService.deleteSession(token);
    }

    @Override
    public void kdspLogout(Long userId, LoginProperties loginProperties) {
        sessionService.kdspDeleteSession(userId, loginProperties);
    }

    @Override
    public List<User> findAll() {
        return userRepository.findAll();
    }


    @Transactional(rollbackFor = Exception.class)
    @Override
    public void deregister(Long userId) {

        User user = userRepository.findById(userId);

        if (Objects.isNull(user)) {
            throw new UserRegisterLoginException("用户不存在");
        }

        /* 保存用户销户记录 */
        userDeregisterService.save(user);
        /* 删除用户 */
        userRepository.deleteById(userId);

        /* 删除用户附加信息 */
        userAttachedRepository.deleteByUserId(userId);
        /* 删除 user_bt_register 信息 */
        userBtRegisterRepository.deleteByUserId(userId);
        /* 删除用户身份证和手机的hashMapping */
        userHashMappingRepository.deleteByUserId(userId);
        /* 删除用户手机号加身份证hashMapping */
        userHashPhoneNoIdNoMappingRepository.deleteByUserId(userId);
        /* 删除用户uuid和手机号mapping */
        uuidPhoneMappingRepository.deleteByUuid(user.getUuid());

        /* 清空session */
        sessionService.deleteByUserId(user.getId());
        /* 清空缓存 */
        sessionService.deleteUserCatch(user);
        /* 禁用微信 Or 删除?*/
//        wechatService.forbiddenXyqbAndWuxiUserByUserId(user.getId());
        weChatUserRepository.deleteByUserId(userId);

        /* 删除用户租户关联 羊小咩租户*/
        productLoginRepository.deleteTenantAndPhoneNo("qtg", "yxm", user.getPhoneNo());
    }
}
