package cn.com.ty.lift.system.user.service.impl;

import cn.com.ty.lift.common.aliservice.constants.AliConstants;
import cn.com.ty.lift.common.constants.ApiConstants;
import cn.com.ty.lift.common.utils.ProjectUtils;
import cn.com.ty.lift.system.settings.dao.entity.MaintenanceCompany;
import cn.com.ty.lift.system.settings.service.IMaintenanceCompanyService;
import cn.com.ty.lift.system.user.dao.entity.Menu;
import cn.com.ty.lift.system.user.dao.entity.Role;
import cn.com.ty.lift.system.user.dao.entity.UserAccount;
import cn.com.ty.lift.system.user.dao.entity.UserRole;
import cn.com.ty.lift.system.user.dao.entity.model.UserRequest;
import cn.com.ty.lift.system.user.dao.entity.model.UserResponse;
import cn.com.ty.lift.system.user.service.*;
import cn.com.ty.lift.system.utils.PasswordUtils;
import cn.com.xwy.boot.web.dto.RestResponse;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.session.data.redis.RedisOperationsSessionRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.interceptor.TransactionAspectSupport;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @author huangyuan
 * @date 2019-11-29
 * @description 用户登录接口
 */
@Service
public class LoginService implements ILoginService {
    @Resource
    private RedisOperationsSessionRepository sessionRepository;

    @Resource
    private IUserService userService;

    @Resource
    private IUserAccountService userAccountService;

    @Resource
    private RedisTemplate redisTemplate;

    @Resource
    private IUserLoginService userLoginService;

    @Resource
    private IRoleService roleService;

    @Resource
    private IUserRoleService userRoleService;

    @Resource
    private IRoleMenuService roleMenuService;

    @Resource
    private IMaintenanceCompanyService maintenanceCompanyService;

    @Resource
    private IMenuService menuService;

    @Resource
    private IMtCompanyUserService mtCompanyUserService;

    @Override
    public RestResponse register(HttpServletRequest request, UserRequest userRequest) {
        //判断手机号是否为空
        if (StringUtils.isBlank(userRequest.getMobile())) {
            return RestResponse.fail(ApiConstants.RESULT_ERROR, "手机号为空");
        }
        //判断密码是否为空
        if (StringUtils.isBlank(userRequest.getPassword())) {
            return RestResponse.fail(ApiConstants.RESULT_ERROR, "密码为空");
        }
        //校验短信验证码
        RestResponse verifyResult = verifySmsCode(userRequest.getMobile(), userRequest.getInputSmsCode());
        //如果结果不为空说明校验未通过
        if (verifyResult != null) {
            return verifyResult;
        }
        //判断手机号是否注册过
        UserAccount userAccount = userAccountService.getByMobile(userRequest.getMobile());
        if (userAccount != null) {
            return RestResponse.fail(ApiConstants.RESULT_ERROR, "用户手机号已经注册过了");
        }

        //保存用户信息,并返回用户账户信息
        userAccount = userService.saveUserInfo(userRequest);
        if (userAccount != null) {
            return RestResponse.success(null, ApiConstants.RESULT_SUCCESS, "注册成功");
        }
        return RestResponse.fail(ApiConstants.RESULT_ERROR, "注册失败");
    }

