package com.brframework.commonsecurity.core;

import com.alibaba.fastjson.JSON;
import com.brframework.commonsecurity.exception.ConflictTokenException;
import com.brframework.commonsecurity.exception.ExpiredTokenException;
import com.brframework.commonsecurity.utils.JwtUtils;
import com.brframework.commonweb.exception.HandleException;
import com.google.common.base.Strings;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.Data;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

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

/**
 * @Author xu
 * @Date 2018/1/31 0031 下午 7:32
 * 用户安全服务
 */
@Data
public class SecurityUserDetailsService implements UserDetailsService {

    public static final String KEY_PREFIX = "token-";
    public static final String KEY_USER_PREFIX = "token-user-list-";
    /** 账号在其他地方登录了 */
    public static final String TOKEN_CONFLICT = "error_token_conflict";

    /** 该JWT的签发者 */
    private String iss;
    /** 有效期 （秒） */
    private long exp;
    /** 密钥 */
    private String secret;
    /** 一个签发者中一个用户只能拥有一个JWT */
    private boolean singleUser;
    /** 使用Redis保存token */
    private StringRedisTemplate redis;

    public SecurityUserDetailsService(String iss, long exp, String secret,
                                      boolean singleUser, StringRedisTemplate redis){
        this.iss = iss;
        this.exp = exp;
        this.secret = secret;
        this.singleUser = singleUser;
        this.redis = redis;
    }

    @Override
    public UserDetails loadUserByUsername(String token) throws UsernameNotFoundException {
        return new SecurityUserDetails(parse(token));
    }

    public SecuritySubject parse(String token){

        String sub = redis.opsForValue().get(token);
        if(StringUtils.isEmpty(sub)){
            //登录超时，或无效的token
            throw new ExpiredTokenException();
        }

        if(sub.equals(TOKEN_CONFLICT)){
            //在其他地方登录
            throw new ConflictTokenException();
        }

        return JSON.parseObject(sub, SecuritySubject.class);
    }

    public void expiredToken(String username){
        String audKeys = KEY_USER_PREFIX + username;
        //清除所有的key
        Set<Object> keys = redis.opsForHash().keys(audKeys);
        for (Object k : keys) {
            redis.delete(k.toString());
        }
        redis.delete(audKeys);
    }

    /**
     * 生成token
     * @param aud token对应的用户标识
     * @param sub token存放的数据
     * @return
     */
    public String genToken(String aud, SecuritySubject sub){

        String key = KEY_PREFIX + RandomStringUtils.randomAlphabetic(48);
        String audKeys = KEY_USER_PREFIX + aud;

        redis.opsForValue().set(key, JSON.toJSONString(sub), exp, TimeUnit.SECONDS);

        //单用户登录
        if(singleUser){
            //清除所有的key
            Set<Object> keys = redis.opsForHash().keys(audKeys);
            for (Object k : keys) {
                redis.opsForValue().set(k.toString(), TOKEN_CONFLICT);
            }
            redis.delete(audKeys);
        } else {
            //如果大于30条token时，自动全部失效，避免数据爆炸
            Long size = redis.opsForHash().size(audKeys);
            if(size > 30){
                Set<Object> keys = redis.opsForHash().keys(audKeys);
                for (Object k : keys) {
                    redis.delete(k.toString());
                }
                redis.delete(audKeys);
            }
        }
        redis.opsForHash().put(audKeys, key, String.valueOf(System.currentTimeMillis()));

        return key;
    }
}
