package cn.reghao.oss.console.account.service.impl;

import cn.reghao.oss.console.account.db.repository.UserRepository;
import cn.reghao.oss.console.account.model.dto.AccountLoginDto;
import cn.reghao.oss.console.account.model.po.User;
import cn.reghao.oss.console.account.security.exceptioin.AccountLoginException;
import cn.reghao.oss.console.account.security.form.AccountAuthToken;
import cn.reghao.oss.console.account.service.AccountAuthService;
import cn.reghao.oss.console.account.service.CodeService;
import cn.reghao.oss.console.account.service.PubkeyService;
import cn.reghao.oss.console.util.CacheKeys;
import cn.reghao.jutil.web.ServletUtil;
import com.github.benmanes.caffeine.cache.Cache;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedCredentialsNotFoundException;
import org.springframework.stereotype.Service;

import javax.servlet.http.Cookie;

/**
 * @author reghao
 * @date 2022-07-08 14:51:17
 */
@Service
public class AccountAuthServiceImpl implements AccountAuthService {
    private final String cookieName = "USERDATA";
    private final String domain = "";
    private final Cache<String, Object> cache;
    private final UserRepository userRepository;
    private final CodeService codeService;
    private final PubkeyService pubkeyService;
    private final PasswordEncoder passwordEncoder;

    public AccountAuthServiceImpl(Cache<String, Object> cache, UserRepository userRepository,
                                  CodeService codeService, PubkeyService pubkeyService, PasswordEncoder passwordEncoder) {
        this.cache = cache;
        this.userRepository = userRepository;
        this.codeService = codeService;
        this.pubkeyService = pubkeyService;
        this.passwordEncoder = passwordEncoder;
    }

    @Override
    public AccountAuthToken getPreAuthentication(AccountLoginDto accountLoginDto) {
        String principal = accountLoginDto.getPrincipal();
        String credential = accountLoginDto.getCredential();
        String captchaCode = accountLoginDto.getCaptchaCode();
        Boolean rememberMe = accountLoginDto.getRememberMe();

        /*String savedCaptchaCode = codeService.getCaptcha();
        if (savedCaptchaCode == null) {
            String errMsg = "当前会话已过期, 请刷新页面后重新登录. 或者检查浏览器是否禁用了 cookie";
            throw new PreAuthenticatedCredentialsNotFoundException(errMsg);
        } else if (!savedCaptchaCode.equalsIgnoreCase(captchaCode)) {
            String errMsg = "图形验证码不正确...";
            throw new PreAuthenticatedCredentialsNotFoundException(errMsg);
        }*/

        String decryptCredential;
        try {
            decryptCredential = pubkeyService.decrypt(credential);
        } catch (Exception e) {
            throw new PreAuthenticatedCredentialsNotFoundException(e.getMessage());
        }

        String loginId = ServletUtil.getSessionId();
        return new AccountAuthToken(loginId, rememberMe, principal, decryptCredential);
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userRepository.findByUsername(username);
        if (user == null) {
            String errMsg = String.format("帐号 %s 不存在", username);
            throw new UsernameNotFoundException(errMsg);
        }

        return user;
    }

    @Override
    public AccountAuthToken authByPassword(AccountAuthToken authToken) {
        String username = (String) authToken.getPrincipal();
        User user = (User) loadUserByUsername(username);
        String password = (String) authToken.getCredentials();
        String encodedPassword = passwordEncoder.encode(password + user.getSalt());
        if (!user.getPassword().equals(encodedPassword)) {
            String errMsg = "账号或密码不正确";
            throw new AccountLoginException(errMsg);
        }

        return new AccountAuthToken(authToken, user);
    }

    @Override
    public void setCookie(AccountAuthToken authToken, long timeout) {
        int userId = authToken.getUserId();
        int plat = 1;
        String loginId = authToken.getLoginId();

        String userdata = String.format("%s:%s:%s", userId, plat, loginId);
        Cookie cookie3 = generateCookie(cookieName, userdata, timeout);
        ServletUtil.getResponse().addCookie(cookie3);

        String loginSuccessKey = CacheKeys.getLoginSuccessKey(userId, plat, loginId);
        if (timeout == 0) {
            timeout = 3600*24*30;
        }
        cache.put(loginSuccessKey, authToken);
    }

    private Cookie generateCookie(String name, String value, long timeout) {
        String path = "/";

        Cookie cookie = new Cookie(name, value);
        if (timeout != 0) {
            cookie.setMaxAge((int) timeout);
        }
        cookie.setDomain(domain);
        cookie.setSecure(true);
        cookie.setPath(path);
        return cookie;
    }

    @Override
    public void logout() {
        // 删除 cookie 中的数据(或者是撤销 token)
        // 删除 redis 中的 account login 数据
        // 删除 redis 中的 session 数据
        // 删除 SecurityContextHolder 中的数据

        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication instanceof AccountAuthToken) {
            SecurityContext context = SecurityContextHolder.getContext();
            context.setAuthentication(null);
            SecurityContextHolder.clearContext();
        }
    }

    @Override
    public void clearCookie() {
        String path = "/";
        Cookie cookie2 = new Cookie(cookieName, null);
        cookie2.setMaxAge(0);
        cookie2.setDomain(domain);
        cookie2.setSecure(true);
        cookie2.setPath(path);
        ServletUtil.getResponse().addCookie(cookie2);
    }
}