    @Override
    public RestResponse login(HttpServletRequest request, UserRequest userRequest) {
        String password = userRequest.getPassword();
        //登录端类型
        Integer mobileType = userRequest.getMobileType();
        if (StringUtils.isBlank(userRequest.getMobile())) {
            return RestResponse.fail(ApiConstants.RESULT_ERROR, "手机号为空无法登录");
        }
        //校验用户是否已经登录
//        Object userInfo = getLoginUserInfoFromRedis(userRequest);
//        if (userInfo != null) {
//            return RestResponse.fail(ApiConstants.RESULT_ERROR, "账号已经登录无法重复登录");
//        }
        if (StringUtils.isBlank(password)) {
            return RestResponse.fail(ApiConstants.RESULT_ERROR, "用户密码为空无法登录");
        }
        //通过手机号获取用户账户信息
        UserAccount userAccount = userAccountService.getByMobile(userRequest.getMobile());
        //验证手机号是否存在
        if (userAccount == null) {
            return RestResponse.fail(ApiConstants.RESULT_ERROR, "手机号尚未注册") ;
        }
        //判断用户是否为物管端用户
        int userType = userAccount.getType() != null ? userAccount.getType() : ApiConstants.UserConstants.TYPE_USER;
        if (ApiConstants.UserConstants.PUBLIC_TYPE_USER == userType) {
            //物管端用户，查看登录端
            if (ApiConstants.ACCESS_TYPE_APPLETS == mobileType
                    || ApiConstants.ACCESS_TYPE_PC == mobileType) {
                return RestResponse.fail(ApiConstants.RESULT_ERROR, "没有登录权限");
            }
        }
        //校验密码是否正确
        String inputPassword = PasswordUtils.generatePassword(password, userAccount.getSalt());
        if (!inputPassword.equals(userAccount.getPassword())) {
            return RestResponse.fail(ApiConstants.RESULT_ERROR, "密码输入有误");
        }
        userRequest.setUserId(userAccount.getUserId());
        //登录成功后初始化登录信息
        UserResponse userResponse = initLoginInfo(request, userRequest);
        //添加登录记录
        if (!userLoginService.saveLoginInfo(request, userAccount.getUserId())) {
            return RestResponse.fail(ApiConstants.RESULT_ERROR, "保存登录记录失败");
        }
        if (appLoginSaveDeviceInfo(userRequest, mobileType, userAccount)) {
            return RestResponse.fail(ApiConstants.RESULT_ERROR, "更新设备类型失败");
        }
        return RestResponse.success(userResponse, ApiConstants.RESULT_SUCCESS, "登录成功");
    }

    /**
     * @return redis中用户的信息
     * @description
     * @date 2020/4/22 3:35 下午
     */
    public Object getLoginUserInfoFromRedis(UserRequest userRequest) {
        Object mobileUserInfo = getUserInfoKeyInRedis(userRequest);
        //获取电话是否在redis中存在
        return redisTemplate.opsForValue().get(mobileUserInfo);
    }

    @Override
    public RestResponse resetPassword(UserRequest userRequest, HttpSession session) {
        RestResponse verifyResult = verifySmsCode(userRequest.getMobile(), userRequest.getInputSmsCode());
        //如果结果不为空说明校验未通过
        if (verifyResult != null) {
            return verifyResult;
        }
        //获取用户信息
        UserAccount userAccount = userAccountService.getByMobile(userRequest.getMobile());
        if (userAccount == null) {
            return RestResponse.fail(ApiConstants.RESULT_ERROR, "手机号尚未注册");
        }
        String salt = userAccount.getSalt();
        //新密码加密
        String newPassword = PasswordUtils.generatePassword(userRequest.getPassword(), salt);
        userAccount.setPassword(newPassword);
        userAccountService.updateById(userAccount);
        this.blankingUserInfoIntoRedis(userRequest.getMobile(), userRequest.getToken(), session);
        return RestResponse.success(null, ApiConstants.RESULT_SUCCESS, "重置密码成功");
    }

    @Override
    public RestResponse changeTeam(HttpServletRequest request, UserRequest userRequest) {
        UserResponse userResponse = new UserResponse();
        userResponse.setUserId(userRequest.getUserId());
        //设置token信息
        userResponse.setToken(userRequest.getToken());
        //校验用户信息，并设置用户信息
        RestResponse restResponse = checkAndSetChangeInfo(userRequest, userResponse);
        //如果校验信息不为空返回校验信息
        if (restResponse != null) {
            return restResponse;
        }
        //更新redis中的信息数据
        updateUserInfoInRedis(userRequest, userResponse);
        //在session中设置切换团队后的公司id
        request.getSession().setAttribute(ApiConstants.CURRENT_COMPANY_ID, userRequest.getCompanyId());
        mtCompanyUserService.updateLoginCompanyFlag(userRequest.getUserId(), userRequest.getCompanyId());
        return RestResponse.success(userResponse, ApiConstants.RESULT_SUCCESS, "切换团队成功");
    }

