package com.luotuo.config;

import com.luotuo.custom.AuthorityInfo;
import com.luotuo.service.PrivilegeConfigService;
import com.luotuo.service.UserPrivilegeService;
import com.luotuo.service.UserService;
import com.luotuo.service.UserWechatService;
import com.luotuo.user.entity.PrivilegeConfig;
import com.luotuo.user.entity.User;
import com.luotuo.user.entity.UserPrivilege;
import com.luotuo.user.entity.UserWechat;
import com.luotuo.utils.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.security.oauth2.resource.AuthoritiesExtractor;
import org.springframework.boot.autoconfigure.security.oauth2.resource.FixedAuthoritiesExtractor;
import org.springframework.boot.autoconfigure.security.oauth2.resource.FixedPrincipalExtractor;
import org.springframework.boot.autoconfigure.security.oauth2.resource.PrincipalExtractor;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.client.OAuth2RestOperations;
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
import org.springframework.security.oauth2.client.resource.BaseOAuth2ProtectedResourceDetails;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.OAuth2Request;
import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

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

/**
 * Created by luotuo on 17-9-27.
 */
public class MyUserInfoTokenServices implements MyResourceServerTokenServices {
    protected final Log logger = LogFactory.getLog(this.getClass());
    private final String userInfoEndpointUrl;
    private final String clientId;
    private OAuth2RestOperations restTemplate;
    private String tokenType = "Bearer";
    private AuthoritiesExtractor authoritiesExtractor = new FixedAuthoritiesExtractor();
    private PrincipalExtractor principalExtractor = new FixedPrincipalExtractor();


    private UserService userService;
    private UserWechatService userWechatService;
    private UserPrivilegeService userPrivilegeService;
    private PrivilegeConfigService privilegeConfigService;

    public MyUserInfoTokenServices(String userInfoEndpointUrl,
                                   String clientId,
                                   UserService userService,
                                   UserWechatService userWechatService,
                                   UserPrivilegeService userPrivilegeService,
                                   PrivilegeConfigService privilegeConfigService) {
        this.userInfoEndpointUrl = userInfoEndpointUrl;
        this.clientId = clientId;
        this.userService = userService;
        this.userWechatService = userWechatService;
        this.userPrivilegeService = userPrivilegeService;
        this.privilegeConfigService = privilegeConfigService;
    }

    public void setTokenType(String tokenType) {
        this.tokenType = tokenType;
    }

    public void setRestTemplate(OAuth2RestOperations restTemplate) {
        this.restTemplate = restTemplate;
    }

    public void setAuthoritiesExtractor(AuthoritiesExtractor authoritiesExtractor) {
        Assert.notNull(authoritiesExtractor, "AuthoritiesExtractor must not be null");
        this.authoritiesExtractor = authoritiesExtractor;
    }

    public void setPrincipalExtractor(PrincipalExtractor principalExtractor) {
        Assert.notNull(principalExtractor, "PrincipalExtractor must not be null");
        this.principalExtractor = principalExtractor;
    }

    public OAuth2Authentication loadAuthentication(String accessToken,
                                                   String ip) throws AuthenticationException, InvalidTokenException {
        Map<String, Object> map = this.getMap(this.userInfoEndpointUrl, accessToken);
        for (Map.Entry<String, Object> entry : map.entrySet()) {
            System.out.println("key == " + entry.getKey() + " value == " + entry.getValue());
        }
        if(map.containsKey("error")) {
            if(this.logger.isDebugEnabled()) {
                this.logger.debug("userinfo returned error: " + map.get("error"));
            }

            throw new InvalidTokenException(accessToken);
        } else {
            return this.extractAuthentication(map, ip);
        }
    }

    public OAuth2Authentication loadAuthentication(String accessToken) throws AuthenticationException, InvalidTokenException {
        return this.loadAuthentication(accessToken, null);
    }

