package com.gz.service.system.impl;

import cn.hutool.captcha.CaptchaUtil;
import cn.hutool.captcha.CircleCaptcha;
import cn.hutool.captcha.LineCaptcha;
import cn.hutool.core.codec.Base62;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.tree.Tree;
import cn.hutool.core.lang.tree.TreeNodeConfig;
import cn.hutool.core.lang.tree.TreeUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONObject;
import com.gz.common.DataGlobalVariable;
import com.gz.core.exception.BusinessException;
import com.gz.core.exception.CustomExceptionEnum;
import com.gz.dto.system.AdminDTO;
import com.gz.dto.system.DeptDTO;
import com.gz.dto.system.LogDTO;
import com.gz.dto.system.MenuDTO;
import com.gz.jwt.JwtConfig;
import com.gz.jwt.JwtPayload;
import com.gz.mapper.system.AdminMapper;
import com.gz.mapper.system.DeptMapper;
import com.gz.mapper.system.LogMapper;
import com.gz.mapper.system.MenuMapper;
import com.gz.rvo.system.AdminRVO;
import com.gz.rvo.system.InitialHomeRVO;
import com.gz.rvo.system.LoginRVO;
import com.gz.service.system.AdminService;
import com.gz.service.system.AuthService;
import com.gz.utils.JwtUtils;
import com.gz.utils.PasswordUtils;
import com.gz.utils.RequestUtils;
import com.gz.vo.system.LoginVO;
import com.gz.vo.system.UpdatePasswordVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import sun.net.util.IPAddressUtil;

import javax.annotation.Resource;
import javax.security.auth.spi.LoginModule;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * 身份验证Service
 *
 * @author LiuchangLan
 * @date 2021/1/28 12:55
 */
@Slf4j
@Service
public class AuthServiceImpl implements AuthService {

    @Resource
    private AdminMapper adminMapper;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Resource
    private MenuMapper menuMapper;

    @Resource
    private HttpServletRequest request;

    @Resource
    private LogMapper logMapper;

    @Resource
    private DeptMapper deptMapper;

    @Resource
    private AdminService adminService;


    private Integer getRoleId(String deptId) {
        if (StrUtil.isEmpty(deptId)){
            return null;
        }
        // 查询部门权限
        DeptDTO deptDTO = deptMapper.selectByPrimaryKey(deptId);
        if (deptDTO.getRoleId() == null) {
            // 当前部门未设置权限
            if (deptDTO.getParentId() != null) {
                // 有父级部门  查询父级别部门权限
                return getRoleId(deptDTO.getParentId());
            } else {
                // 无父级部门
                return deptDTO.getRoleId();
            }
        } else {
            return deptDTO.getRoleId();
        }
    }

