package com.njzscloud.common.security.support;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import com.njzscloud.common.core.ex.ExceptionMsg;
import com.njzscloud.common.security.contant.Constants;
import com.njzscloud.common.security.ex.UserLoginException;
import org.springframework.security.authentication.AccountStatusException;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.CredentialsContainer;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

import java.util.Set;

/**
 * 认证器
 */
public abstract class AbstractAuthenticationProvider implements AuthenticationProvider {

    /**
     * 是否支持当前登录信息
     *
     * @param authentication 认证信息类型（在 filter 中调用 authenticate 方法传入的待认证对象的类型）
     * @return true-->支持
     */
    @Override
    public final boolean supports(Class<?> authentication) {
        return UserAuthenticationToken.class.isAssignableFrom(authentication);
    }

    @Override
    public final Authentication authenticate(Authentication authentication) throws AuthenticationException {
        UserAuthenticationToken userAuthenticationToken = (UserAuthenticationToken) authentication;

        Object principal = userAuthenticationToken.getPrincipal();

        if (!principal.getClass().isAssignableFrom(this.getLoginFormClazz())) return null;

        LoginForm loginForm = (LoginForm) principal;

        this.beforeCheck(loginForm);

        UserDetail userDetail;
        try {
            userDetail = this.retrieveUser(loginForm);
        } catch (AuthenticationException e) {
            throw e;
        } catch (Exception e) {
            throw new InternalAuthenticationServiceException("服务器异常，用户信息加载失败", e);
        }

        Assert.notNull(userDetail, () -> new UsernameNotFoundException("用户不存在"));

        Set<String> roles = userDetail.getRoles();
        if (CollUtil.isEmpty(roles)) {
            userDetail.setRoles(CollUtil.newHashSet(Constants.ROLE_ANONYMOUS, Constants.ROLE_AUTHENTICATED));
        } else {
            roles.add(Constants.ROLE_ANONYMOUS);
            roles.add(Constants.ROLE_AUTHENTICATED);
        }

        this.afterCheck(loginForm, userDetail);

        // 敏感信息擦除
        if (loginForm instanceof CredentialsContainer) {
            ((CredentialsContainer) loginForm).eraseCredentials();
        }

        this.lastCheck(loginForm, userDetail);
        AuthenticationDetails details = (AuthenticationDetails) userAuthenticationToken.getDetails();
        return this.createAuthentication(userDetail, details);
    }

    /**
     * 创建已完成认证的认证对象
     *
     * @param userDetail 用户信息
     * @param details    额外登录信息
     * @return 已完成认证的认证对象 {@link UserAuthenticationToken}
     */
    private Authentication createAuthentication(UserDetail userDetail, AuthenticationDetails details) {
        Token token = Token.create(userDetail.getUserId(), userDetail.getAccountId(), userDetail.getAuthWay());
        userDetail.setToken(token);
        return UserAuthenticationToken.create(userDetail, details);
    }

    /**
     * 表单检查，在查询用户信息之前执行
     *
     * @param loginForm 登录表单
     * @throws UserLoginException 表单校验失败是抛出
     */
    protected void beforeCheck(LoginForm loginForm) throws UserLoginException {
    }

    /**
     * 用户信息检查（如：密码校验），在查询用户信息之后执行
     *
     * @param userDetail 用户信息
     * @throws AccountStatusException 用户信息校验失败时抛出
     */
    protected void afterCheck(LoginForm loginForm, UserDetail userDetail) throws UserLoginException {
    }

    /**
     * 最终检查，用于检查账号状态，如：是否锁定
     *
     * @param userDetail 用户信息
     * @throws AccountStatusException 账号状态校验失败时抛出
     */
    protected void lastCheck(LoginForm loginForm, UserDetail userDetail) throws UserLoginException {
        if (userDetail.getDisabled() == Boolean.TRUE) {
            throw new UserLoginException(ExceptionMsg.CLI_ERR_MSG, "用户已被禁用");
        }
    }

    /**
     * 加载用户信息
     *
     * @param loginForm 登录表单
     * @return 用户信息
     */
    protected abstract UserDetail retrieveUser(LoginForm loginForm) throws UserLoginException;

    /**
     * 获取登录表单的类型
     *
     * @return {@link LoginForm} 的子类
     */
    protected abstract Class<?> getLoginFormClazz();

}