    @Override
    public RestResponse wxLogin(HttpServletRequest request, UserRequest userRequest) {
        //通过授权码获取用户openId
        String openId = userRequest.getWeChatOpenId();
        UserAccount userAccount = userAccountService.getOne(new QueryWrapper<UserAccount>()
                .eq("wechat_open_id", openId)
        );
        if (userAccount == null) {
            return RestResponse.fail(ApiConstants.RESULT_ERROR, "未绑定微信号，请登录绑定微信号");
        }
        //用户存在，关联登录成功
        //登录成功初始化登录信息
        userRequest.setUserId(userAccount.getUserId());
        UserResponse userResponse = initLoginInfo(request, userRequest);
        //保存登录记录
        userLoginService.saveLoginInfo(request, userAccount.getUserId());
        if (appLoginSaveDeviceInfo(userRequest, userRequest.getMobileType(), userAccount)) {
            return RestResponse.fail(ApiConstants.RESULT_ERROR, "更新设备类型失败");
        }
        return RestResponse.success(userResponse, ApiConstants.RESULT_SUCCESS, "用户登录成功");
    }

    @Override
    public RestResponse appletLogin(HttpServletRequest request, UserRequest userRequest) {
        //通过授权码获取用户openId
        String openId = userRequest.getWeChatOpenId();
        UserAccount userAccount = userAccountService.getOne(new QueryWrapper<UserAccount>()
                .eq("applet_open_id", openId)
        );
        if (userAccount == null) {
            return RestResponse.success(openId, ApiConstants.RESULT_NO_DATA, "用户尚未绑定用户信息");
        }
        userRequest.setMobile(userAccount.getMobile());
        UserResponse userResponse = userService.getAppletUserInfo(userAccount.getUserId());
        if (userResponse == null) {
            return RestResponse.fail(ApiConstants.RESULT_ERROR, "访问权限不足");
        }
        appletLoginHandle(request, userRequest, userAccount, userResponse);
        return RestResponse.success(userResponse, ApiConstants.RESULT_SUCCESS, "登录成功");
    }

    @Override
    public RestResponse bindMobile(HttpServletRequest request, UserRequest userRequest) {
        //校验手机号和短信验证码
        RestResponse restResponse = checkMobileAndSmsCode(userRequest);
        if (restResponse != null) {
            return restResponse;
        }
        UserAccount userAccount = userAccountService.getByMobile(userRequest.getMobile());
        if (userAccount == null) {
            return RestResponse.fail(ApiConstants.RESULT_ERROR, "用户信息为空无法登录");
        }
        //获取openId
        userAccount.setAppletOpenId(userRequest.getAppletOpenId());
        UserResponse userResponse = userService.getAppletUserInfo(userAccount.getUserId());
        if (userResponse == null) {
            return RestResponse.fail(ApiConstants.RESULT_ERROR, "访问权限不足");
        }
        appletLoginHandle(request, userRequest, userAccount, userResponse);
        return RestResponse.success(userResponse, ApiConstants.RESULT_SUCCESS, "绑定成功");
    }

    @Override
    public RestResponse appBindMobile(UserRequest userRequest) {
        //通过授权码获取用户openId
        String openId = userRequest.getWeChatOpenId();
        UserAccount oldBindUser = userAccountService.getOne(new QueryWrapper<UserAccount>().eq(
                "wechat_open_id", openId
        ));
        if (oldBindUser != null) {
            //解除上一个手机的绑定信息
            if (!userAccountService.update(Wrappers.<UserAccount>update().set("wechat_open_id", null))) {
                return RestResponse.fail(ApiConstants.RESULT_ERROR, "解除原来绑定失败");
            }
        }
        //获取当前用户id
        UserAccount userAccount = userAccountService.getByUserId(userRequest.getUserId());
        if (userAccount == null) {
            return RestResponse.fail(ApiConstants.RESULT_ERROR, "用户不存在无法绑定");
        }
        userAccount.setWechatOpenId(openId);
        if (!userAccountService.updateById(userAccount)) {
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            return RestResponse.fail(ApiConstants.RESULT_ERROR, "绑定微信失败");
        }
        return RestResponse.success(null, ApiConstants.RESULT_SUCCESS, "绑定微信成功");
    }