    @Override
    public LoginRVO login(LoginVO vo) {
        // 后台存储的验证码
        String serverCaptcha = String.valueOf(request.getSession().getAttribute("captcha"));
        if (!vo.getCaptcha().equals(serverCaptcha)) {
            throw new BusinessException(CustomExceptionEnum.CAPTCHA_ERROR);
        }
        // 查询用户参数
        AdminDTO param = new AdminDTO();
        // 根据用户名查
        param.setAccount(vo.getAccount());
        AdminDTO loginDTO = adminMapper.selectOne(param);
        // 用户名没查到
        if (loginDTO == null) {
            throw new BusinessException(CustomExceptionEnum.ACCOUNT_OR_PASSWORD_INCORRECT);
        }

        // 用户禁用
        if (loginDTO.getStatus().equals(DataGlobalVariable.USER_STATUS_DISABLE)) {
            log.warn("账号{}已禁用 无法登录", vo.getAccount());
            throw new BusinessException(CustomExceptionEnum.ACCOUNT_DISABLE);
        }
        // 密码不正确
        if (!PasswordUtils.verification(vo.getPassword(), loginDTO.getSalt(), loginDTO.getPassword())) {
            throw new BusinessException(CustomExceptionEnum.ACCOUNT_OR_PASSWORD_INCORRECT);
        }
        DeptDTO deptDTO = deptMapper.selectByPrimaryKey(loginDTO.getDeptId());
        // 用户设置的权限
        Integer roleId = loginDTO.getRoleId();
        // 登录用户未设置权限
        if (roleId == null) {
            // 获取部门权限
            roleId = deptDTO.getRoleId();
            // 当前部门也未设置权限
            if (roleId == null) {
                roleId = this.getRoleId(deptDTO.getParentId());
            }
            if (roleId == null) {
                throw new BusinessException(CustomExceptionEnum.ACCOUNT_NOT_ROLE);
            }
        }

        // 生成jwt载体
        JwtPayload jwtPayload = new JwtPayload(loginDTO.getId(), loginDTO.getAccount(), loginDTO.getAdminName(), roleId, loginDTO.getDeptId(), deptDTO.getDeptName());
        // token
        String token = JwtUtils.createToken(jwtPayload, JwtConfig.ACCESS_TOKEN_EFFECTIVE);
        // refreshToken
        String refreshToken = IdUtil.simpleUUID();
        // 存储token的key
        String refreshTokenKey = String.format(JwtConfig.REFRESH_TOKEN_KEY_FORMAT, refreshToken);
        // 设置当前有效的Token
        stringRedisTemplate.opsForHash().put(refreshTokenKey, JwtConfig.REDIS_JWT_TOKEN_KEY, token);
        // 存储载体
        stringRedisTemplate.opsForHash().put(refreshTokenKey, JwtConfig.REDIS_JWT_PAYLOAD_KEY, JSONObject.toJSONString(jwtPayload));
        // refresh token 设置过期时间
        stringRedisTemplate.expire(refreshTokenKey, JwtConfig.REFRESH_TOKEN_EXPIRE_TIME, TimeUnit.SECONDS);
        // 记录登录日志
        logMapper.insertSelective(new LogDTO(DataGlobalVariable.LOGIN_LOG_CODE, "系统管理", "登录", "com.gz.controller.system.login", null, null, RequestUtils.getIpAddress(request), null, null));
        log.info("账号：{}  密码：{} 登录成功", vo.getAccount(), vo.getPassword());
        return new LoginRVO(token, refreshToken, System.currentTimeMillis());
    }

    @Override
    public LoginRVO login(String base64) {
        String account = Base64.decodeStr(base64);
        // 查询用户参数
        AdminDTO param = new AdminDTO();
        // 根据用户名查
        param.setAccount(account);
        AdminDTO loginDTO = adminMapper.selectOne(param);
        // 用户名没查到
        if (loginDTO == null) {
            throw new BusinessException(CustomExceptionEnum.ACCOUNT_OR_PASSWORD_INCORRECT);
        }
        // 用户禁用
        if (loginDTO.getStatus().equals(DataGlobalVariable.USER_STATUS_DISABLE)) {
            log.warn("账号{}已禁用 无法登录", account);
            throw new BusinessException(CustomExceptionEnum.ACCOUNT_DISABLE);
        }
        DeptDTO deptDTO = deptMapper.selectByPrimaryKey(loginDTO.getDeptId());
        // 用户设置的权限
        Integer roleId = loginDTO.getRoleId();
        // 登录用户未设置权限
        if (roleId == null) {
            // 获取部门权限
            roleId = deptDTO.getRoleId();
            // 当前部门也未设置权限
            if (roleId == null) {
                roleId = this.getRoleId(deptDTO.getParentId());
            }
            if (roleId == null) {
                throw new BusinessException(CustomExceptionEnum.ACCOUNT_NOT_ROLE);
            }
        }

        // 生成jwt载体
        JwtPayload jwtPayload = new JwtPayload(loginDTO.getId(), loginDTO.getAccount(), loginDTO.getAdminName(), roleId, loginDTO.getDeptId(), deptDTO.getDeptName());
        // token
        String token = JwtUtils.createToken(jwtPayload, JwtConfig.ACCESS_TOKEN_EFFECTIVE);
        // refreshToken
        String refreshToken = IdUtil.simpleUUID();
        // 存储token的key
        String refreshTokenKey = String.format(JwtConfig.REFRESH_TOKEN_KEY_FORMAT, refreshToken);
        // 设置当前有效的Token
        stringRedisTemplate.opsForHash().put(refreshTokenKey, JwtConfig.REDIS_JWT_TOKEN_KEY, token);
        // 存储载体
        stringRedisTemplate.opsForHash().put(refreshTokenKey, JwtConfig.REDIS_JWT_PAYLOAD_KEY, JSONObject.toJSONString(jwtPayload));
        // refresh token 设置过期时间
        stringRedisTemplate.expire(refreshTokenKey, JwtConfig.REFRESH_TOKEN_EXPIRE_TIME, TimeUnit.SECONDS);
        // 记录登录日志
        logMapper.insertSelective(new LogDTO(DataGlobalVariable.LOGIN_LOG_CODE, "系统管理", "登录", "com.gz.controller.system.login", null, null, RequestUtils.getIpAddress(request), null, null));
        log.info("一体化平台跳转登录成");
        return new LoginRVO(token, refreshToken, System.currentTimeMillis());
    }

