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

import cn.reghao.tnb.account.api.constant.VerifyChannel;
import cn.reghao.tnb.account.app.model.constant.LoginType;
import cn.reghao.tnb.account.app.model.dto.AccountLoginDto;
import cn.reghao.tnb.account.app.model.vo.UserDetail;
import cn.reghao.tnb.account.app.service.AccountLoginService;
import cn.reghao.tnb.account.app.service.LoginAttemptService;
import cn.reghao.tnb.account.app.db.query.UserAuthQuery;
import cn.reghao.tnb.account.app.security.exception.AccountLoginException;
import cn.reghao.tnb.account.app.security.form.AccountAuthToken;
import cn.reghao.tnb.account.app.model.dto.AccountDetail;
import cn.reghao.tnb.account.app.service.*;
import cn.reghao.jutil.jdk.string.StringRegexp;
import org.springframework.security.authentication.DisabledException;
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 java.util.UUID;

/**
 * @author reghao
 * @date 2022-07-08 14:51:17
 */
@Service
public class AccountLoginServiceImpl implements AccountLoginService {
    private final UserAuthQuery userAuthQuery;
    private final CodeService codeService;
    private final PubkeyService pubkeyService;
    private final AccountRegistryService accountRegistryService;
    private final LoginAttemptService loginAttemptService;
    private final PasswordEncoder passwordEncoder;

    public AccountLoginServiceImpl(UserAuthQuery userAuthQuery, CodeService codeService, PubkeyService pubkeyService,
                                   AccountRegistryService accountRegistryService, LoginAttemptService loginAttemptService,
                                   PasswordEncoder passwordEncoder) {
        this.userAuthQuery = userAuthQuery;
        this.codeService = codeService;
        this.pubkeyService = pubkeyService;
        this.accountRegistryService = accountRegistryService;
        this.loginAttemptService = loginAttemptService;
        this.passwordEncoder = passwordEncoder;
    }

    @Override
    public AccountAuthToken getPreAuthentication(AccountLoginDto userLoginDto) {
        Integer loginType = userLoginDto.getLoginType();
        Integer plat = userLoginDto.getPlat();
        String principal = userLoginDto.getPrincipal();
        String credential = userLoginDto.getCredential();
        String captchaCode = userLoginDto.getCaptchaCode();
        Boolean rememberMe = userLoginDto.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 = UUID.randomUUID().toString().replace("-", "");
        return new AccountAuthToken(plat, loginId, loginType, rememberMe, principal, decryptCredential);
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        AccountDetail accountDetail;
        if (StringRegexp.matchEmail(username)) {
            accountDetail = userAuthQuery.findByEmail(username);
        } else if (StringRegexp.matchMobile(username)) {
            accountDetail = userAuthQuery.findByMobile(username);
        } else {
            accountDetail = userAuthQuery.findByUsername(username);
        }

        if (accountDetail == null) {
            String errMsg = String.format("帐号 %s 未注册", username);
            throw new UsernameNotFoundException(errMsg);
        }
        return new UserDetail(accountDetail);
    }

    @Override
    public AccountAuthToken authByPassword(AccountAuthToken authToken) {
        String username = (String) authToken.getPrincipal();
        UserDetail userDetail = (UserDetail) loadUserByUsername(username);
        long userId = userDetail.getUserId();
        int plat = authToken.getPlat();
        loginAttemptService.check(userId, plat);

        String password = (String) authToken.getCredentials();
        String encodedPassword = passwordEncoder.encode(password + userDetail.getSalt());
        if (!userDetail.getPassword().equals(encodedPassword)) {
            String errMsg = "账号或密码不正确";
            throw new AccountLoginException(errMsg, userId, plat);
        }

        userDetail.setLoginType(LoginType.usernamePassword.getValue());
        return new AccountAuthToken(authToken, userDetail);
    }

    /**
     * TODO 手机号验证码登录, 手机号不存在则自动注册
     *
     * @param
     * @return
     * @date 2022-07-08 下午3:11
     */
    @Override
    public AccountAuthToken authByVerifyCode(AccountAuthToken authToken) {
        int plat = authToken.getPlat();
        String principal = (String) authToken.getPrincipal();
        String credential = (String) authToken.getCredentials();

        UserDetail userDetail;
        try {
            userDetail = (UserDetail) loadUserByUsername(principal);
            String savedCredential = codeService.getVerifyCode(VerifyChannel.loginChannel.getValue(), principal);
            if (savedCredential == null) {
                String errMsg = "当前会话已过期, 请刷新页面后重新登录. 或者检查浏览器是否禁用了 cookie";
                throw new DisabledException(errMsg);
            } else if (!credential.equals(savedCredential)) {
                String errMsg = "短信验证码不正确";
                if (userDetail != null) {
                    long userId = userDetail.getUserId();
                    loginAttemptService.check(userId, plat);
                    throw new AccountLoginException(errMsg, userId, plat);
                }

                throw new DisabledException(errMsg);
            }
        } catch (UsernameNotFoundException e) {
            // 验证码方式登入时帐号不存在则自动注册
            AccountDetail accountDetail = accountRegistryService.createAccount(principal, "");
            userDetail = new UserDetail(accountDetail);
        }

        userDetail.setLoginType(LoginType.mobileCode.getValue());
        return new AccountAuthToken(authToken, userDetail);
    }
}
