package com.ningdatech.kqapi.user.manage;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ningdatech.basic.exception.BizException;
import com.ningdatech.basic.model.PageVo;
import com.ningdatech.basic.util.CollUtils;
import com.ningdatech.kqapi.common.constant.BizConst;
import com.ningdatech.kqapi.security.constant.AuthTypeEnum;
import com.ningdatech.kqapi.security.model.UserInfoDetails;
import com.ningdatech.kqapi.user.model.entity.UserAuth;
import com.ningdatech.kqapi.user.model.entity.UserInfo;
import com.ningdatech.kqapi.user.model.enumerization.AccountStatus;
import com.ningdatech.kqapi.user.model.enumerization.RoleEnum;
import com.ningdatech.kqapi.user.model.po.ChangeAccountStatusReq;
import com.ningdatech.kqapi.user.model.po.ModifyPasswordReq;
import com.ningdatech.kqapi.user.model.po.SaveUserReq;
import com.ningdatech.kqapi.user.model.po.UserListReq;
import com.ningdatech.kqapi.user.model.vo.LoginUserDetailVO;
import com.ningdatech.kqapi.user.model.vo.UserDetailVO;
import com.ningdatech.kqapi.user.model.vo.UserListVO;
import com.ningdatech.kqapi.user.service.IUserAuthService;
import com.ningdatech.kqapi.user.service.IUserInfoService;
import com.ningdatech.kqapi.user.util.BizUtils;
import com.ningdatech.kqapi.user.util.LoginUserUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.session.data.redis.RedisIndexedSessionRepository;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;


