package com.backendsys.modules.system.service.impl;

import cn.hutool.core.convert.Convert;
import com.backendsys.exception.CustException;
import com.backendsys.modules.common.config.redis.utils.RedisUtil;
import com.backendsys.modules.common.config.security.utils.SecurityUtil;
import com.backendsys.modules.common.config.security.utils.TokenUtil;
import com.backendsys.modules.common.utils.MybatisUtil;
import com.backendsys.modules.sse.entity.SseResponse;
import com.backendsys.modules.sse.entity.SseResponseEnum;
import com.backendsys.modules.sse.utils.SseUtil;
import com.backendsys.modules.system.dao.*;
import com.backendsys.modules.system.entity.*;
import com.backendsys.modules.system.service.SysUserIntegralService;
import com.backendsys.modules.system.service.SysUserRoleMenuService;
import com.backendsys.modules.system.service.SysUserService;
import com.backendsys.utils.MD5Util;
import com.backendsys.utils.response.PageEntity;
import com.backendsys.utils.response.PageInfoResult;
import com.backendsys.utils.v2.PageUtils;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Service
public class SysUserServiceImpl extends ServiceImpl<SysUserDao, SysUser> implements SysUserService {

    @Value("${REDIS_LOGIN_TOKEN_PREFIX}")
    private String REDIS_LOGIN_TOKEN_PREFIX;

    @Value("${spring.application.name}")
    private String APPLICATION_NAME;
    @Autowired
    private SseUtil sseUtil;
    @Lazy
    @Autowired
    RedissonClient redissonClient;
    @Autowired
    private RedisUtil redisUtil;
    @Autowired
    private TokenUtil tokenUtil;
    @Autowired
    private SecurityUtil securityUtil;

    @Autowired
    private SysUserDao sysUserDao;
    @Autowired
    private SysUserInfoDao sysUserInfoDao;
    @Autowired
    private SysUserRoleDao sysUserRoleDao;
    @Autowired
    private SysUserRoleMenuService sysUserRoleMenuService;
    @Autowired
    private SysUserRoleRelationDao sysUserRoleRelationDao;
    @Autowired
    private SysUserRolePermissionRelationDao sysUserRolePermissionRelationDao;
    @Autowired
    private SysUserIntegralService sysUserIntegralService;

    /**
     * 获取系统用户列表
     */
    @Override
    public PageEntity selectUserList(SysUserDTO sysUserDTO) {
        PageUtils.startPage();  // 分页
        List<SysUserInfo> list = sysUserInfoDao.selectUserList(sysUserDTO);
        return new PageInfoResult(list).toEntity();
    }

    /**
     * 获取系统用户列表 (在线的)
     */
    @Override
    public PageEntity selectUserOnlineList(SysUserDTO sysUserDTO) {
        PageUtils.startPage();  // 分页
        // 获得 用户最后登录的 tokenUUID
        Collection<String> redisKeys = redisUtil.keys(REDIS_LOGIN_TOKEN_PREFIX + "*");
        List<String> last_login_uuids = redisKeys.stream().map(e -> String.valueOf(e).replace(REDIS_LOGIN_TOKEN_PREFIX, "")).collect(Collectors.toList());
        List<SysUserInfo> list = sysUserInfoDao.selectUserByLastLoginUuids(last_login_uuids);
        return new PageInfoResult(list).toEntity();
    }

    /**
     * 获取系统用户详情
     */
    @Override
    public SysUserInfo selectUserInfo(Long user_id) {

        // 获得 用户账号信息
        SysUser sysUser = sysUserDao.selectOne(new QueryWrapper<SysUser>().eq("id", user_id));
        if (sysUser == null) throw new CustException("用户不存在");

        // 获得 用户基本信息
        SysUserInfo sysUserInfo = sysUserInfoDao.selectOne(new QueryWrapper<SysUserInfo>().eq("user_id", user_id));
        if (sysUserInfo == null) throw new CustException("用户不存在");

        // 用户账号信息 赋值到 用户基本信息
        BeanUtils.copyProperties(sysUser, sysUserInfo);

        // 获得 用户角色
        List<Map<String, Object>> roles = sysUserRoleDao.selectRoleByUserId(user_id);
        sysUserInfo.setRoles(roles);

        // 获得 用户角色Id
        List<Long> role_ids = roles.stream().map(m -> Convert.toLong(m.get("role_id"))).collect(Collectors.toList());
        sysUserInfo.setRole_id(role_ids);

        // 获得 用户角色权限Id (根据角色)
        List<String> permission_ids = sysUserRolePermissionRelationDao.selectUserRolePermissionIdsByRoleIds(role_ids);
        sysUserInfo.setPermission_ids(permission_ids);

        // 获得用户角色菜单 (根据权限)
        List<Map<String, Object>> sysUserRoleMenuList = sysUserRoleMenuService.selectUserRoleMenuList(permission_ids, 1);
        sysUserInfo.setMenus(sysUserRoleMenuList);

        // 获得用户积分 (需要权限)
        Integer integral = sysUserIntegralService.selectIntegralByUserId(user_id);
        sysUserInfo.setIntegral(integral);

        return sysUserInfo;
    }

