/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed 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 com.android.tv.settings.users;

import android.content.Context;
import android.content.pm.UserInfo;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;

import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockPatternUtils.RequestThrottledException;
import com.android.internal.widget.LockscreenCredential;

import java.util.Arrays;

/**
 * Utilities for saving restricted profile PINs. This class binds to a
 * {@link RestrictedProfilePinService} to store and retrieve pins.
 */
public class RestrictedProfilePinStorage {
    private static final String TAG = RestrictedProfilePinStorage.class.getSimpleName();

    private final int mOwnerUserId;
    private final LockPatternUtils mLockPatternUtils;

    private final RestrictedProfilePinServiceConnection mConnection;

    /**
     * Return a new instance of RestrictedProfilePinStorage.
     */
    public static RestrictedProfilePinStorage newInstance(Context context) {
        LockPatternUtils lpu = new LockPatternUtils(context);

        UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE);
        UserInfo currentUserInfo = um.getUserInfo(context.getUserId());

        final int ownerUserId = findRestrictedProfileParent(currentUserInfo);

        RestrictedProfilePinServiceConnection connection =
                new RestrictedProfilePinServiceConnection(context);
        return new RestrictedProfilePinStorage(lpu, ownerUserId, connection);
    }

    @VisibleForTesting
    RestrictedProfilePinStorage(LockPatternUtils lpu, int ownerUserId,
            RestrictedProfilePinServiceConnection connection) {
        mLockPatternUtils = lpu;
        mOwnerUserId = ownerUserId;
        mConnection = connection;
    }

    /** Binds the pin service to be used later for all pin operations. To be called before any
     * other use of this class. */
    public void bind() {
        mConnection.bindPinService();
    }

    /** Unbinds the pin service. */
    public void unbind() {
        mConnection.unbindPinService();
    }

    /**
     * Set PIN password for the profile.
     * @param pin New PIN password
     * @param originalPin Original PIN password
     */
    boolean setPin(@NonNull String pin, String originalPin) {
        if (!isPinSet() || isPinCorrect(originalPin)) {
            setPinInternal(pin);
            clearLockPassword(originalPin);
            return true;
        } else {
            Log.w(TAG, "Unable to validate the original PIN");
            return false;
        }
    }

    /**
     * Delete PIN password for the profile.
     */
    boolean deletePin(@NonNull String oldPin) {
        if (isPinCorrect(oldPin)) {
            deletePinInternal();
            clearLockPassword(oldPin);
            return true;
        } else {
            Log.w(TAG, "Unable to validate the original PIN");
            return false;
        }
    }

    /**
     * Validate PIN password for the profile.
     */
    boolean isPinCorrect(@NonNull String pin) {
        return isPinSetInternal()
                ? isPinCorrectInternal(pin)
                : !hasLockscreenSecurity() || checkPasswordLegacy(pin);
    }

    /**
     * Check if there is a PIN password set on the profile.
     */
    public boolean isPinSet() {
        return isPinSetInternal() || hasLockscreenSecurity();
    }

    private boolean setPinInternal(String pin) {
        if (pin == null) {
            Log.e(TAG, "Attempt to set restricted profile pin to null. Aborting...");
            return false;
        }

        try {
            getPinService().setPin(pin);
            return true;
        } catch (RemoteException e) {
            Log.w(TAG, "Could not set pin due to remote exception");
            e.printStackTrace();
            return false;
        }
    }

    private boolean deletePinInternal() {
        try {
            getPinService().deletePin();
            return true;
        } catch (RemoteException e) {
            Log.w(TAG, "Could not delete pin due to remote exception");
            e.printStackTrace();
            return false;
        }
    }

    private boolean isPinCorrectInternal(String originalPin) {
        try {
            return getPinService().isPinCorrect(originalPin);
        } catch (RemoteException e) {
            Log.w(TAG, "Could not check pin due to remote exception");
            e.printStackTrace();
        }

        return false;
    }

    private boolean isPinSetInternal() {
        try {
            return getPinService().isPinSet();
        } catch (RemoteException e) {
            e.printStackTrace();
        }

        return true;
    }

    private IRestrictedProfilePinService getPinService() throws RemoteException {
        return mConnection.getPinService();
    }

    private void clearLockPassword(String oldPinString) {
        try (LockscreenCredential oldPin = LockscreenCredential.createPinOrNone(oldPinString)) {
            mLockPatternUtils.setLockCredential(LockscreenCredential.createNone(), oldPin,
                    mOwnerUserId);
        }
    }

    private boolean checkPasswordLegacy(String pin) {
        try (LockscreenCredential credential = LockscreenCredential.createPin(pin)) {
            Log.i(TAG, "checkPasswordLegacy " + Arrays.toString(credential.getCredential()));
            boolean response = mLockPatternUtils.checkCredential(credential, mOwnerUserId, null);
            Log.i(TAG, "response " + response);
            if (response) {
                // Copy PIN to internal storage.
                setPinInternal(pin);
            }
            return response;
        } catch (RequestThrottledException e) {
            Log.e(TAG, "Unable to check password for unlocking the user", e);
        }
        return false; // Interpret this as an incorrect pin and ask for the pin again.
    }

    private boolean hasLockscreenSecurity() {
        return mLockPatternUtils.isSecure(mOwnerUserId);
    }

    private static int findRestrictedProfileParent(UserInfo currentUserInfo) {
        final int ownerUserId;

        if (!currentUserInfo.isRestricted()) {
            ownerUserId = currentUserInfo.id;
        } else if (currentUserInfo.restrictedProfileParentId == UserInfo.NO_PROFILE_GROUP_ID) {
            ownerUserId = UserHandle.USER_SYSTEM;
        } else {
            ownerUserId = currentUserInfo.restrictedProfileParentId;
        }

        return ownerUserId;
    }
}
