package com.tofly.auth.controller;

import com.tofly.auth.config.Page;
import com.tofly.auth.feign.BaseUserLoginService;
import com.tofly.common.core.constant.CommonConstants;
import com.tofly.common.core.constant.SecurityConstants;
import com.tofly.common.core.entity.ResultRespone;
import com.tofly.common.core.util.StringUtil;
import com.tofly.common.log.annotation.ToFlyAppLog;
import com.tofly.common.oauth.auth.ToflyUser;
import com.tofly.common.oauth.auth.innerCall;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.data.redis.core.ConvertingCursor;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.util.OAuth2Utils;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;

import javax.annotation.PostConstruct;
import java.util.*;

/**
 * @author Administrator
 * @date 2019/9/12  11:06
 */
@RestController
@AllArgsConstructor
@RequestMapping("/token")
@Slf4j
public class TokenController {
    private static final String PROJECT_OAUTH_ACCESS = SecurityConstants.PROJECT_PREFIX + SecurityConstants.OAUTH_PREFIX + "access:";
    private static final String CURRENT = "current";
    private static final String SIZE = "size";
    private final TokenStore tokenStore;
    private final RedisTemplate redisTemplate;

    private final CacheManager cacheManager;

    private final BaseUserLoginService baseUserLoginService;
    /**
     * 令牌管理调用
     *
     * @param token token
     //* @param from  内部调用标志
     */
    @DeleteMapping("/{token}")
    @ApiOperation(tags = "登陆接口",value = "退出登陆")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "token", value = "token", required = true, dataType = "String")
    })
    @ToFlyAppLog(title = "注销登出")
    public ResultRespone<Boolean> removeToken(@PathVariable("token") String token) {
//        if (StringUtil.isBlank(from)) {
//            return null;
//        }
//        return ResultRespone.success(redisTemplate.delete(PROJECT_OAUTH_ACCESS + token));
        RedisTokenStore redisTokenStore=(RedisTokenStore)tokenStore;
        String name = redisTokenStore.readAuthentication(token).getName();
        Cache cache = cacheManager.getCache("user_details");
        cache.put(name,null);
//        redisTokenStore.removeAccessToken(token);
        // 计算当前用户登录时长
        if(!StringUtils.isEmpty(name)){
            ResultRespone<Boolean> booleanResultRespone = baseUserLoginService.updateUserOperationTime(name);
            // 删除回调token
            String key = "tofly_oauth:auth:"+token+"_2";
            redisTemplate.delete(key);
            System.out.println(booleanResultRespone.getResult());
        }

        return ResultRespone.success(true);
    }

    @DeleteMapping("/logout")
    @ApiOperation(tags = "登陆接口",value = "退出登陆")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "userId", value = "userId", required = true, dataType = "Long")
    })
    @ToFlyAppLog(title = "注销登出")
    public ResultRespone<Boolean> removeByUserId(Long userId) {
        ScanOptions options = ScanOptions.scanOptions().match(PROJECT_OAUTH_ACCESS + "*").build();
        RedisSerializer<String> redisSerializer = (RedisSerializer<String>) redisTemplate.getKeySerializer();
        Cursor cursor = (Cursor) redisTemplate.executeWithStickyConnection(redisConnection -> new ConvertingCursor<>(redisConnection.scan(options), redisSerializer::deserialize));
        assert cursor != null;
        while (cursor.hasNext()) {
            String msg=cursor.next().toString();
            String accessToken = StringUtil.subAfter(msg, PROJECT_OAUTH_ACCESS, true);
            OAuth2AccessToken token = tokenStore.readAccessToken(accessToken);
            OAuth2Authentication oAuth2Auth = tokenStore.readAuthentication(token);
            Authentication authentication = oAuth2Auth.getUserAuthentication();
            if (authentication.getPrincipal() instanceof ToflyUser){
                ToflyUser user = (ToflyUser) authentication.getPrincipal();
                if(user.getUserid().intValue()==userId.intValue()){
                    Cache cache = cacheManager.getCache("user_details");
                    cache.put(authentication.getName(),null);
                    tokenStore.removeAccessToken(token);
                    return ResultRespone.success(true);
                }
            }
        }
        return ResultRespone.failed();
    }

    @innerCall
    @DeleteMapping("/remove")
    public ResultRespone<Boolean> removeToken(@RequestBody Map<String, Object> params){
        RedisTokenStore redisTokenStore=(RedisTokenStore)tokenStore;
        Collection<OAuth2AccessToken> client_id = redisTokenStore.findTokensByClientIdAndUserName((String)params.get("client_id"), (String)params.get("username"));
        client_id.forEach(oAuth2AccessToken -> {
            tokenStore.removeAccessToken(oAuth2AccessToken);
        });
        return ResultRespone.success(true);
    }



    /**
     * 查询token
     *
     * @param params 分页参数
     * @param from   标志
     */
    @PostMapping("/page")
    @ApiOperation(tags = "登陆接口",value = "查询所有登录用户")
    public ResultRespone getTokenPage(@RequestBody Map<String, Object> params, @RequestHeader(required = false) String from) {
//        if (StringUtil.isBlank(from)) {
//            return null;
//        }
        List<Map<String, String>> list = new ArrayList<>();
        if (StringUtils.isEmpty((Integer)params.get(CURRENT)) || StringUtils.isEmpty((Integer)params.get(SIZE))) {
            params.put(CURRENT, 1);
            params.put(SIZE, 20);
        }
        //根据分页取参数获对应数据
        List<String> pages = findKeysForPage(PROJECT_OAUTH_ACCESS + "*",
                (Integer)params.get(CURRENT), (Integer)params.get(SIZE));

        for (String page : pages) {
            String accessToken = StringUtil.subAfter(page, PROJECT_OAUTH_ACCESS, true);
            OAuth2AccessToken token = tokenStore.readAccessToken(accessToken);
            Map<String, String> map = new HashMap<>(8);


            map.put(OAuth2AccessToken.TOKEN_TYPE, token.getTokenType());
            map.put(OAuth2AccessToken.ACCESS_TOKEN, token.getValue());
            map.put(OAuth2AccessToken.EXPIRES_IN, token.getExpiresIn() + "");


            OAuth2Authentication oAuth2Auth = tokenStore.readAuthentication(token);
            Authentication authentication = oAuth2Auth.getUserAuthentication();

            map.put(OAuth2Utils.CLIENT_ID, oAuth2Auth.getOAuth2Request().getClientId());
            map.put(OAuth2Utils.GRANT_TYPE, oAuth2Auth.getOAuth2Request().getGrantType());

            if (authentication instanceof UsernamePasswordAuthenticationToken) {
                UsernamePasswordAuthenticationToken authenticationToken = (UsernamePasswordAuthenticationToken) authentication;

                if (authenticationToken.getPrincipal() instanceof ToflyUser) {
                    ToflyUser user = (ToflyUser) authenticationToken.getPrincipal();
                    map.put("username", user.getUsername() + "");
                    map.put("userid",user.getUserid()+"");
                }
            } else if (authentication instanceof PreAuthenticatedAuthenticationToken) {
                //刷新token方式
                PreAuthenticatedAuthenticationToken authenticationToken = (PreAuthenticatedAuthenticationToken) authentication;
                if (authenticationToken.getPrincipal() instanceof ToflyUser) {
                    ToflyUser user = (ToflyUser) authenticationToken.getPrincipal();
                    map.put("username", user.getUsername() + "");
                    map.put("userid",user.getUserid()+"");
                }
            }
            list.add(map);
        }

        Page result = new Page((Integer)params.get(CURRENT), (Integer)params.get(SIZE));
        result.setRecords(list);
        result.setTotal(Long.valueOf(redisTemplate.keys(PROJECT_OAUTH_ACCESS + "*").size()));
        return ResultRespone.success(result);

    }


    @GetMapping("/getUserByToken/{token}")
    @ApiOperation(tags = "登录接口",value = "根据Token获取当前用户信息")
    public  ResultRespone getUserByToken(@PathVariable("token") String token){
        String key = "tofly_oauth:auth:"+token+"_2";
        String username=(String) redisTemplate.opsForValue().get(key);
        ToflyUser toflyUser = null;
        if(!StringUtils.isEmpty(username)) {
            Cache cache = cacheManager.getCache("user_details");
            if (cache != null && cache.get(username) != null && cache.get(username).get() != null) {
                toflyUser = (ToflyUser) cache.get(username).get();
            }
        }
        return ResultRespone.success(toflyUser);
    }

    private List<String> findKeysForPage(String patternKey, int pageNum, int pageSize) {
        ScanOptions options = ScanOptions.scanOptions().match(patternKey).build();
        RedisSerializer<String> redisSerializer = (RedisSerializer<String>) redisTemplate.getKeySerializer();
        Cursor cursor = (Cursor) redisTemplate.executeWithStickyConnection(redisConnection -> new ConvertingCursor<>(redisConnection.scan(options), redisSerializer::deserialize));
        List<String> result = new ArrayList<>();
        int tmpIndex = 0;
        int startIndex = (pageNum - 1) * pageSize;
        int end = pageNum * pageSize;

        assert cursor != null;
        while (cursor.hasNext()) {
            if (tmpIndex >= startIndex && tmpIndex < end) {
                result.add(cursor.next().toString());
                tmpIndex++;
                continue;
            }
            if (tmpIndex >= end) {
                break;
            }
            tmpIndex++;
            cursor.next();
        }
        return result;
    }

}