/**
 * @author liuxinxin
 * @date 2022/11/23 下午5:04
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class UserInfoManage {

    private final IUserInfoService userInfoService;
    private final IUserAuthService userAuthService;
    private final PasswordEncoder passwordEncoder;
    private final RedisIndexedSessionRepository redisIndexedSessionRepository;

    private static final String PASSWORD_MODIFY_DENY = "您暂无修改该用户密码的权限！";


    @Transactional(rollbackFor = Exception.class)
    public synchronized void changeAccountStatus(ChangeAccountStatusReq req) {
        UserInfo userInfo = userInfoService.getById(req.getUserId());
        if (userInfo == null || req.getAccountStatus().equals(userInfo.getAccountStatus())) {
            return;
        }
        Wrapper<UserInfo> update = Wrappers.lambdaUpdate(UserInfo.class)
                .set(UserInfo::getAccountStatus, req.getAccountStatus())
                .eq(UserInfo::getId, req.getUserId());
        userInfoService.update(update);
        // 账号禁用退出登录
        if (AccountStatus.DISABLE.equals(req.getAccountStatus())) {
            kickOff(req.getUserId());
        }
    }

    @Transactional(rollbackFor = Exception.class)
    public synchronized void saveUser(SaveUserReq req) {
        Long userId = req.getUserId();
        UserInfo oldUser;
        if (userId == null) {
            oldUser = null;
            BizUtils.password(req.getPassword());
        } else {
            if (StrUtil.isNotBlank(req.getPassword())) {
                BizUtils.password(req.getPassword());
            }
            oldUser = userInfoService.getById(userId);
        }
        // 校验 账号是否已经使用
        validateAuthIdentifier(req.getUserName(), userId);
        validateAuthIdentifier(req.getPhoneNo(), userId);
        // 新建
        UserInfo entity = new UserInfo();
        entity.setId(userId);
        entity.setMobile(req.getPhoneNo());
        entity.setRealName(req.getRealName());
        entity.setUsername(req.getUserName());
        entity.setAvatarFileId(req.getAvatarFileId());
        entity.setAccountStatus(AccountStatus.ENABLE);
        if (userId == null) {
            entity.setRole(RoleEnum.USER.name());
        }
        userInfoService.saveOrUpdate(entity);
        if (userId == null) {
            saveUserAuth(req, entity);
        } else {
            modifyAccount(req, oldUser, userId);
        }
    }

    private void modifyAccount(SaveUserReq req, UserInfo oldUser, Long userId) {
        // 用户ID不为空时修改账号
        String password = null;
        if (StrUtil.isNotBlank(req.getPassword())) {
            password = passwordEncoder.encode(req.getPassword());
        }
        if (!oldUser.getMobile().equals(req.getPhoneNo())) {
            Wrapper<UserAuth> phoneAuth = Wrappers.lambdaUpdate(UserAuth.class)
                    .set(UserAuth::getIdentifier, req.getPhoneNo())
                    .set(password != null, UserAuth::getCredential, password)
                    .eq(UserAuth::getUserId, userId)
                    .eq(UserAuth::getAuthType, AuthTypeEnum.PHONE_PASSWORD);
            userAuthService.update(phoneAuth);
        }
        if (!oldUser.getUsername().equals(req.getUserName())) {
            Wrapper<UserAuth> accountAuth = Wrappers.lambdaUpdate(UserAuth.class)
                    .set(UserAuth::getIdentifier, req.getUserName())
                    .set(password != null, UserAuth::getCredential, password)
                    .eq(UserAuth::getUserId, userId)
                    .eq(UserAuth::getAuthType, AuthTypeEnum.ACCOUNT_PASSWORD);
            userAuthService.update(accountAuth);
        }
    }

    private void saveUserAuth(SaveUserReq req, UserInfo entity) {
        String password = passwordEncoder.encode(req.getPassword());
        List<UserAuth> auths = new ArrayList<>();
        UserAuth accountAuth = new UserAuth();
        accountAuth.setAuthType(AuthTypeEnum.ACCOUNT_PASSWORD.name());
        accountAuth.setUserId(entity.getId());
        accountAuth.setIdentifier(entity.getUsername());
        accountAuth.setCredential(password);
        auths.add(accountAuth);
        UserAuth phoneAuth = new UserAuth();
        phoneAuth.setAuthType(AuthTypeEnum.PHONE_PASSWORD.name());
        phoneAuth.setUserId(entity.getId());
        phoneAuth.setIdentifier(entity.getMobile());
        phoneAuth.setCredential(password);
        auths.add(phoneAuth);
        userAuthService.saveBatch(auths);
    }

    public UserDetailVO getUserDetail(Long userId) {
        UserInfo info = userInfoService.getById(userId);
        Assert.notNull(info, "用户不存在");
        return UserDetailVO.builder()
                .userId(info.getId())
                .realName(info.getRealName())
                .userName(info.getUsername())
                .avatarFileId(info.getAvatarFileId())
                .phoneNo(info.getMobile())
                .accountStatus(info.getAccountStatus())
                .build();
    }

    public PageVo<UserListVO> listUser(UserListReq req) {
        // 获取入参
        String realName = req.getRealName();
        // 获取区域列表
        Wrapper<UserInfo> query = Wrappers.lambdaQuery(UserInfo.class)
                .like(StrUtil.isNotBlank(realName), UserInfo::getRealName, realName)
                .eq(StrUtil.isNotBlank(req.getMobile()), UserInfo::getMobile, req.getMobile())
                .orderByDesc(UserInfo::getUpdateOn);
        Page<UserInfo> page = userInfoService.page(req.page(), query);
        if (page.getTotal() == 0) {
            return PageVo.empty();
        }
        List<UserListVO> data = page.getRecords().stream().map(r -> {
            UserListVO res = new UserListVO();
            res.setUserId(r.getId());
            res.setRealName(r.getRealName());
            res.setUserName(r.getUsername());
            res.setCreateTime(r.getCreateOn());
            res.setPhoneNo(r.getMobile());
            res.setAccountStatus(r.getAccountStatus());
            return res;
        }).collect(Collectors.toList());
        return PageVo.of(data, page.getTotal());
    }

    public LoginUserDetailVO loginUserDetail() {
        UserInfoDetails details = LoginUserUtil.loginUserDetail();
        LoginUserDetailVO result = new LoginUserDetailVO();
        result.setUserId(details.getUserId());
        result.setRealName(details.getRealName());
        result.setUserName(details.getIdentifier());
        return result;
    }

    private void validateAuthIdentifier(String identifier, Long userId) {
        Wrapper<UserAuth> query = Wrappers.lambdaQuery(UserAuth.class)
                .eq(UserAuth::getIdentifier, identifier)
                .ne(userId != null, UserAuth::getUserId, userId)
                .last(BizConst.LIMIT_1);
        if (userAuthService.count(query) > 0) {
            throw new BizException("当前账号已经注册");
        }

    }

    /**
     * 踢用户下线
     *
     * @param userId 用户ID
     */
    public void kickOff(Long userId) {
        List<UserAuth> userAuthList = userAuthService.listByUserId(userId);
        List<String> identifierList = CollUtils.fieldList(userAuthList, UserAuth::getIdentifier);
        if (CollectionUtil.isNotEmpty(identifierList)) {
            Set<String> allSessionIdList = new HashSet<>();
            for (String identifier : identifierList) {
                Set<String> sessionIds = redisIndexedSessionRepository.findByPrincipalName(identifier).keySet();
                allSessionIdList.addAll(sessionIds);
            }
            if (!allSessionIdList.isEmpty()) {
                for (String sessionId : allSessionIdList) {
                    redisIndexedSessionRepository.deleteById(sessionId);
                }
            }
        }
    }

    @Transactional(rollbackFor = Exception.class)
    public void deleteUser(Long userId) {
        Assert.isTrue(LoginUserUtil.getUserId().equals(userId), "不能删除当前登录账号信息");
        kickOff(userId);
        userInfoService.removeById(userId);
        userAuthService.removeByUserId(userId);
    }

    public void changePassword(ModifyPasswordReq req) {
        String password = passwordEncoder.encode(req.getPassword());
        Wrapper<UserAuth> update = Wrappers.lambdaUpdate(UserAuth.class)
                .set(UserAuth::getCredential, password)
                .set(UserAuth::getUpdateOn, LocalDateTime.now())
                .eq(UserAuth::getUserId, req.getUserId());
        userAuthService.update(update);
    }

    public void changeSelfPassword(ModifyPasswordReq req) {
        Long userId = LoginUserUtil.getUserId();
        if (!userId.equals(req.getUserId())) {
            throw BizException.wrap(PASSWORD_MODIFY_DENY);
        }
        changePassword(req);
    }


}