    private RestResponse checkMobileAndSmsCode(UserRequest userRequest) {
        if (StringUtils.isBlank(userRequest.getMobile())) {
            return RestResponse.fail(ApiConstants.RESULT_ERROR, "手机号为空无法绑定");
        }
        return verifySmsCode(userRequest.getMobile(), userRequest.getInputSmsCode());
    }

    @Override
    public RestResponse verifySmsCode(String mobile, String inputSmsCode) {
        Object smsCode = redisTemplate.opsForValue().get(mobile + AliConstants.SmsConstants.SMS_CODE_FIELD);
        if (smsCode != null) {
            if (!(smsCode.equals(inputSmsCode))) {
                return RestResponse.fail(ApiConstants.RESULT_ERROR, "短信验证码输入有误");
            }
        } else {
            return RestResponse.fail(ApiConstants.RESULT_ERROR, "验证码过期，请重新获取验证码");
        }
        return null;
    }

    @Override
    public void blankingUserInfoIntoRedis(String mobile, String token, HttpSession session) {
        String mobileUserInfo = mobile + ApiConstants.UserConstants.USER_INFO_MOBILE;
        sessionRepository.deleteById(session.getId());
        if (StringUtils.isNotBlank(token)) {
            redisTemplate.delete(token);
        }
        redisTemplate.delete(mobileUserInfo + ApiConstants.ACCESS_TYPE_PC);
        redisTemplate.delete(mobileUserInfo + ApiConstants.ACCESS_TYPE_APP);
        redisTemplate.delete(mobileUserInfo + ApiConstants.ACCESS_TYPE_PUBLIC);
        redisTemplate.delete(mobileUserInfo + ApiConstants.ACCESS_TYPE_APPLETS);
    }

    /**
     * @param
     * @return
     * @description 初始化登录信息
     * @date 2019-12-30 11:22
     */
    private UserResponse initLoginInfo(HttpServletRequest request, UserRequest userRequest) {
        HttpSession session = request.getSession();
        //获取用户信息
        UserResponse userResponse = userService.getLoginUserInfo(userRequest.getUserId());
        //将当前用户id放入session中
        session.setAttribute(ApiConstants.CURRENT_USER_ID, userResponse.getUserId());
        //将当前公司id放入session中
        if (userResponse.getMaintenanceCompany() != null) {
            session.setAttribute(ApiConstants.CURRENT_COMPANY_ID, userResponse.getMaintenanceCompany().getId());
        }
        //封装登录用户信息
        Map<String, Object> currentUserInfoMap = getUserInfoMap(userResponse);
        //设置token为sessionId
        userResponse.setToken(session.getId());
        String mobileUserInfo = getUserInfoKeyInRedis(userRequest);
        putUserInfoToRedis(userRequest, userResponse, currentUserInfoMap, mobileUserInfo);
        return userResponse;
    }

