package cn.reghao.tnb.auth.app.service.impl;

import cn.reghao.jutil.jdk.converter.DateTimeConverter;
import cn.reghao.jutil.jdk.web.result.Result;
import cn.reghao.tnb.auth.app.service.AccountTokenService;
import cn.reghao.tnb.auth.app.service.LoginAttemptService;
import cn.reghao.tnb.auth.app.model.vo.LoginRecordVo;
import cn.reghao.tnb.auth.app.config.redis.ds.RedisOps;
import cn.reghao.tnb.auth.app.security.form.AccountAuthToken;
import cn.reghao.tnb.auth.app.config.redis.RedisKeys;
import cn.reghao.tnb.common.web.ServletUtil;
import cn.reghao.tnb.auth.app.db.mapper.UserLoginMapper;
import cn.reghao.tnb.auth.app.model.po.UserLogin;
import cn.reghao.tnb.auth.app.config.redis.ds.RedisString;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedCredentialsNotFoundException;
import org.springframework.stereotype.Service;

import jakarta.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author reghao
 * @date 2022-07-08 10:37:49
 */
@Slf4j
@Service
public class LoginAttemptServiceImpl implements LoginAttemptService {
    // 帐号可同时登入的设备数量
    private final int maxDevice = 3;
    private final UserLoginMapper attemptsMapper;
    private final RedisString redisString;
    private final RedisOps redisOps;
    private final AccountTokenService accountTokenService;

    public LoginAttemptServiceImpl(UserLoginMapper attemptsMapper, RedisString redisString, RedisOps redisOps,
                                   AccountTokenService accountTokenService) {
        this.attemptsMapper = attemptsMapper;
        this.redisString = redisString;
        this.redisOps = redisOps;
        this.accountTokenService = accountTokenService;
    }

    @Override
    public void loginCheck(long userId) {
        checkFailedCount(userId);
        checkMaxSession(userId);
    }

    private void checkFailedCount(long userId) {
        String redisKey = RedisKeys.getLoginFailedLockKey(userId);
        boolean exist = redisOps.exists(redisKey);
        if (exist) {
            String errMsg = "登入错误次数超过限制，请一个小时后再尝试登入";
            throw new PreAuthenticatedCredentialsNotFoundException(errMsg);
        }
    }

    private void checkMaxSession(long userId) {
        String keyPattern = RedisKeys.getLoginSuccessKeyPattern(userId);
        List<String> loginIds = redisOps.keys(keyPattern).stream()
                .filter(key -> key.endsWith("auth_token"))
                .map(key -> {
                    String[] arr = key.split(":");
                    if (arr.length != 7) {
                        return null;
                    }

                    return arr[arr.length-2];
                })
                .filter(Objects::nonNull)
                .collect(Collectors.toList());

        if (loginIds.size() >= maxDevice) {
            // 登出最早登入的设备
            List<UserLogin> list = attemptsMapper.findByLoginIds(loginIds).stream()
                    .sorted((o1, o2) -> (int) (o1.getLoginAt()-o2.getLoginAt()))
                    .collect(Collectors.toList());
            if (!list.isEmpty()) {
                UserLogin oldest = list.get(0);
                accountTokenService.logout(oldest.getLoginId());
            }
        }
    }

    @Override
    public Result loginFailedCount(long userId, String loginId) {
        attemptsMapper.updateSetFailed(loginId);

        String loginFailedCountKey = RedisKeys.getLoginFailedCountKey(userId);
        String failedCountStr = redisString.get(loginFailedCountKey);
        long timeout = 600;
        if (failedCountStr == null) {
            redisString.setWithTimeout(loginFailedCountKey, "1", timeout);
        } else {
            long failedCount = redisString.incr(loginFailedCountKey);
            if (failedCount >= 3) {
                redisString.setWithTimeout(RedisKeys.getLoginFailedLockKey(userId), "failed-lock", timeout);
                return Result.fail("登入错误次数超过限制，请一个小时后再尝试登入");
            }
        }

        return Result.success("");
    }

    @Override
    public void saveLoginAttempts(AccountAuthToken authToken) {
        long userId = authToken.getUserId();
        String loginId = authToken.getLoginId();
        int plat = authToken.getPlat();
        int loginType = authToken.getLoginType();
        boolean rememberMe = authToken.isRememberMe();

        HttpServletRequest request = ServletUtil.getRequest();
        String userAgent = request.getHeader("user-agent");
        String loginIp = request.getRemoteAddr();

        UserLogin userLogin = new UserLogin(loginId, userId, plat, loginIp, loginType, userAgent, rememberMe);
        attemptsMapper.save(userLogin);
    }

    @Override
    public List<LoginRecordVo> getActiveLogin() {
        AccountAuthToken authToken = accountTokenService.getAuthToken();
        long userId = authToken.getUserId();
        String loginId = authToken.getLoginId();
        String keyPattern = RedisKeys.getLoginSuccessKeyPattern(userId);
        List<String> loginIds = redisOps.keys(keyPattern).stream()
                .filter(key -> key.endsWith("auth_token"))
                .map(key -> {
                    String[] arr = key.split(":");
                    if (arr.length != 7) {
                        return null;
                    }

                    return arr[arr.length-2];
                })
                .filter(Objects::nonNull)
                .collect(Collectors.toList());

        List<UserLogin> userLoginList = attemptsMapper.findByUserId(userId, 10);
        userLoginList.forEach(userLogin -> {
            long userId0 = userLogin.getUserId();
            String loginId0 = userLogin.getLoginId();
            String redisKey = RedisKeys.getAuthTokenKey(userId0, loginId0);
            boolean online = redisOps.exists(redisKey);
        });

        return attemptsMapper.findByLoginIds(loginIds).stream()
                .map(userLogin -> {
                    String status = "在线";
                    if (loginId.equals(userLogin.getLoginId())) {
                        status += "(当前设备)";
                    }

                    String loginAtStr = DateTimeConverter.format(userLogin.getLoginAt());
                    String location = getLocationByIp(userLogin.getLoginIp());
                    return new LoginRecordVo(userLogin.getLoginId(), userLogin.getUserAgent(), userLogin.getLoginIp(), location, loginAtStr, status);
                })
                .collect(Collectors.toList());
    }

    private String getLocationByIp(String ip) {
        return "中国-成都";
    }
}