    /**
     * 获取系统用户详情(简约)
     */
    @Override
    public SysUserInfoSimple selectUserInfoSimple(Long user_id) {
        // 获得 用户账号信息
        SysUser sysUser = sysUserDao.selectOne(new QueryWrapper<SysUser>().eq("id", user_id));
        if (sysUser == null) throw new CustException("用户不存在");

        // 获得 用户基本信息
        SysUserInfo sysUserInfo = sysUserInfoDao.selectOne(new QueryWrapper<SysUserInfo>().eq("user_id", user_id));
        if (sysUserInfo == null) throw new CustException("用户不存在");

        // 获得 用户角色
        List<Map<String, Object>> roles = sysUserRoleDao.selectRoleByUserId(user_id);
        sysUserInfo.setRoles(roles);

        // 获得 用户角色Id
        List<Long> role_ids = roles.stream().map(m -> Convert.toLong(m.get("role_id"))).collect(Collectors.toList());
        sysUserInfo.setRole_id(role_ids);

        // 获得用户积分 (需要权限)
        Integer integral = sysUserIntegralService.selectIntegralByUserId(user_id);
        sysUserInfo.setIntegral(integral);

        // 用户账号信息 赋值到 用户基本信息
        BeanUtils.copyProperties(sysUser, sysUserInfo);

        // 用户基本信息 赋值到 用户基本信息(简约)
        SysUserInfoSimple sysUserInfoSimple = new SysUserInfoSimple();
        BeanUtils.copyProperties(sysUserInfo, sysUserInfoSimple);

        return sysUserInfoSimple;
    }


    /**
     * 获取系统用户权限
     */
    @Override
    public Map<String, Object> selectUserPermission(Long user_id) {

        // 获得当前角色关系(集合)
        List<Long> userRoleIds = sysUserRoleRelationDao.selectUserRoleIds(user_id);

        // 获得当前角色关系(集合) 所对应的权限(集合)
        List<String> rolePermissionIds = new ArrayList<>();
        if (userRoleIds.size() > 0) {
            rolePermissionIds = sysUserRolePermissionRelationDao.selectUserRolePermissionIdsByRoleIds(userRoleIds);
            rolePermissionIds = rolePermissionIds.stream().distinct().collect(Collectors.toList());
        }

        Map<String, Object> resp = new LinkedHashMap<>();
        resp.put("user_id", user_id);
        resp.put("permission_ids", rolePermissionIds);
        return resp;
    }

    /**
     * 获取系统用户菜单
     */
    @Override
    public Map<String, Object> selectUserMenu(Long user_id) {

        // 获取系统用户权限
        Map<String, Object> permissionResp = selectUserPermission(user_id);
        List<String> permission_ids = (List<String>) permissionResp.get("permission_ids");

        // 获取系统用户菜单 (根据权限)
        List<Map<String, Object>> menuList = sysUserRoleMenuService.selectUserRoleMenuList(permission_ids, 1);

        Map<String, Object> resp = new LinkedHashMap<>();
        resp.put("user_id", user_id);
        resp.put("menus", menuList);
        return resp;
    }