    @Override
    public LoginRVO refreshToken(String refreshToken) {
        String refreshTokenKey = String.format(JwtConfig.REFRESH_TOKEN_KEY_FORMAT, refreshToken);
        String payload = (String) stringRedisTemplate.opsForHash().get(refreshTokenKey, JwtConfig.REDIS_JWT_PAYLOAD_KEY);

        if (StringUtils.isEmpty(payload)) { // refreshtoken 已经过期
            throw new BusinessException(CustomExceptionEnum.LOGIN_TIMEOUT);
        }

        JwtPayload JwtPayload = JSONObject.parseObject(payload, JwtPayload.class);
        String newAccessToken = JwtUtils.createToken(JwtPayload, JwtConfig.ACCESS_TOKEN_EFFECTIVE);

        String oldAccessToken = (String) stringRedisTemplate.opsForHash().get(refreshTokenKey, JwtConfig.REDIS_JWT_TOKEN_KEY);
        stringRedisTemplate.opsForHash().put(refreshTokenKey, JwtConfig.REDIS_JWT_TOKEN_KEY, newAccessToken);

        // 旧 token 放入黑名单，防止超过 10s 后被再次使用
        String blacklistKey = String.format(JwtConfig.BLACKLIST_KEY_FORMAT, oldAccessToken);
        stringRedisTemplate.opsForValue()
                .set(blacklistKey, oldAccessToken, JwtConfig.ACCESS_TOKEN_EFFECTIVE, TimeUnit.SECONDS);

        return new LoginRVO(newAccessToken, refreshToken, System.currentTimeMillis());
    }


    @Override
    public AdminDTO getLoginInfo() {
        AdminDTO adminDTO = adminMapper.selectByPrimaryKey(JwtUtils.getCurrentUserJwtPayload().getId());
        adminDTO.setPassword(null);
        adminDTO.setSalt(null);
        return adminDTO;
    }

    @Override
    public void logout(String refreshToken) {
        String refreshTokenKey = String.format(JwtConfig.REFRESH_TOKEN_KEY_FORMAT, refreshToken);
        String oldAccessToken = (String) stringRedisTemplate.opsForHash().get(refreshTokenKey, JwtConfig.REDIS_JWT_TOKEN_KEY);
        if (!StringUtils.isEmpty(oldAccessToken)) {
            // 旧 token 放入黑名单，防止再次被调用
            String blacklistKey = String.format(JwtConfig.BLACKLIST_KEY_FORMAT, oldAccessToken); // 被黑名单的 key
            stringRedisTemplate.opsForValue()
                    .set(blacklistKey, "", JwtConfig.ACCESS_TOKEN_EFFECTIVE, TimeUnit.SECONDS);
        }
        stringRedisTemplate.delete(refreshTokenKey);
    }