    private OAuth2Authentication extractAuthentication(Map<String, Object> map, String ip) {
        // Here, we try to get user's privileges
        // First, try to find user by openid
        // if no user was found, try to find user in user wechat repository,
        // if now userwechat was found, save it, then add it into user repository
        // TODO: need basic privileges
        User user = null;
        UserWechat userWechat = null;
        List<GrantedAuthority> authorities = new ArrayList<>();
        if (map.containsKey("openid")) {
            String openId = (String)map.get("openid");
            System.out.println("openId == " + openId);
            user = userService.getUserByOpenId(openId);
            if (user == null) {
                // Try to find userwechat
                userWechat = userWechatService.getByOpenId(openId);
                if (userWechat == null) {
                    userWechat = new UserWechat(map);
                    userWechat = userWechatService.save(userWechat);
                    user = new User(userWechat);
                    user.setHas_join(0);
                }
            }
            user.setLast_ip(ip);
            user = userService.save(user);
            System.out.println("user name == " + user.getId());
            map.put("username", user.getId());
            List<UserPrivilege> userPrivileges = userPrivilegeService.findByUserId(user.getId());
            List<Long> privilegeIds = new ArrayList<>();
            for (UserPrivilege p : userPrivileges) {
                privilegeIds.add(p.getPrivilege_id());
            }
            if (!privilegeIds.isEmpty()) {
                List<PrivilegeConfig> privilegeConfigs = privilegeConfigService.getByIds(privilegeIds);
                for (PrivilegeConfig p : privilegeConfigs) {
                    if (StringUtils.isNotBlank(p.getUrl())) {
                        AuthorityInfo authorityInfo = new AuthorityInfo(p.getUrl());
                        authorities.add(authorityInfo);
                    }
                }
            }
        } else {
            authorities = this.authoritiesExtractor.extractAuthorities(map);
        }
        Object principal = this.getPrincipal(map);
        if (authorities != null)
            System.out.println("authorities == " + authorities.size());
        OAuth2Request request = new OAuth2Request((Map)null,
                this.clientId,
                (Collection)null,
                true,
                (Set)null, (Set)null, (String)null, (Set)null, (Map)null);
        UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(principal, "N/A", authorities);
        token.setDetails(map);
        return new OAuth2Authentication(request, token);
    }

    private OAuth2Authentication extractAuthentication(Map<String, Object> map) {
        return this.extractAuthentication(map, null);
    }

    protected Object getPrincipal(Map<String, Object> map) {
        Object principal = this.principalExtractor.extractPrincipal(map);
        return principal == null?"unknown":principal;
    }

    public OAuth2AccessToken readAccessToken(String accessToken) {
        throw new UnsupportedOperationException("Not supported: read access token");
    }

    private Map<String, Object> getMap(String path, String accessToken) {
        if(this.logger.isDebugEnabled()) {
            this.logger.debug("Getting user info from: " + path);
        }
        try {
            OAuth2RestOperations restTemplate = this.restTemplate;
            if(restTemplate == null) {
                BaseOAuth2ProtectedResourceDetails resource = new BaseOAuth2ProtectedResourceDetails();
                resource.setClientId(this.clientId);
                restTemplate = new OAuth2RestTemplate(resource);
            }
            OAuth2AccessToken existingToken = ((OAuth2RestOperations)restTemplate).getOAuth2ClientContext().getAccessToken();
            if(existingToken == null || !accessToken.equals(existingToken.getValue())) {
                DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken(accessToken);
                token.setTokenType(this.tokenType);
                ((OAuth2RestOperations)restTemplate).getOAuth2ClientContext().setAccessToken(token);
            }

            return (Map)((OAuth2RestOperations)restTemplate).getForEntity(path, Map.class, new Object[0]).getBody();
        } catch (Exception var6) {
            this.logger.warn("Could not fetch user details: " + var6.getClass() + ", " + var6.getMessage());
            return Collections.singletonMap("error", "Could not fetch user details");
        }
    }
}