    /**
     * @param userResponse 用户信息
     * @return map 用户信息map
     * @description 封装放入redis的用户信息
     * @date 2020-01-16 11:40
     */
    private Map<String, Object> getUserInfoMap(UserResponse userResponse) {
        //封装用户登录信息，并将登录信息放入redis中
        Map<String, Object> currentUserInfoMap = new HashMap<>();
        //当前用户id
        currentUserInfoMap.put(ApiConstants.CURRENT_USER_ID, userResponse.getUserId());
        //如果用户类型为空，设置用户类型为普通类型
        int userType = ApiConstants.UserConstants.TYPE_USER;
        if (userResponse.getType() != null) {
            userType = userResponse.getType();
        }
        //当前用户信息
        currentUserInfoMap.put(ApiConstants.CURRENT_USER_TYPE, userType);
        //当前公司id
        MaintenanceCompany maintenanceCompany = userResponse.getMaintenanceCompany();
        if (maintenanceCompany != null) {
            currentUserInfoMap.put(ApiConstants.CURRENT_COMPANY_ID, maintenanceCompany.getId());
        } else {
            currentUserInfoMap.put(ApiConstants.CURRENT_COMPANY_ID, 0L);
        }
        //当前角色id
        Role role = userResponse.getRole();
        if (role != null) {
            currentUserInfoMap.put(ApiConstants.CURRENT_ROLE_ID, role.getId());
            currentUserInfoMap.put(ApiConstants.CURRENT_ROLE_CODE, role.getCode());
        }
        //用户权限信息
        String permissionUrl = menuService.menuToUrl(ProjectUtils.deepCopy(userResponse.getMenus()));
        currentUserInfoMap.put(ApiConstants.CURRENT_PERMISSION_URL, permissionUrl);
        return currentUserInfoMap;
    }

    /**
     * @param userRequest  请求参数
     * @param userResponse 用户信息
     * @return
     * @description 检验并设置更换团队中角色的信息
     * @date 2020/2/26 1:53 下午
     */
    private RestResponse checkAndSetChangeInfo(UserRequest userRequest, UserResponse userResponse) {
        //校验团队信息
        MaintenanceCompany maintenanceCompany = maintenanceCompanyService.getById(userRequest.getCompanyId());
        if (maintenanceCompany == null) {
            return RestResponse.fail(ApiConstants.RESULT_ERROR, "要切换的团队不存在");
        }
        maintenanceCompany.setCurrentTeamFlag(true);
        userResponse.setMaintenanceCompany(maintenanceCompany);
        //获取用户在切换团队的角色信息
        UserRole userRole = userRoleService.getOne(new QueryWrapper<UserRole>()
                .eq("company_id", userRequest.getCompanyId())
                .eq("user_id", userRequest.getUserId())
        );
        //设置角色菜单信息
        if (userRole != null) {
            Role role = roleService.getById(userRole.getRoleId());
            if (role != null) {
                userResponse.setRole(role);
                //获取角色对应的菜单信息
                List<Menu> menuTree = roleMenuService.getMenuTree(role.getId());
                userResponse.setMenus(menuTree);
            }
        }
        return null;
    }

    /**
     * @description 将用户信息放入redis中
     * @date 2020/4/22 6:09 下午
     */
    private void putUserInfoToRedis(UserRequest userRequest, UserResponse userResponse,
                                    Map<String, Object> currentUserInfoMap, String mobileUserInfo) {
        if (ApiConstants.ACCESS_TYPE_APP == userRequest.getMobileType()) {
            //app端 将token对应用户手机号放入redis中,设置过期时间为30天
            putUserInfoToRedis(userResponse.getToken(), mobileUserInfo, currentUserInfoMap, TimeUnit.DAYS);
        } else {
            //其他端 将token对应用户手机号放入redis中,设置过期时间为30分钟
            putUserInfoToRedis(userResponse.getToken(), mobileUserInfo, currentUserInfoMap, TimeUnit.MINUTES);
        }
    }

    /**
     * @param token          token
     * @param mobileUserInfo 用户键
     * @param userInfoMap    用户信息
     * @param timeUnit       持续时间
     * @description 将用户信息放入redis中
     * @date 2020/4/22 5:54 下午
     */
    private void putUserInfoToRedis(String token, String mobileUserInfo,
                                    Map<String, Object> userInfoMap, TimeUnit timeUnit) {
        //token放入redis中
        redisTemplate.opsForValue().set(token, mobileUserInfo, 30L, timeUnit);
        //重新将用户信息放入到redis中
        redisTemplate.opsForValue().set(mobileUserInfo, JSONUtil.toJsonPrettyStr(userInfoMap), 30L, timeUnit);
    }