    @Override
    public InitialHomeRVO getLoginMenus() {
        InitialHomeRVO initialHomeRVO = new InitialHomeRVO();
        // 设置首页信息
        initialHomeRVO.getHomeInfo().put("title", "首页");
        initialHomeRVO.getHomeInfo().put("href", "page/home/welcome.html?t=1");
        // logo信息
        initialHomeRVO.getLogoInfo().put("title", "南京市规划和自然资源局综合档案系统");
        initialHomeRVO.getLogoInfo().put("image", "images/logo.png");
        initialHomeRVO.getLogoInfo().put("href", "");

        // 获取所有菜单
        List<MenuDTO> menusOrder = menuMapper.getRoleMenu(JwtUtils.getCurrentUserJwtPayload().getRoleId());
        //配置
        TreeNodeConfig treeNodeConfig = new TreeNodeConfig();
        // 自定义属性名 都要默认值的
        treeNodeConfig.setIdKey("id");
        treeNodeConfig.setNameKey("title");
        treeNodeConfig.setChildrenKey("child");
        // 最大递归深度
        treeNodeConfig.setDeep(100);

        //转换器
        List<Tree<String>> treeNodes = TreeUtil.build(menusOrder, "-1", treeNodeConfig,
                (treeNode, tree) -> {
                    tree.setId(treeNode.getId().toString());
                    tree.setParentId(treeNode.getParentId().toString());
                    tree.setName(treeNode.getTitle());
                    tree.putExtra("href", treeNode.getHref());
                    tree.putExtra("icon", treeNode.getIcon());
                    tree.putExtra("img", treeNode.getIcon());
                });
        initialHomeRVO.setMenuInfo(treeNodes);

        return initialHomeRVO;
    }


    @Override
    public Integer updatePassword(UpdatePasswordVO vo) throws BusinessException {
        String id = JwtUtils.getCurrentUserJwtPayload().getId();
        AdminDTO adminDTO = adminMapper.selectByPrimaryKey(id);
        String salt = adminDTO.getSalt();
        String password = adminDTO.getPassword();

        // 密码不正确
        if (!PasswordUtils.verification(vo.getOldPassword(), salt, password)) {
            throw new BusinessException(CustomExceptionEnum.PASSWORD_NO_INCORRECT);
        }

        salt = IdUtil.simpleUUID();
        password = PasswordUtils.encryption(vo.getNewPassword(), salt);
        adminDTO.setSalt(salt);
        adminDTO.setPassword(password);
        adminDTO.setUpdateTime(DateUtil.now());

        return adminMapper.updateByPrimaryKey(adminDTO);
    }

    @Override
    public List<MenuDTO> getLoginButtons(Integer menuId) {
        return menuMapper.getRoleButtle(JwtUtils.getCurrentUserJwtPayload().getRoleId(), menuId);
    }

    @Override
    public String generatePictureCaptcha() {
//        LineCaptcha lineCaptcha = null;
        CircleCaptcha circleCaptcha = null;
        try {
            HttpSession session = request.getSession();
//            lineCaptcha = CaptchaUtil.createLineCaptcha(200, 100);
            circleCaptcha = CaptchaUtil.createCircleCaptcha(200, 100, 4, 20);
            session.setAttribute("captcha", circleCaptcha.getCode());
            log.info("生成验证码:{}", circleCaptcha.getCode());
        } catch (Exception e) {
            throw new BusinessException(500, "生成验证码失败");
        }
//        return "cn.hutool.captcha";
        return "data:image/jpg;base64," + circleCaptcha.getImageBase64();
    }

    @Override
    public AdminDTO currLoginAdmin() {
        String id = JwtUtils.getCurrentUserJwtPayload().getId();
        return adminMapper.selectByPrimaryKey(id);
    }

    @Override
    public AdminRVO currLoginAdminDept() {
        return adminService.selectByPrimaryKeyDept(JwtUtils.getCurrentUserJwtPayload().getId());
    }
}