    /**
     * 创建系统用户
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Map<String, Object> insertUser(SysUserDTO sysUserDTO) {
        RLock lock = redissonClient.getLock("insertUser");
        try { lock.tryLock(3, TimeUnit.SECONDS);

            String phone = sysUserDTO.getPhone();
            Integer phoneAreaCode = sysUserDTO.getPhone_area_code();
            String username = sysUserDTO.getUsername();

            // [查询] 判断用户名是否存在
            if (username != null) {
                SysUser sysUser1 = sysUserDao.selectOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getUsername, username));
                if (sysUser1 != null) throw new CustException("用户名 (" + username + ") 已存在");
            }

            // [查询] 判断手机号是否存在
            if (phone != null) {
                LambdaQueryWrapper<SysUser> queryWrapper = new LambdaQueryWrapper<>();
                queryWrapper.eq(SysUser::getPhone, phone);
                queryWrapper.eq(SysUser::getPhone_area_code, phoneAreaCode);
                SysUser sysUser2 = sysUserDao.selectOne(queryWrapper);
                if (sysUser2 != null) throw new CustException("手机号码 (+" + phoneAreaCode + " " + phone + ") 已存在");
            }

            // 密码二次加密
            String password = sysUserDTO.getPassword();
            BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
            String encodedPassword = encoder.encode(password);
            sysUserDTO.setPassword(encodedPassword);

            // 创建用户
            sysUserDao.insertUser(sysUserDTO);

            // 初始化用户积分
            sysUserIntegralService.init(sysUserDTO.getId());

            return Map.of("user_id", sysUserDTO.getId());

        } catch (InterruptedException e) { throw new RuntimeException(e);
        } finally { lock.unlock(); }
    }

    /**
     * 编辑系统用户信息
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Map<String, Object> updateUserInfo(SysUserDTO sysUserDTO) {
        RLock lock = redissonClient.getLock("updateUserInfo");
        try { lock.tryLock(3, TimeUnit.SECONDS);

            // 判断记录是否存在
            MybatisUtil.checkExists(sysUserDao, "id", sysUserDTO.getUser_id(), "用户不存在");

            // 判断手机号码是否存在
            if (sysUserDTO.getPhone() != null) {
                SysUser sysUser = sysUserDao.selectOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getPhone, sysUserDTO.getPhone()));
                if (sysUser != null) throw new CustException("手机号码已存在");
            }

            // 当 status 状态为 -1(禁用) 时，同时清除登录状态
            Integer status = sysUserDTO.getStatus();
            if (status != null && status == -1) {
                // 删除旧的登录缓存
                tokenUtil.deleteRedisLoginToken(sysUserDTO.getLast_login_uuid());
                sysUserDTO.setLast_login_uuid("");
            }

            System.out.println("sysUserDTO = " + sysUserDTO);

            sysUserDao.updateUserInfo(sysUserDTO);

            return Map.of("user_id", sysUserDTO.getUser_id());

        } catch (InterruptedException e) { throw new RuntimeException(e);
        } finally { lock.unlock(); }
    }


    /**
     * 编辑系统用户角色绑定
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Map<String, Object> updateUserRoleRelation(SysUserRoleRelation sysUserRoleRelation) {
        RLock lock = redissonClient.getLock("updateUserRoleRelation");
        try { lock.tryLock(3, TimeUnit.SECONDS);

            List<Long> role_ids = sysUserRoleRelation.getRole_ids();
            Long user_id = sysUserRoleRelation.getUser_id();

            // 1.删除全部用户与角色的关系 (sys_user_role_relation)
            LambdaQueryWrapper<SysUserRoleRelation> wrapperRoleRelation = new LambdaQueryWrapper<>();
            wrapperRoleRelation.eq(SysUserRoleRelation::getUser_id, user_id);
            sysUserRoleRelationDao.delete(wrapperRoleRelation);

            // 2.重新添加参数中的角色与权限的关系 (sys_user_role_relation)
            sysUserRoleRelationDao.insertBatch(user_id, role_ids);

            return Map.of("user_id", sysUserRoleRelation.getUser_id());

        } catch (InterruptedException e) { throw new RuntimeException(e);
        } finally { lock.unlock(); }
    }

    /**
     * 编辑系统用户密码
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Map<String, Object> updateUserPassword(SysUserDTO sysUserDTO) {
        RLock lock = redissonClient.getLock("updateUserPassword");
        try { lock.tryLock(3, TimeUnit.SECONDS);

            // 查询用户
            SysUser sysUser = sysUserDao.selectOne(new QueryWrapper<SysUser>().eq("id", sysUserDTO.getUser_id()));
            if (sysUser == null) throw new CustException("原密码不正确");

            BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();

            // [判断] 原密码是否正确
            String old_password_request = sysUserDTO.getOld_password();
            String old_password = sysUser.getPassword();
            if (!encoder.matches(old_password_request, old_password)) {
                throw new CustException("原密码不正确");
            }

            SysUser entity = new SysUser();
            entity.setId(sysUserDTO.getUser_id());

//            // MD5加密
//            String md5Password = MD5Util.encrypt(sysUserDTO.getPassword());

            // 加盐加密 (前端会传已加密过的密码)
            String encodedPassword = encoder.encode(sysUserDTO.getPassword());
            entity.setPassword(encodedPassword);
            System.out.println(entity);

            // [编辑] 用户密码
            sysUserDao.updateById(entity);

            // [SSE] 发送退出登录的消息
            String dataStr = (new SseResponse(SseResponseEnum.LOGOUT)).toJsonStr();
            sseUtil.send(sysUserDTO.getUser_id(), dataStr);

            return Map.of("user_id", sysUserDTO.getUser_id());

        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally { lock.unlock(); }
    }

    /**
     * 重置系统用户密码
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Map<String, Object> resetUserPassword(SysUserDTO sysUserDTO) {
        RLock lock = redissonClient.getLock("resetUserPassword");
        try { lock.tryLock(3, TimeUnit.SECONDS);

            // 生成一个六位的随机数密码
            String uuid = UUID.randomUUID().toString().replace("-", "");
            String password = uuid.substring(0, 6);
            // MD5加密
            String md5Password = MD5Util.encrypt(password);
            // 加盐加密
            BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
            String encodedPassword = encoder.encode(md5Password);
            sysUserDTO.setPassword(encodedPassword);
            //
            SysUser entity = new SysUser();
            entity.setId(sysUserDTO.getUser_id());
            entity.setPassword(encodedPassword);
            System.out.println(entity);

            // [编辑] 用户密码
            sysUserDao.updateById(entity);

            // 要先判断连接存不存在?

            // [SSE] 发送退出登录的消息
            try {
                String dataStr = (new SseResponse(SseResponseEnum.LOGOUT)).toJsonStr();
                sseUtil.send(sysUserDTO.getUser_id(), dataStr);
            } catch (Exception e) {
                System.out.println(e.getMessage());
            }

            Map<String, Object> response = new LinkedHashMap<>();
            response.put("user_id", sysUserDTO.getUser_id());
            response.put("plain_password", password);
            return response;

        } catch (InterruptedException | NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        } finally { lock.unlock(); }
    }


    /**
     * 删除系统用户 (以及部分关联表)
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Map<String, Object> deleteUser(SysUserDTO sysUserDTO) {
        RLock lock = redissonClient.getLock("deleteUser");
        try { lock.tryLock(3, TimeUnit.SECONDS);

            // 单个删除
            Long user_id = sysUserDTO.getUser_id();
            if (user_id != null) {
                sysUserDao.deleteById(user_id);
                sysUserInfoDao.delete(new QueryWrapper<SysUserInfo>().eq("user_id", user_id));
                sysUserRoleRelationDao.delete(new QueryWrapper<SysUserRoleRelation>().eq("user_id", user_id));
                return Map.of("user_id", user_id);
            }

            // 批量删除
            List<Long> user_ids = sysUserDTO.getUser_ids();
            if (user_ids != null && user_ids.size() > 0) {
                sysUserDao.delete(new QueryWrapper<SysUser>().in("id", user_ids));
                sysUserInfoDao.delete(new QueryWrapper<SysUserInfo>().in("user_id", user_ids));
                sysUserRoleRelationDao.delete(new QueryWrapper<SysUserRoleRelation>().in("user_id", user_ids));
                return Map.of("user_ids", user_ids);
            }

            return null;

        } catch (InterruptedException e) { throw new RuntimeException(e);
        } finally { lock.unlock(); }
    }

    /**
     * 踢出系统用户
     */
    @Override
    public Map<String, Object> kickUser(Long user_id) {
        RLock lock = redissonClient.getLock("kickUser");
        try { lock.tryLock(3, TimeUnit.SECONDS);

            // 查询用户信息
            Wrapper queryWrapper = new QueryWrapper<SysUserInfo>().lambda().eq(SysUserInfo::getUser_id, user_id);
            SysUserInfo sysUserInfo = sysUserInfoDao.selectOne(queryWrapper);

            if (sysUserInfo != null) {
                // 删除旧的登录缓存
                tokenUtil.deleteRedisLoginToken(sysUserInfo.getLast_login_uuid());
                // 更新用户信息 (查询最后登录uuid，并清除)
                Wrapper updateWrapper = new UpdateWrapper<SysUserInfo>().lambda().set(SysUserInfo::getLast_login_uuid, "").eq(SysUserInfo::getUser_id, user_id);
                sysUserInfoDao.update(null, updateWrapper);

                // [SSE] 发送退出登录的消息
                String dataStr = (new SseResponse(SseResponseEnum.LOGOUT)).toJsonStr();
                sseUtil.send(sysUserInfo.getUser_id(), dataStr);
            }

            return Map.of("user_id", user_id);

        } catch (InterruptedException e) { throw new RuntimeException(e);
        } finally { lock.unlock(); }
    }


    /**
     * 审核用户
     */
    @Override
    public Map<String, Object> auditUser(SysUserDTO sysUserDTO) {
        RLock lock = redissonClient.getLock("auditUser");
        try { lock.tryLock(3, TimeUnit.SECONDS);

            Long user_id = sysUserDTO.getUser_id();

            SysUserInfo entity = new SysUserInfo();
            entity.setAudit_status(sysUserDTO.getAudit_status());
            entity.setAudit_note(sysUserDTO.getAudit_note());
            sysUserInfoDao.update(entity, new LambdaQueryWrapper<SysUserInfo>().eq(SysUserInfo::getUser_id, user_id));

            return Map.of("user_id", user_id);

        } catch (InterruptedException e) { throw new RuntimeException(e);
        } finally { lock.unlock(); }
    }
}