    /**
     * @param
     * @return
     * @description 更新用户在redis中的信息
     * @date 2020/2/26 12:52 下午
     */
    private void updateUserInfoInRedis(UserRequest userRequest, UserResponse userResponse) {
        //从redis中获取用户信息
        String mobileUserInfo = (String) redisTemplate.opsForValue().get(userResponse.getToken());
        Map<String, Object> userInfoMap = JSONUtil.parseObj(redisTemplate.opsForValue().get(mobileUserInfo));
        if (userResponse.getRole() != null) {
            //设置用户角色id
            userInfoMap.put(ApiConstants.CURRENT_ROLE_ID, userResponse.getRole().getId());
            //设置角色编码信息
            userInfoMap.put(ApiConstants.CURRENT_ROLE_CODE, userResponse.getRole().getCode());
        }
        //设置用户公司id
        userInfoMap.put(ApiConstants.CURRENT_COMPANY_ID, userResponse.getMaintenanceCompany().getId());
        //设置用户菜单信息
        userInfoMap.put(ApiConstants.CURRENT_PERMISSION_URL, menuService.menuToUrl(ProjectUtils
                .deepCopy(userResponse.getMenus())));
        putUserInfoToRedis(userRequest, userResponse, userInfoMap, mobileUserInfo);
    }

    /**
     * @param userRequest 请求数据
     * @return redis中对应key
     * @description
     * @date 2020/4/22 4:21 下午
     */
    private String getUserInfoKeyInRedis(UserRequest userRequest) {
        return userRequest.getMobile() + ApiConstants.UserConstants.USER_INFO_MOBILE + userRequest.getMobileType();
    }

    /**
     * @description 小程序登录成功后处理
     * @date 2020/4/27 3:10 下午
     */
    private void appletLoginHandle(HttpServletRequest request, UserRequest userRequest,
                                   UserAccount userAccount, UserResponse userResponse) {
        //封装登录用户信息
        Map<String, Object> currentUserInfoMap = getUserInfoMap(userResponse);
        String mobileUserInfo = getUserInfoKeyInRedis(userRequest);
        //设置token
        userResponse.setToken(request.getSession().getId());
        //将用户信息放入redis中
        putUserInfoToRedis(userRequest, userResponse, currentUserInfoMap, mobileUserInfo);
        setUserInfoToSession(request, userResponse);
        //添加登录记录
        userLoginService.saveLoginInfo(request, userAccount.getUserId());
        //更新用户信息
        userAccountService.updateById(userAccount);
    }

    /**
     * @param request 请求
     * @description 将用户信息放入session
     * @date 2020/4/27 3:08 下午
     */
    private void setUserInfoToSession(HttpServletRequest request, UserResponse userResponse) {
        //将用户信息放入session中
        HttpSession session = request.getSession();
        session.setAttribute(ApiConstants.CURRENT_USER_ID, userResponse.getUserId());
        if (userResponse.getMaintenanceCompany() != null) {
            session.setAttribute(ApiConstants.CURRENT_COMPANY_ID, userResponse.getMaintenanceCompany().getId());
        }
        if (userResponse.getRole() != null) {
            session.setAttribute(ApiConstants.CURRENT_ROLE_ID, userResponse.getRole().getId());
            session.setAttribute(ApiConstants.CURRENT_ROLE_CODE, userResponse.getRole().getCode());
        }
    }

    /**
     * @param userRequest 请求参数
     * @return
     * @description app登录后保存设备信息
     * @date 2020/5/7 1:53 下午
     */
    private boolean appLoginSaveDeviceInfo(UserRequest userRequest, Integer mobileType, UserAccount userAccount) {
        //将登录设备类型更新到数据库
        if (ApiConstants.ACCESS_TYPE_APP == mobileType) {
            //设备类型: 1.Android 2:IOS
            userAccount.setDeviceModel(userRequest.getDeviceModel());
            //设备唯一标识
            userAccount.setDeviceFlag(userRequest.getDeviceFlag());
            if (!userAccountService.updateById(userAccount)) {
                TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
                return true;
            }
        }
        return false;
    }

}
