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

import cn.reghao.jutil.jdk.converter.DateTimeConverter;
import cn.reghao.jutil.jdk.result.Result;
import cn.reghao.tnb.account.app.service.LoginAttemptService;
import cn.reghao.tnb.account.app.model.vo.LoginRecordVo;
import cn.reghao.tnb.account.app.redis.ds.RedisOps;
import cn.reghao.tnb.account.app.security.form.AccountAuthToken;
import cn.reghao.tnb.account.app.redis.RedisKeys;
import cn.reghao.jutil.web.ServletUtil;
import cn.reghao.tnb.account.app.db.mapper.LoginAttemptsMapper;
import cn.reghao.tnb.account.app.model.po.LoginAttempts;
import cn.reghao.tnb.account.app.redis.ds.RedisString;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedCredentialsNotFoundException;
import org.springframework.stereotype.Service;

import javax.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 LoginAttemptsMapper attemptsMapper;
    private final RedisString redisString;
    private final RedisOps redisOps;

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

    @Override
    public void check(long userId, int plat) {
        checkFailedCount(userId, plat);
        checkMaxSession(userId, plat);
    }

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

    private void checkMaxSession(long userId, int plat) {
        String keyPattern = RedisKeys.getLoginSuccessKeyPattern(userId, plat);
        Set<String> keys = redisOps.keys(keyPattern);
        int maxSession = 10;
        if (keys.size() >= maxSession) {
            SortedMap<Long, String> sortedMap = new TreeMap<>();
            keys.forEach(redisKey -> {
                long ttl = redisOps.ttl(redisKey);
                sortedMap.putIfAbsent(ttl, redisKey);
            });

            String oldestSession = sortedMap.get(sortedMap.firstKey());
            redisOps.del(oldestSession);
            //String errMsg = "您在本平台登录的设备数量已超过最大限制, 请注销一些设备后再尝试登录";
            //throw new PreAuthenticatedCredentialsNotFoundException(errMsg);
        }
    }

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

        return Result.success("");
    }

    @Override
    public void saveSuccessLogin(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();

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

    @Override
    public List<LoginRecordVo> getActiveLogin() {
        AccountAuthToken authToken = (AccountAuthToken) SecurityContextHolder.getContext().getAuthentication();
        long userId = authToken.getUserId();
        String loginId = authToken.getLoginId();
        String keyPattern = RedisKeys.getLoginSuccessKeyPattern(userId);
        List<String> onlineLogins = redisOps.keys(keyPattern).stream()
                .map(key -> {
                    String[] arr = key.split(":");
                    return arr[5];
                })
                .collect(Collectors.toList());

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

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

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

    @Override
    public LoginAttempts getLoginAttempts(String loginId) {
        return attemptsMapper.findByLoginId(loginId);
    }
}
