package com.qiaoba.auth.service.impl;

import cn.hutool.captcha.CaptchaUtil;
import cn.hutool.captcha.LineCaptcha;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import com.qiaoba.api.auth.constants.SecurityConstant;
import com.qiaoba.api.auth.entity.OnlineUser;
import com.qiaoba.api.auth.service.AuthConfigApiService;
import com.qiaoba.api.auth.service.OnlineUserService;
import com.qiaoba.api.auth.service.SysUserDetailsApiService;
import com.qiaoba.api.auth.utils.SecurityUtil;
import com.qiaoba.api.auth.utils.TokenUtil;
import com.qiaoba.api.monitor.entity.SysLoginLog;
import com.qiaoba.api.monitor.service.SysLoginLogApiService;
import com.qiaoba.api.system.entity.SysUser;
import com.qiaoba.api.system.entity.dto.LoginDto;
import com.qiaoba.api.system.service.SysUserApiService;
import com.qiaoba.auth.service.SysLoginService;
import com.qiaoba.common.base.constant.BaseConstant;
import com.qiaoba.common.base.constant.ConfigConstant;
import com.qiaoba.common.base.enums.BaseEnum;
import com.qiaoba.common.base.exception.ServiceException;
import com.qiaoba.common.redis.service.RedisService;
import com.qiaoba.common.web.util.IpUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * 登录 服务层实现
 *
 * @author ailanyin
 * @version 1.0
 * @since 2023/5/15 15:31
 */
@Service
@RequiredArgsConstructor
public class SysLoginServiceImpl implements SysLoginService {

    private final RedisService redisService;
    private final HttpServletRequest request;
    private final SysUserDetailsApiService userDetailsService;
    private final SysUserApiService sysUserApiService;
    private final OnlineUserService onlineUserService;
    private final AuthConfigApiService authConfigApiService;
    private final SysLoginLogApiService sysLoginLogApiService;

    @Override
    public Map<String, Object> getCaptchaImage() {
        Map<String, Object> map = new HashMap<String, Object>(4);

        map.put("register", authConfigApiService.getRegisterConfig());
        if (!authConfigApiService.getCaptchaConfig()) {
            map.put("captchaEnabled", false);
            return map;
        }

        String uuid = UUID.randomUUID().toString(true);
        LineCaptcha captcha = CaptchaUtil.createLineCaptcha(150, 50, 4, 20);
        map.put("uuid", uuid);
        map.put("img", captcha.getImageBase64());
        redisService.set(SecurityConstant.CAPTCHA_KEY + uuid, captcha.getCode(), 120);

        return map;
    }

    @Override
    public String login(LoginDto dto) {
        Boolean blacklistSwitch = authConfigApiService.getBlacklistConfig();
        // 校验黑名单
        validateBlacklist(blacklistSwitch);
        // 校验验证码
        authConfigApiService.validateCaptcha(dto.getCode(), dto.getUuid());
        // username查询用户信息
        SysUser sysUser = sysUserApiService.selectByUsername(dto.getUsername());
        // 检查账号信息
        validateUser(dto.getUsername(), sysUser);
        // 检验密码
        validatePassword(blacklistSwitch, dto.getUsername(), sysUser.getPassword(), dto.getPassword());
        // 缓存在线用户
        OnlineUser onlineUser = cacheOnlineUser(dto.getUsername(), sysUser.getNickname());
        // 缓存userDetails
        userDetailsService.toCache(sysUser.getUsername(), onlineUser.getDeviceSn());
        // 新增登录日志
        addLoginLog(onlineUser);
        // 生成Token
        return TokenUtil.generateToken(sysUser.getUsername(), onlineUser.getDeviceSn());
    }

    private void validateBlacklist(Boolean blacklistSwitch) {
        String ip = IpUtil.getIp(request);
        if (blacklistSwitch && redisService.hasKey(SecurityConstant.BLACKLIST_KEY + ip)) {
            throw new ServiceException(SecurityConstant.HAS_BEEN_PULLED_BLACK);
        }
    }

