/*
 * Copyright (C) 2021 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.car.settings.profiles;

import static android.os.UserManager.DISALLOW_ADD_USER;

import static com.android.car.settings.common.PreferenceController.AVAILABLE;
import static com.android.car.settings.common.PreferenceController.AVAILABLE_FOR_VIEWING;
import static com.android.car.settings.common.PreferenceController.DISABLED_FOR_PROFILE;
import static com.android.car.settings.enterprise.ActionDisabledByAdminDialogFragment.DISABLED_BY_ADMIN_CONFIRM_DIALOG_TAG;
import static com.android.car.settings.enterprise.EnterpriseUtils.hasUserRestrictionByDpm;
import static com.android.car.settings.enterprise.EnterpriseUtils.hasUserRestrictionByUm;

import android.car.Car;
import android.car.user.CarUserManager;
import android.content.Context;
import android.os.UserManager;
import android.widget.Toast;

import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;

import com.android.car.settings.R;
import com.android.car.settings.common.ConfirmationDialogFragment;
import com.android.car.settings.common.ErrorDialog;
import com.android.car.settings.common.FragmentController;
import com.android.car.settings.common.PreferenceController;
import com.android.car.settings.enterprise.EnterpriseUtils;

/**
 * Consolidates adding profile logic into one handler so we can have consistent logic across various
 * parts of the Settings app.
 */
public class AddProfileHandler implements AddNewProfileTask.AddNewProfileListener {

    @VisibleForTesting
    static final String CONFIRM_CREATE_NEW_PROFILE_DIALOG_TAG =
            "com.android.car.settings.profiles.ConfirmCreateNewProfileDialog";
    @VisibleForTesting
    static final String MAX_PROFILES_LIMIT_REACHED_DIALOG_TAG =
            "com.android.car.settings.profiles.MaxProfilesLimitReachedDialog";

    @VisibleForTesting
    AddNewProfileTask mAddNewProfileTask;
    /** Indicates that a task is running. */
    private boolean mIsBusy;

    private final Context mContext;
    private final FragmentController mFragmentController;
    private final PreferenceController mPreferenceController;
    private final Car mCar;
    private CarUserManager mCarUserManager;

    @VisibleForTesting
    ConfirmationDialogFragment.ConfirmListener mConfirmCreateNewProfileListener;

    public AddProfileHandler(Context context, FragmentController fragmentController,
            PreferenceController preferenceController) {
        mContext = context;
        mFragmentController = fragmentController;
        mPreferenceController = preferenceController;
        mCar = Car.createCar(context);
        mCarUserManager = (CarUserManager) mCar.getCarManager(Car.CAR_USER_SERVICE);

        mConfirmCreateNewProfileListener = arguments -> {
            mAddNewProfileTask = new AddNewProfileTask(mContext,
                    mCarUserManager, /* addNewProfileListener= */ this);
            mAddNewProfileTask.execute(mContext.getString(R.string.user_new_user_name));

            mIsBusy = true;
            mPreferenceController.refreshUi();
        };
    }

    /**
     * Handles operations that should happen in host's onCreateInternal().
     * Resets listeners as they can get unregistered with certain configuration changes.
     */
    public void onCreateInternal() {
        ConfirmationDialogFragment.resetListeners(
                (ConfirmationDialogFragment) mFragmentController.findDialogByTag(
                        CONFIRM_CREATE_NEW_PROFILE_DIALOG_TAG),
                mConfirmCreateNewProfileListener,
                /* rejectListener= */ null,
                /* neutralListener= */ null);
    }

    /**
     * Handles operations that should happen in host's onStopInternal().
     */
    public void onStopInternal() {
        mFragmentController.showProgressBar(false);
    }

    /**
     * Handles events that should happen in host's onDestroyInternal().
     */
    public void onDestroyInternal() {
        if (mAddNewProfileTask != null) {
            mAddNewProfileTask.cancel(/* mayInterruptIfRunning= */ false);
        }
        if (mCar != null) {
            mCar.disconnect();
        }
    }

