package com.jumeirah.common.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.jumeirah.common.entity.AppUser;
import com.jumeirah.common.enums.StateEnum;
import com.jumeirah.common.mapper.AppUserMapper;
import com.jumeirah.common.param.AppUserPageParam;
import com.jumeirah.common.param.LoginParam;
import com.jumeirah.common.param.RegisterParam;
import com.jumeirah.common.service.AppUserService;
import com.jumeirah.common.vo.AppUserQueryVo;
import com.jumeirah.common.vo.LoginSysUserTokenVo;
import io.geekidea.springbootplus.config.properties.JwtProperties;
import io.geekidea.springbootplus.config.properties.SpringBootPlusProperties;
import io.geekidea.springbootplus.framework.common.api.ApiCode;
import io.geekidea.springbootplus.framework.common.api.ApiResult;
import io.geekidea.springbootplus.framework.common.service.impl.BaseServiceImpl;
import io.geekidea.springbootplus.framework.core.pagination.PageInfo;
import io.geekidea.springbootplus.framework.core.pagination.Paging;
import io.geekidea.springbootplus.framework.shiro.cache.AppLoginRedisService;
import io.geekidea.springbootplus.framework.shiro.jwt.JwtToken;
import io.geekidea.springbootplus.framework.shiro.util.JwtUtil;
import io.geekidea.springbootplus.framework.shiro.util.SaltUtil;
import io.geekidea.springbootplus.framework.shiro.vo.LoginSysUserVo;
import io.geekidea.springbootplus.framework.util.PasswordUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.Duration;
import java.util.concurrent.TimeUnit;

/**
 * APP用户 服务实现类
 *
 * @author wei
 * @since 2020-09-23
 */
@Slf4j
@Service
public class AppUserServiceImpl extends BaseServiceImpl<AppUserMapper, AppUser> implements AppUserService {

    @Autowired
    private RedisTemplate redisTemplate;

    @Lazy
    @Autowired
    private AppLoginRedisService appLoginRedisService;

    @Lazy
    @Autowired
    private JwtProperties jwtProperties;

    @Autowired
    private SpringBootPlusProperties springBootPlusProperties;

    @Autowired
    private AppUserMapper appUserMapper;

    @Override
    public ApiResult<LoginSysUserTokenVo> register(RegisterParam registerParam, String language) {
        return null;
    }

    @Override
    public ApiResult<LoginSysUserTokenVo> login(LoginParam loginParam, String language) {


        // 校验验证码
//            checkVerifyCode(loginParam.getVerifyToken(), loginParam.getCode());

        String username = loginParam.getUsername();

        // 从数据库中获取登录用户信息
        AppUser appUser = appUserMapper.selectOne(new QueryWrapper<AppUser>(new AppUser().setUsername(username)));

        if (appUser == null) {

            log.error("登录失败,loginParam:{}", loginParam);
//            throw new AuthenticationException("用户名或密码错误");
            return ApiResult.fail(ApiCode.PWD_OR_USERNAME_ERROR, language);
        }
        if (StateEnum.DISABLE.getCode().equals(appUser.getState())) {
            throw new AuthenticationException("账号已禁用");
        }

        // 实际项目中，前端传过来的密码应先加密
        // 原始密码明文：123456
        // 原始密码前端加密：sha256(123456)
        // 后台加密规则：sha256(sha256(123456) + salt)
        String encryptPassword = PasswordUtil.encrypt(loginParam.getPassword(), appUser.getSalt());
        if (!encryptPassword.equals(appUser.getPassword())) {
            return ApiResult.fail(ApiCode.PWD_OR_USERNAME_ERROR, language);
        }

//         将系统用户对象转换成登录用户对象
        LoginSysUserVo loginSysUserVo = new LoginSysUserVo();
        loginSysUserVo.setUsername(username);


        // 获取数据库中保存的盐值
        String newSalt = SaltUtil.getSalt(appUser.getSalt(), jwtProperties);

        // 生成token字符串并返回
        Long expireSecond = jwtProperties.getExpireSecond();
        String token = JwtUtil.generateToken(username, newSalt, Duration.ofSeconds(expireSecond));
        log.debug("token:{}", token);

        // 创建AuthenticationToken
        JwtToken jwtToken = JwtToken.build(token, username, newSalt, expireSecond,"app");

        boolean enableShiro = springBootPlusProperties.getShiro().isEnable();
        if (enableShiro) {
            // 从SecurityUtils里边创建一个 subject
            Subject subject = SecurityUtils.getSubject();
            // 执行认证登录
            subject.login(jwtToken);
        } else {
            log.warn("未启用Shiro");
        }

        // 缓存登录信息到Redis
        appLoginRedisService.cacheLoginInfo(jwtToken, loginSysUserVo);
        log.debug("登录成功,username:{}", username);

        // 缓存登录信息到redis
        String tokenSha256 = DigestUtils.sha256Hex(token);
        redisTemplate.opsForValue().set(tokenSha256, loginSysUserVo, 1, TimeUnit.DAYS);

        // 返回token和登录用户信息对象
        LoginSysUserTokenVo loginSysUserTokenVo = new LoginSysUserTokenVo();
        loginSysUserTokenVo.setToken(token);
        loginSysUserTokenVo.setLoginSysUserVo(loginSysUserVo);

        // 设置token响应头
//        response.setHeader(JwtTokenUtil.getTokenName(), loginSysUserTokenVo.getToken());

        return ApiResult.ok(loginSysUserTokenVo, language);

    }

    @Override
    public ApiResult<LoginSysUserTokenVo> phoneLogin(RegisterParam registerParam, String language) {
        return null;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean saveAppUser(AppUser appUser) throws Exception {
        return super.save(appUser);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean updateAppUser(AppUser appUser) throws Exception {
        return super.updateById(appUser);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean deleteAppUser(Long id) throws Exception {
        return super.removeById(id);
    }

    @Override
    public AppUserQueryVo getAppUserById(Long id) throws Exception {
        return appUserMapper.getAppUserById(id);
    }

    @Override
    public Paging<AppUserQueryVo> getAppUserPageList(AppUserPageParam appUserPageParam) throws Exception {
        Page<AppUserQueryVo> page = new PageInfo<>(appUserPageParam, OrderItem.desc(getLambdaColumn(AppUser::getCreateTime)));
        IPage<AppUserQueryVo> iPage = appUserMapper.getAppUserPageList(page, appUserPageParam);
        return new Paging<AppUserQueryVo>(iPage);
    }

}