    private void validatePassword(Boolean blacklistSwitch, String username, String password, String inputPassword) {
        boolean result = SecurityUtil.matchesPassword(inputPassword, password);
        // 密码正确
        if (result) {
            if (blacklistSwitch) {
                // 删除错误次数
                String ip = IpUtil.getIp(request);
                redisService.del(SecurityConstant.LOGIN_ERROR_COUNT + ip);
            }

        } else {
            // 密码错误
            String msg = beforePasswordError(blacklistSwitch, username);
            throw new ServiceException(msg);
        }
    }

    private String beforePasswordError(Boolean blacklistSwitch, String username) {
        String ip = IpUtil.getIp(request);
        // 未开启->直接结束
        if (!blacklistSwitch) {
            return "密码错误";
        }
        // 开启->继续
        // 错误次数是否到达允许最大错误次数
        Integer maxAllowCount = authConfigApiService.getAllowMaxErrorCount();
        Integer ipErrorCount = getIpErrorCount(ip);
        if (ipErrorCount >= maxAllowCount) {
            // 是-> 进入黑名单库 && 返回"IP已被拉黑"
            redisService.set(SecurityConstant.BLACKLIST_KEY + ip, username, 60 * authConfigApiService.getBlacklistExpireTime());
            redisService.del(SecurityConstant.LOGIN_ERROR_COUNT + ip);
            return SecurityConstant.HAS_BEEN_PULLED_BLACK;
        } else {
            //  否-> 错误次数+1 && 返回"你还剩xx次错误机会"
            ipErrorCount++;
            redisService.set(SecurityConstant.LOGIN_ERROR_COUNT + ip, ipErrorCount, 600);
            return StrUtil.format("密码错误, 还有[{}]次错误机会", (maxAllowCount - ipErrorCount));
        }

    }

    private Integer getIpErrorCount(String ip) {
        Object ipErrorCount = redisService.get(SecurityConstant.LOGIN_ERROR_COUNT + ip);
        return Objects.isNull(ipErrorCount) ? 0 : Integer.parseInt(ipErrorCount.toString());
    }

    private void validateUser(String username, SysUser user) {
        if (ObjectUtil.isNull(user)) {
            throw new ServiceException(StrUtil.format("登录用户：{} 不存在", username));
        } else if (BaseEnum.ABNORMAL.getCode().equals(user.getStatus())) {
            throw new ServiceException(StrUtil.format("对不起, 您的账号：{} 已被禁用", username));
        }
    }


    private OnlineUser cacheOnlineUser(String username, String nickname) {
        String deviceSn = UUID.fastUUID().toString(true);
        String ip = IpUtil.getIp(request);
        String address = IpUtil.getIpAddr(ip);
        UserAgent userAgent = UserAgentUtil.parse(request.getHeader("User-Agent"));
        String browser = userAgent.getBrowser().getName() + BaseConstant.LINE_JOIN_STR + userAgent.getVersion();
        String os = userAgent.getOs().getName();
        OnlineUser onlineUser = new OnlineUser(deviceSn, username, nickname, ip, address, browser, os, new Date());
        onlineUserService.insert(onlineUser);
        redisService.set(SecurityConstant.LAST_LOGIN_USER_REDIS_KEY + username, deviceSn, TokenUtil.TOKEN_EXPIRE_HOUR_TIME * 3600);
        // 允许同时在线
        if (authConfigApiService.checkAllowBothOnline()) {
            redisService.hSet(SecurityConstant.LOGIN_USER_DEVICES_REDIS_KEY + username, deviceSn, 1, TokenUtil.TOKEN_EXPIRE_HOUR_TIME * 3600);
        }
        return onlineUser;
    }

    private void addLoginLog(OnlineUser onlineUser) {
        // 查询系统配置 - 是否开启登录日志
        String onOff = redisService.getObject(ConfigConstant.LOGIN_LOG_ON_OFF_KEY, String.class);
        if (ConfigConstant.COMMON_ON_VALUE.equals(onOff)) {
            SysLoginLog sysLoginLog = BeanUtil.copyProperties(onlineUser, SysLoginLog.class);
            sysLoginLogApiService.insert(sysLoginLog);
        }
    }
}
