/*
 * Copyright (c) 2016 Uber Technologies, Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package com.uber.sdk.android.core.auth;

import android.content.Context;
import android.content.SharedPreferences;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import android.webkit.CookieManager;

import com.uber.sdk.core.auth.AccessToken;
import com.uber.sdk.core.auth.AccessTokenStorage;
import com.uber.sdk.core.auth.Scope;

import java.util.Collection;
import java.util.Set;

/**
 * Manages the storage of {@link AccessToken}s.
 */
public class AccessTokenManager implements AccessTokenStorage {

    public static final String ACCESS_TOKEN_DEFAULT_KEY = "defaultAccessToken";
    private static final String ACCESS_TOKEN_SHARED_PREFERENCES = "uberSdkAccessTokenConfig";
    private static final String DATE_KEY_SUFFIX = "_date";
    private static final String EXPIRED_EMPTY_LOGGED_IN_COOKIE = "logged_in=;expires=Thu, 01 Jan 1970 00:00:01 GMT";
    private static final String EXPIRED_EMPTY_SESSION_COOKIE = "session=;expires=Thu, 01 Jan 1970 00:00:01 GMT";
    private static final String LOGIN_COOKIE_URL = "https://.login.uber.com";
    private static final String TOKEN_KEY_SUFFIX = "_token";
    private static final String REFRESH_TOKEN_KEY_SUFFIX = "_refresh_token";
    private static final String TOKEN_TYPE_KEY_SUFFIX = "_token_type";
    private static final String SCOPES_KEY_SUFFIX = "_scopes";
    private static final String UBER_COOKIE_URL = ".uber.com";

    @NonNull
    private final SharedPreferences sharedPreferences;

    @NonNull
    private final CookieUtils cookieUtils;

    @NonNull
    private final String accessTokenKey;

    /**
     *
     * @param context for access {@link SharedPreferences} to save {@link AccessToken}
     */
    public AccessTokenManager(@NonNull Context context) {
        this(context, ACCESS_TOKEN_DEFAULT_KEY);
    }

    /**
     * Instantiate {@link AccessTokenManager} and override accessTokenKey.
     * Use one instance per token.
     *
     * @param context
     * @param accessTokenKey key for storage
     */
    public AccessTokenManager(@NonNull Context context, @NonNull String accessTokenKey) {
        this(context, new CookieUtils(), accessTokenKey);
    }

    @VisibleForTesting
    AccessTokenManager(@NonNull Context context,
                       @NonNull CookieUtils cookieManagerUtil) {
        this(context, cookieManagerUtil, ACCESS_TOKEN_DEFAULT_KEY);
    }

    AccessTokenManager(@NonNull Context context,
                               @NonNull CookieUtils cookieManagerUtil,
                               @NonNull String accessTokenKey) {
        sharedPreferences = context.getApplicationContext()
                .getSharedPreferences(ACCESS_TOKEN_SHARED_PREFERENCES, Context.MODE_PRIVATE);
        cookieUtils = cookieManagerUtil;
        this.accessTokenKey = accessTokenKey;
    }

    /**
     * Gets an {@link AccessToken} stored.
     */
    @Override
    @Nullable
    public AccessToken getAccessToken() {

        long expiresIn;
        String token;
        Set<String> scopesString;
        String refreshToken;
        String tokenType;

        try {
            expiresIn = sharedPreferences.getLong(accessTokenKey + DATE_KEY_SUFFIX, -1);
            token = sharedPreferences.getString(accessTokenKey + TOKEN_KEY_SUFFIX, null);
            scopesString = sharedPreferences.getStringSet(accessTokenKey + SCOPES_KEY_SUFFIX, null);
            refreshToken = sharedPreferences.getString(accessTokenKey + REFRESH_TOKEN_KEY_SUFFIX, null);
            tokenType = sharedPreferences.getString(accessTokenKey + TOKEN_TYPE_KEY_SUFFIX, null);
        } catch (ClassCastException ignored) {
            return null;
        }

        if (expiresIn == -1 || token == null || scopesString == null) {
            // Return null, if we can't parse it this token is considered unsaved.
            return null;
        }

        Collection<Scope> scopes;
        try {
            scopes = AuthUtils.stringCollectionToScopeCollection(scopesString);
        } catch (IllegalArgumentException ignored) {
            return null;
        }

        return new AccessToken(expiresIn, scopes, token, refreshToken, tokenType);
    }

    /**
     * Removes the {@link AccessToken} stored.
     **/
    @Override
    public void removeAccessToken() {
        cookieUtils.clearUberCookies();

        sharedPreferences.edit().remove(accessTokenKey + DATE_KEY_SUFFIX).apply();
        sharedPreferences.edit().remove(accessTokenKey + TOKEN_KEY_SUFFIX).apply();
        sharedPreferences.edit().remove(accessTokenKey + SCOPES_KEY_SUFFIX).apply();
        sharedPreferences.edit().remove(accessTokenKey + REFRESH_TOKEN_KEY_SUFFIX).apply();
        sharedPreferences.edit().remove(accessTokenKey + TOKEN_TYPE_KEY_SUFFIX).apply();
    }

    /**
     * Stores the {@link AccessToken}.
     *
     */
    @Override
    public void setAccessToken(@NonNull AccessToken accessToken) {
        sharedPreferences.edit().putLong(accessTokenKey + DATE_KEY_SUFFIX, accessToken.getExpiresIn()).apply();
        sharedPreferences.edit().putString(accessTokenKey + TOKEN_KEY_SUFFIX, accessToken.getToken()).apply();
        sharedPreferences.edit().putStringSet(accessTokenKey + SCOPES_KEY_SUFFIX,
                AuthUtils.scopeCollectionToStringSet(accessToken.getScopes())).apply();
        sharedPreferences.edit().putString(accessTokenKey + REFRESH_TOKEN_KEY_SUFFIX, accessToken.getRefreshToken()).apply();
        sharedPreferences.edit().putString(accessTokenKey + TOKEN_TYPE_KEY_SUFFIX, accessToken.getTokenType()).apply();
    }

    @VisibleForTesting
    static class CookieUtils {

        /**
         * Clears Uber logged_in and session cookies.
         */
        void clearUberCookies() {
            CookieManager cookieManager = CookieManager.getInstance();

            cookieManager.setCookie(UBER_COOKIE_URL, EXPIRED_EMPTY_LOGGED_IN_COOKIE);
            cookieManager.setCookie(LOGIN_COOKIE_URL, EXPIRED_EMPTY_SESSION_COOKIE);

            cookieManager.removeExpiredCookie();
        }
    }
}