package com.backendsys.modules.app.service.impl;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.json.JSONUtil;
import com.backendsys.exception.CustException;
import com.backendsys.modules.app.dao.AppUserDao;
import com.backendsys.modules.app.entity.AppAuth;
import com.backendsys.modules.app.entity.AppUser;
import com.backendsys.modules.app.service.AppAuthService;
import com.backendsys.modules.common.config.redis.utils.RedisUtil;
import com.backendsys.modules.common.config.security.entity.SecurityAppUserInfo;
import com.backendsys.modules.common.config.security.utils.*;
import com.backendsys.modules.system.entity.TokenCatch;
import com.backendsys.modules.system.service.SysCommonService;
import com.backendsys.utils.response.ResultEnum;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Service
public class AppAuthServiceImpl implements AppAuthService {

    @Autowired
    private JwtUtil jwtUtil;
    @Autowired
    private TokenUtil tokenUtil;
    @Autowired
    private RedisUtil redisUtil;
    @Autowired
    private CaptchaUtil captchaUtil;
    @Autowired
    private LockStatusUtil lockStatusUtil;
    @Autowired
    private HttpRequestUtil httpRequestUtil;

    @Autowired
    private AppUserDao appUserDao;
    @Autowired
    private SysCommonService sysCommonService;

    @Value("${tencent.sms.debug}")
    private String SMS_DEBUG;
    @Value("${CAPTCHA_DURATION}")
    private Integer CAPTCHA_DURATION;
    @Value("${REDIS_LOGIN_TOKEN_PREFIX}")
    private String REDIS_LOGIN_TOKEN_PREFIX;
    @Value("${spring.application.name}")
    private String APPLICATION_NAME;
    private String redisKeyOfLogin = APPLICATION_NAME + "-app-sms-login";
    private String redisKeyOfRegister = APPLICATION_NAME + "-app-sms-register";
    private String redisKeyOfLoginFail = APPLICATION_NAME + "-app-login-error";
    private String redisKeyOfRegisterFail = APPLICATION_NAME + "-app-register-error";


    // [方法] 登录失败 (errMsg: 错误提示文本, phone: 手机号码, intercept: 是否拦截)
    private void loginFail(String errMsg, String phone, Boolean isIntercept) {
        // 删除图形验证码
        redisUtil.delete(httpRequestUtil.getKaptchaKey());
        // 添加登录错误的冻结标记
        if (isIntercept) lockStatusUtil.setLockStatus(redisKeyOfLoginFail, phone);
        throw new CustException(errMsg, ResultEnum.INVALID_CREDENTIALS.getCode());
    }

    // [方法] 登录成功
    private AppUser loginSuccess(AppUser appUser) {

        // 删除图形验证码缓存
        redisUtil.delete(httpRequestUtil.getKaptchaKey());

        // 删除旧的登录缓存
        tokenUtil.deleteRedisLoginToken(appUser.getLast_login_uuid());

        // 判断用户是否启用
        Integer status = appUser.getStatus();
        if (status != null && status.equals(-1)) throw new CustException("该用户已被禁用，请与客服联系");

        // 判断用户是否已删除
        Integer del_flag = appUser.getDel_flag();
        if (del_flag != null && del_flag.equals(1)) throw new CustException("当前用户不可用，请与客服联系");

        // 设置 最后一次的登录信息 (uuid, ip, 登录时间)
        String uuid = Convert.toStr(UUID.randomUUID());
        appUser.setUser_id(appUser.getId());
        appUser.setLast_login_uuid(uuid);
        appUser.setLast_login_ip(httpRequestUtil.getIpAddr());
        appUser.setLast_login_time(DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss"));
        appUserDao.updateById(appUser);


        // [系统配置] 微信用户默认登录过期时间(小时)
        Integer APP_USER_LOGIN_DURATION_DEFAULT = Convert.toInt(sysCommonService.getCommonByTag("APP_USER_LOGIN_DURATION_DEFAULT"));
        // 将小时转换为毫秒
        Long DEFAULT_MILLISECONDS = APP_USER_LOGIN_DURATION_DEFAULT * DateUnit.HOUR.getMillis();
        Integer token_duration_hours = Convert.toInt(DEFAULT_MILLISECONDS / 3600000L);

        Date token_expiration = new Date((new Date()).getTime() + DEFAULT_MILLISECONDS);
        appUser.setToken_expiration(DateUtil.format(token_expiration, "yyyy-MM-dd HH:mm:ss"));
        appUser.setRole("APP_USER");

        // 生成 Token
        SecurityAppUserInfo securityUserInfo = JSONUtil.toBean(JSONUtil.parseObj(appUser), SecurityAppUserInfo.class);
        String token = jwtUtil.createAppJwtToken(securityUserInfo);
        String token_redis_key = REDIS_LOGIN_TOKEN_PREFIX + uuid;
        appUser.setToken(token);

        // [Redis] 将 Token 存入缓存
        TokenCatch tokenCatch = new TokenCatch(token, null);
        redisUtil.setCacheObject(token_redis_key, JSONUtil.toJsonStr(tokenCatch), token_duration_hours, TimeUnit.HOURS);

        return appUser;
    }

    /**
     * APP登录 (用户名登录)
     */
    @Override
    public AppUser login(AppAuth appAuth) {

        String phone = appAuth.getPhone();
        String password = appAuth.getPassword();
        String captcha = appAuth.getCaptcha();

        // 判断是否处于登录错误的冻结状态 (2分钟内错误5次，则出现冻结提示)
        lockStatusUtil.checkLockStatus(redisKeyOfLoginFail, phone);

        // 判断图形验证码是否正确
        if (!captchaUtil.isCaptchaValid(captcha, httpRequestUtil.getKaptchaKey())) {
            loginFail("验证码错误", phone, false);
            return null;
        }

        // [Method] 判断 用户 是否存在 && 密码是否正确
        AppUser appUser = appUserDao.selectOne(new LambdaQueryWrapper<AppUser>().eq(AppUser::getPhone, phone));
        if (appUser == null) {
            // [登录失败] 用户不存在
            loginFail("手机号码或密码错误", phone, true);
            return null;
        } else {
            // [登录失败] 密码不正确
            BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
            if (!encoder.matches(password, appUser.getPassword())) {
                loginFail("手机号码或密码错误", phone, true);
            }
            // [登录成功]
            return loginSuccess(appUser);
        }

    }

}
