package cn.reghao.bnt.web.admin.service.impl;

import cn.reghao.bnt.web.admin.db.repository.LoginAttemptsRepository;
import cn.reghao.bnt.web.admin.model.po.LoginAttempt;
import cn.reghao.bnt.web.admin.model.vo.LoginRecordInfo;
import cn.reghao.bnt.web.admin.security.form.AccountAuthToken;
import cn.reghao.bnt.web.admin.service.AccountSessionService;
import cn.reghao.bnt.web.admin.service.LoginAttemptService;
import cn.reghao.bnt.web.admin.service.UserContext;
import cn.reghao.bnt.web.util.CacheKeys;
import cn.reghao.jutil.jdk.converter.DateTimeConverter;
import cn.reghao.jutil.jdk.web.result.Result;
import cn.reghao.jutil.jdk.thread.ThreadPoolWrapper;
import cn.reghao.jutil.web.ServletUtil;
import com.github.benmanes.caffeine.cache.Cache;
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.List;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author reghao
 * @date 2024-08-30 17:53:00
 */
/**
 * @author reghao
 * @date 2022-07-08 10:37:49
 */
@Slf4j
@Service
public class LoginAttemptServiceImpl implements LoginAttemptService {
    private final ScheduledExecutorService scheduler;
    // 帐号可同时登入的设备数量
    private final int maxDevice = 2;
    private long timeout = 600;
    private String timeoutStr = "十分钟";
    private final LoginAttemptsRepository loginAttemptsRepository;
    private final Cache<String, Object> caffeineCache;
    private final AccountSessionService accountSessionService;

    public LoginAttemptServiceImpl(LoginAttemptsRepository loginAttemptsRepository, Cache<String, Object> caffeineCache,
                                   AccountSessionService accountSessionService) {
        this.scheduler = ThreadPoolWrapper.scheduledThreadPool("login-attempt-scheduler", 10);
        this.loginAttemptsRepository = loginAttemptsRepository;
        this.caffeineCache = caffeineCache;
        this.accountSessionService = accountSessionService;
    }

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

    private void checkFailedCount(int userId) {
        String key = CacheKeys.getLoginFailedLockKey(userId);
        String loginFailed = (String) caffeineCache.getIfPresent(key);
        if (loginFailed != null) {
            String errMsg = String.format("登入错误次数超过限制，请%s后再尝试登入", timeoutStr);
            throw new PreAuthenticatedCredentialsNotFoundException(errMsg);
        }
    }

    private void checkMaxSession(int 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<LoginAttempt> list = loginAttemptsRepository.findByLoginIdIn(loginIds).stream()
                    .sorted((o1, o2) -> (int) (o1.getLoginAt()-o2.getLoginAt()))
                    .collect(Collectors.toList());
            if (!list.isEmpty()) {
                LoginAttempt oldest = list.get(0);
                accountTokenService.logout(oldest.getLoginId());
            }
        }*/
    }

    @Override
    public Result loginFailedCount(int userId, String loginId) {
        LoginAttempt loginAttempt = loginAttemptsRepository.findByLoginId(loginId);
        if (loginAttempt != null) {
            loginAttempt.setSuccess(false);
            loginAttemptsRepository.save(loginAttempt);
            //loginAttemptsRepository.updateSetFailed(loginId);
        }

        String loginFailedCountKey = CacheKeys.getLoginFailedCountKey(userId);
        Integer failedCount = (Integer) caffeineCache.getIfPresent(loginFailedCountKey);
        if (failedCount == null) {
            caffeineCache.put(loginFailedCountKey, 1);
            CacheEvictTask cacheEvictTask = new CacheEvictTask(loginFailedCountKey);
            ScheduledFuture<?> future = scheduler.schedule(cacheEvictTask, timeout, TimeUnit.SECONDS);
        } else {
            failedCount += 1;
            if (failedCount <= 3) {
                caffeineCache.put(loginFailedCountKey, failedCount);
            } else {
                caffeineCache.invalidate(loginFailedCountKey);

                String lockKey = CacheKeys.getLoginFailedLockKey(userId);
                caffeineCache.put(lockKey, "failed-lock");
                CacheEvictTask cacheEvictTask = new CacheEvictTask(lockKey);
                ScheduledFuture<?> future = scheduler.schedule(cacheEvictTask, timeout, TimeUnit.SECONDS);
                return Result.fail(String.format("登入错误次数超过限制，请%s后再尝试登入", timeoutStr));
            }
        }

        return Result.success("");
    }

    @Override
    public void saveLoginAttempts(AccountAuthToken authToken) {
        int 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();

        LoginAttempt loginAttempt = new LoginAttempt(loginId, userId, plat, loginIp, loginType, userAgent, rememberMe);
        loginAttemptsRepository.save(loginAttempt);
    }

    @Override
    public List<LoginRecordInfo> getActiveLogin() {
        /*AccountAuthToken authToken = accountTokenService.getAuthToken();
        int 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());

        PageRequest pageRequest = PageRequest.of(0, 10);
        List<LoginAttempt> loginAttemptsList = loginAttemptsRepository.findByUserId(userId, pageRequest).getContent();
        loginAttemptsList.forEach(loginAttempts -> {
            int userId0 = loginAttempts.getUserId();
            String loginId0 = loginAttempts.getLoginId();
            String redisKey = RedisKeys.getAuthTokenKey(userId0, loginId0);
            boolean online = redisOps.exists(redisKey);
        });*/

        AccountAuthToken accountAuthToken = UserContext.getAccountAuthToken();
        int userId = accountAuthToken.getUserId();
        String loginId = accountAuthToken.getLoginId();
        List<String> loginIds = accountSessionService.getActiveLoginIds(userId);
        return loginAttemptsRepository.findByLoginIdIn(loginIds).stream()
                .map(loginAttempt -> {
                    String status = "在线";
                    if (loginId.equals(loginAttempt.getLoginId())) {
                        status += "(当前设备)";
                    }

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

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

    class CacheEvictTask implements Runnable {
        private final String cacheKey;

        public CacheEvictTask(String cacheKey) {
            this.cacheKey = cacheKey;
        }

        @Override
        public void run() {
            try {
                log.info("exec cache evict task with key {}...", cacheKey);
                caffeineCache.invalidate(cacheKey);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}