    /**
     * Handles events that should happen in host's updateState() when there is task running.
     */
    public void updateState(Preference preference) {
        preference.setEnabled(!mIsBusy);
        mFragmentController.showProgressBar(mIsBusy);
    }

    @Override
    public void onProfileAddedSuccess() {
        mIsBusy = false;
        mPreferenceController.refreshUi();
    }

    @Override
    public void onProfileAddedFailure() {
        mIsBusy = false;
        mPreferenceController.refreshUi();
        // Display failure dialog.
        mFragmentController.showDialog(
                ErrorDialog.newInstance(R.string.add_user_error_title), /* tag= */ null);
    }

    /**
     *  Determines whether the user manager instance has the permission to add profiles
     *
     * @param userManager UserManager instance to evaluate
     * @return whether the user has permissions to add profiles
     */
    public static boolean canAddProfiles(UserManager userManager) {
        return !userManager.hasUserRestriction(DISALLOW_ADD_USER);
    }

    /**
     * Returns {@code PreferenceController.AVAILABLE} when preference should be available,
     * {@code PreferenceController.DISABLED_FOR_PROFILE} when preference should be unavailable,
     * {@code PreferenceController.AVAILABLE_FOR_VIEWING} when preference is visible but
     * disabled.
     */
    public static int getAddProfilePreferenceAvailabilityStatus(Context context) {
        UserManager um = getUserManager(context);
        if (um.isDemoUser() || canAddProfiles(um)) {
            return AVAILABLE;
        }
        if (hasUserRestrictionByUm(context, DISALLOW_ADD_USER)) {
            return DISABLED_FOR_PROFILE;
        }
        return AVAILABLE_FOR_VIEWING;
    }

    /**
     * Display dialog to add a profile
     */
    public void showAddProfileDialog() {
        ConfirmationDialogFragment dialogFragment =
                ProfilesDialogProvider.getConfirmCreateNewProfileDialogFragment(
                        mContext, mConfirmCreateNewProfileListener, null);

        mFragmentController.showDialog(dialogFragment, CONFIRM_CREATE_NEW_PROFILE_DIALOG_TAG);
    }

    /**
     * Shows a dialog or toast when the Preference is disabled while still visible.
     */
    public void runClickableWhileDisabled() {
        if (hasUserRestrictionByDpm(mContext, DISALLOW_ADD_USER)) {
            // Shows a dialog if this PreferenceController is disabled because there is
            // restriction set from DevicePolicyManager
            showActionDisabledByAdminDialog();
        } else if (!getUserManager(mContext).canAddMoreUsers()) {
            // Shows a dialog if no more profiles can be added because the maximum allowed number
            // is reached
            ConfirmationDialogFragment dialogFragment =
                    ProfilesDialogProvider.getMaxProfilesLimitReachedDialogFragment(mContext,
                            ProfileHelper.getInstance(mContext).getMaxSupportedRealProfiles());
            mFragmentController.showDialog(dialogFragment, MAX_PROFILES_LIMIT_REACHED_DIALOG_TAG);
        } else {
            Toast.makeText(mContext, mContext.getString(R.string.action_unavailable),
                    Toast.LENGTH_LONG).show();
        }
    }

    private void showActionDisabledByAdminDialog() {
        mFragmentController.showDialog(
                EnterpriseUtils.getActionDisabledByAdminDialog(mContext, DISALLOW_ADD_USER),
                DISABLED_BY_ADMIN_CONFIRM_DIALOG_TAG);
    }

    private static UserManager getUserManager(Context context) {
        return context.getSystemService(UserManager.class);
    }

    @VisibleForTesting
    void setCarUserManager(CarUserManager carUserManager) {
        mCarUserManager = carUserManager;
    }
}
