/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.apache.cxf.systest.jaxrs.security.oauth;

import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.cxf.jaxrs.impl.MetadataMap;
import org.apache.cxf.rs.security.oauth.data.AccessToken;
import org.apache.cxf.rs.security.oauth.data.AccessTokenRegistration;
import org.apache.cxf.rs.security.oauth.data.AuthorizationInput;
import org.apache.cxf.rs.security.oauth.data.Client;
import org.apache.cxf.rs.security.oauth.data.OAuthPermission;
import org.apache.cxf.rs.security.oauth.data.RequestToken;
import org.apache.cxf.rs.security.oauth.data.RequestTokenRegistration;
import org.apache.cxf.rs.security.oauth.data.Token;
import org.apache.cxf.rs.security.oauth.provider.MD5SequenceGenerator;
import org.apache.cxf.rs.security.oauth.provider.OAuthDataProvider;
import org.apache.cxf.rs.security.oauth.provider.OAuthServiceException;

public class MemoryOAuthDataProvider implements OAuthDataProvider {

    private static final ConcurrentHashMap<String, OAuthPermission> AVAILABLE_PERMISSIONS =
        new ConcurrentHashMap<>();

    static {
        AVAILABLE_PERMISSIONS
                .put("read_info", new OAuthPermission("read_info", "Read your personal information",
                        Collections.singletonList("ROLE_USER")));
        AVAILABLE_PERMISSIONS.put("modify_info",
                new OAuthPermission("modify_info", "Modify your personal information",
                                    Collections.singletonList("ROLE_ADMIN")));
    }

    protected ConcurrentHashMap<String, Client> clientAuthInfo = new ConcurrentHashMap<>();

    protected MetadataMap<String, String> userRegisteredClients = new MetadataMap<>();

    protected MetadataMap<String, String> userAuthorizedClients = new MetadataMap<>();

    protected ConcurrentHashMap<String, Token> oauthTokens = new ConcurrentHashMap<>();

    protected MD5SequenceGenerator tokenGenerator =
        new MD5SequenceGenerator();

    public MemoryOAuthDataProvider() {
        Client client = new Client(OAuthTestUtils.CLIENT_ID,
            OAuthTestUtils.CLIENT_SECRET,
            OAuthTestUtils.APPLICATION_NAME,
            OAuthTestUtils.CALLBACK);
        clientAuthInfo.put(OAuthTestUtils.CLIENT_ID, client);
    }

    private List<OAuthPermission> getPermissionsInfo(List<String> requestPermissions) {
        List<OAuthPermission> permissions = new ArrayList<>();
        for (String requestScope : requestPermissions) {
            OAuthPermission oAuthPermission = AVAILABLE_PERMISSIONS.get(requestScope);
            permissions.add(oAuthPermission);
        }

        return permissions;
    }

    public Client getClient(String consumerKey) {
        return clientAuthInfo.get(consumerKey);
    }

    public RequestToken createRequestToken(RequestTokenRegistration reg) throws OAuthServiceException {
        String token = generateToken();
        String tokenSecret = generateToken();

        RequestToken reqToken = new RequestToken(reg.getClient(), token, tokenSecret,
                                                 reg.getLifetime(), reg.getIssuedAt());
        reqToken.setScopes(getPermissionsInfo(reg.getScopes()));

        oauthTokens.put(token, reqToken);
        return reqToken;
    }

    public RequestToken getRequestToken(String tokenString) throws OAuthServiceException {

        return (RequestToken)oauthTokens.get(tokenString);
    }

    public String finalizeAuthorization(AuthorizationInput input) throws
            OAuthServiceException {
        RequestToken requestToken = input.getToken();
        requestToken.setVerifier(generateToken());
        return requestToken.getVerifier();
    }

    public AccessToken createAccessToken(AccessTokenRegistration reg) throws
            OAuthServiceException {

        RequestToken requestToken = reg.getRequestToken();

        Client client = requestToken.getClient();
        requestToken = getRequestToken(requestToken.getTokenKey());

        String accessTokenString = generateToken();
        String tokenSecretString = generateToken();

        AccessToken accessToken = new AccessToken(client, accessTokenString, tokenSecretString,
                                                  3600, System.currentTimeMillis() / 1000);

        accessToken.setScopes(requestToken.getScopes());

        synchronized (oauthTokens) {
            oauthTokens.remove(requestToken.getTokenKey());
            oauthTokens.put(accessTokenString, accessToken);
            synchronized (userAuthorizedClients) {
                userAuthorizedClients.add(client.getConsumerKey(), client.getConsumerKey());
            }
        }

        return accessToken;
    }

    public AccessToken getAccessToken(String accessToken) throws OAuthServiceException {
        return  (AccessToken)oauthTokens.get(accessToken);
    }

    public void removeToken(Token t) {

        for (Token token : oauthTokens.values()) {
            Client authNInfo = token.getClient();
            if (t.getClient().getConsumerKey().equals(authNInfo.getConsumerKey())) {
                oauthTokens.remove(token.getTokenKey());
                break;
            }
        }

    }

    protected String generateToken() throws OAuthServiceException {
        try {
            return tokenGenerator.generate(UUID.randomUUID().toString().getBytes(StandardCharsets.UTF_8));
        } catch (Exception e) {
            throw new OAuthServiceException("Unable to create token ", e.getCause());
        }
    }

    public void setClientAuthInfo(Map<String, Client> clientAuthInfo) {
        this.clientAuthInfo.putAll(clientAuthInfo);
    }
}
