// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.chrome.browser.autofill.settings;

import static org.chromium.chrome.browser.autofill.AutofillUiUtils.getCardIcon;
import static org.chromium.chrome.browser.autofill.AutofillUiUtils.getSettingsPageIconHeightId;
import static org.chromium.chrome.browser.autofill.AutofillUiUtils.getSettingsPageIconWidthId;

import android.content.Context;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.hardware.biometrics.BiometricManager;
import android.os.Build;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;

import androidx.annotation.Nullable;
import androidx.core.hardware.fingerprint.FingerprintManagerCompat;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceScreen;

import org.chromium.base.ApiCompatibilityUtils;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.autofill.AutofillEditorBase;
import org.chromium.chrome.browser.autofill.PersonalDataManager;
import org.chromium.chrome.browser.autofill.PersonalDataManager.CreditCard;
import org.chromium.chrome.browser.device_reauth.DeviceAuthRequester;
import org.chromium.chrome.browser.device_reauth.ReauthenticatorBridge;
import org.chromium.chrome.browser.feedback.FragmentHelpAndFeedbackLauncher;
import org.chromium.chrome.browser.feedback.HelpAndFeedbackLauncher;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.payments.ServiceWorkerPaymentAppBridge;
import org.chromium.chrome.browser.settings.ChromeManagedPreferenceDelegate;
import org.chromium.chrome.browser.settings.SettingsLauncherImpl;
import org.chromium.components.autofill.VirtualCardEnrollmentState;
import org.chromium.components.browser_ui.settings.ChromeSwitchPreference;
import org.chromium.components.browser_ui.settings.SettingsLauncher;
import org.chromium.components.browser_ui.styles.SemanticColorUtils;
import org.chromium.components.payments.AndroidPaymentAppFactory;

/**
 * Autofill credit cards fragment, which allows the user to edit credit cards and control
 * payment apps.
 */
public class AutofillPaymentMethodsFragment
        extends PreferenceFragmentCompat implements PersonalDataManager.PersonalDataManagerObserver,
                                                    FragmentHelpAndFeedbackLauncher {
    static final String PREF_MANDATORY_REAUTH = "mandatory_reauth";
    private static final String PREF_PAYMENT_APPS = "payment_apps";

    private HelpAndFeedbackLauncher mHelpAndFeedbackLauncher;

    @Nullable
    private ReauthenticatorBridge mReauthenticatorBridge;

    @Override
    public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
        getActivity().setTitle(R.string.autofill_payment_methods);
        setHasOptionsMenu(true);
        PreferenceScreen screen = getPreferenceManager().createPreferenceScreen(getStyledContext());
        // Suppresses unwanted animations while Preferences are removed from and re-added to the
        // screen.
        screen.setShouldUseGeneratedIds(false);

        setPreferenceScreen(screen);
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        menu.clear();
        MenuItem help =
                menu.add(Menu.NONE, R.id.menu_id_targeted_help, Menu.NONE, R.string.menu_help);
        help.setIcon(R.drawable.ic_help_and_feedback);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if (item.getItemId() == R.id.menu_id_targeted_help) {
            mHelpAndFeedbackLauncher.show(
                    getActivity(), getActivity().getString(R.string.help_context_autofill), null);
            return true;
        }
        return super.onOptionsItemSelected(item);
    }

    @Override
    public void onResume() {
        super.onResume();
        // Always rebuild our list of credit cards.  Although we could detect if credit cards are
        // added or deleted, the credit card summary (number) might be different.  To be safe, we
        // update all.
        rebuildPage();
    }

    private void rebuildPage() {
        getPreferenceScreen().removeAll();
        getPreferenceScreen().setOrderingAsAdded(true);

        ChromeSwitchPreference autofillSwitch =
                new ChromeSwitchPreference(getStyledContext(), null);
        autofillSwitch.setTitle(R.string.autofill_enable_credit_cards_toggle_label);
        autofillSwitch.setSummary(R.string.autofill_enable_credit_cards_toggle_sublabel);
        autofillSwitch.setChecked(PersonalDataManager.isAutofillCreditCardEnabled());
        autofillSwitch.setOnPreferenceChangeListener((preference, newValue) -> {
            PersonalDataManager.setAutofillCreditCardEnabled((boolean) newValue);
            return true;
        });
        autofillSwitch.setManagedPreferenceDelegate(new ChromeManagedPreferenceDelegate() {
            @Override
            public boolean isPreferenceControlledByPolicy(Preference preference) {
                return PersonalDataManager.isAutofillCreditCardManaged();
            }

            @Override
            public boolean isPreferenceClickDisabled(Preference preference) {
                return PersonalDataManager.isAutofillCreditCardManaged()
                        && !PersonalDataManager.isAutofillCreditCardEnabled();
            }
        });
        getPreferenceScreen().addPreference(autofillSwitch);

        if (isBiometricAvailable()
                && PersonalDataManager.getInstance().isFidoAuthenticationAvailable()) {
            ChromeSwitchPreference fidoAuthSwitch =
                    new ChromeSwitchPreference(getStyledContext(), null);
            fidoAuthSwitch.setTitle(R.string.enable_credit_card_fido_auth_label);
            fidoAuthSwitch.setSummary(R.string.enable_credit_card_fido_auth_sublabel);
            fidoAuthSwitch.setChecked(PersonalDataManager.isAutofillCreditCardFidoAuthEnabled());
            fidoAuthSwitch.setOnPreferenceChangeListener((preference, newValue) -> {
                PersonalDataManager.setAutofillCreditCardFidoAuthEnabled((boolean) newValue);
                return true;
            });
            getPreferenceScreen().addPreference(fidoAuthSwitch);
        }

        // TODO(crbug.com/1427216): Confirm with Product on the order of the toggles.
        if (ChromeFeatureList.isEnabled(
                    ChromeFeatureList.AUTOFILL_ENABLE_PAYMENTS_MANDATORY_REAUTH)) {
            if (mReauthenticatorBridge == null) {
                mReauthenticatorBridge = ReauthenticatorBridge.create(
                        DeviceAuthRequester.PAYMENT_METHODS_REAUTH_IN_SETTINGS);
            }
            // We don't show the Reauth toggle when Autofill credit card is disabled or the device
            // doesn't have biometric auth.
            if (PersonalDataManager.isAutofillCreditCardEnabled()
                    && mReauthenticatorBridge.canUseAuthenticationWithBiometric()) {
                ChromeSwitchPreference mandatoryReauthSwitch =
                        new ChromeSwitchPreference(getStyledContext(), null);
                mandatoryReauthSwitch.setTitle(
                        R.string.autofill_settings_page_enable_payment_method_mandatory_reauth_label);
                mandatoryReauthSwitch.setSummary(
                        R.string.autofill_settings_page_enable_payment_method_mandatory_reauth_sublabel);
                mandatoryReauthSwitch.setChecked(
                        PersonalDataManager.isAutofillPaymentMethodsMandatoryReauthEnabled());
                mandatoryReauthSwitch.setKey(PREF_MANDATORY_REAUTH);
                mandatoryReauthSwitch.setOnPreferenceChangeListener(
                        this::onMandatoryReauthSwitchToggled);
                getPreferenceScreen().addPreference(mandatoryReauthSwitch);
            }
        }

        for (CreditCard card : PersonalDataManager.getInstance().getCreditCardsForSettings()) {
            // Add a preference for the credit card.
            Preference card_pref = new Preference(getStyledContext());
            // Make the card_pref multi-line, since cards with long nicknames won't fit on a single
            // line.
            card_pref.setSingleLineTitle(false);
            card_pref.setTitle(card.getCardLabel());

            // Show virtual card enabled status for enrolled cards, expiration date otherwise.
            if (card.getVirtualCardEnrollmentState() == VirtualCardEnrollmentState.ENROLLED
                    && ChromeFeatureList.isEnabled(
                            ChromeFeatureList.AUTOFILL_ENABLE_VIRTUAL_CARD_METADATA)) {
                card_pref.setSummary(R.string.autofill_virtual_card_enrolled_text);
            } else {
                card_pref.setSummary(card.getFormattedExpirationDate(getActivity()));
            }

            // Set card icon. It can be either a custom card art or a network icon.
            card_pref.setIcon(getCardIcon(getStyledContext(), card.getCardArtUrl(),
                    card.getIssuerIconDrawableId(), getSettingsPageIconWidthId(),
                    getSettingsPageIconHeightId(), R.dimen.card_art_corner_radius,
                    ChromeFeatureList.isEnabled(ChromeFeatureList.AUTOFILL_ENABLE_CARD_ART_IMAGE)));

            if (card.getIsLocal()) {
                if (ChromeFeatureList.isEnabled(
                            ChromeFeatureList.AUTOFILL_ENABLE_PAYMENTS_MANDATORY_REAUTH)
                        && PersonalDataManager.isAutofillPaymentMethodsMandatoryReauthEnabled()) {
                    // When mandatory reauth is enabled, we require additional authentication before
                    // user can view/edit local card.
                    card_pref.setOnPreferenceClickListener(
                            this::authenticateBeforeShowingLocalCardEditDialog);
                } else {
                    card_pref.setFragment(AutofillLocalCardEditor.class.getName());
                }
            } else {
                card_pref.setFragment(AutofillServerCardEditor.class.getName());
                if (ChromeFeatureList.isEnabled(
                            ChromeFeatureList.AUTOFILL_ENABLE_VIRTUAL_CARD_METADATA)) {
                    card_pref.setWidgetLayoutResource(R.layout.autofill_server_data_label);
                } else {
                    card_pref.setWidgetLayoutResource(R.layout.autofill_server_data_text_label);
                }
            }

            Bundle args = card_pref.getExtras();
            args.putString(AutofillEditorBase.AUTOFILL_GUID, card.getGUID());
            getPreferenceScreen().addPreference(card_pref);
        }

        // Add 'Add credit card' button. Tap of it brings up card editor which allows users type in
        // new credit cards.
        if (PersonalDataManager.isAutofillCreditCardEnabled()) {
            Preference add_card_pref = new Preference(getStyledContext());
            Drawable plusIcon = ApiCompatibilityUtils.getDrawable(getResources(), R.drawable.plus);
            plusIcon.mutate();
            plusIcon.setColorFilter(SemanticColorUtils.getDefaultControlColorActive(getContext()),
                    PorterDuff.Mode.SRC_IN);
            add_card_pref.setIcon(plusIcon);
            add_card_pref.setTitle(R.string.autofill_create_credit_card);
            add_card_pref.setFragment(AutofillLocalCardEditor.class.getName());
            getPreferenceScreen().addPreference(add_card_pref);
        }

        // Add the link to payment apps only after the credit card list is rebuilt.
        Preference payment_apps_pref = new Preference(getStyledContext());
        payment_apps_pref.setTitle(R.string.payment_apps_title);
        payment_apps_pref.setFragment(AndroidPaymentAppsFragment.class.getCanonicalName());
        payment_apps_pref.setShouldDisableView(true);
        payment_apps_pref.setKey(PREF_PAYMENT_APPS);
        getPreferenceScreen().addPreference(payment_apps_pref);
        refreshPaymentAppsPrefForAndroidPaymentApps(payment_apps_pref);
    }

    private Context getStyledContext() {
        return getPreferenceManager().getContext();
    }

    private void refreshPaymentAppsPrefForAndroidPaymentApps(Preference pref) {
        if (AndroidPaymentAppFactory.hasAndroidPaymentApps()) {
            setPaymentAppsPrefStatus(pref, true);
        } else {
            refreshPaymentAppsPrefForServiceWorkerPaymentApps(pref);
        }
    }

    private void refreshPaymentAppsPrefForServiceWorkerPaymentApps(Preference pref) {
        ServiceWorkerPaymentAppBridge.hasServiceWorkerPaymentApps(
                new ServiceWorkerPaymentAppBridge.HasServiceWorkerPaymentAppsCallback() {
                    @Override
                    public void onHasServiceWorkerPaymentAppsResponse(boolean hasPaymentApps) {
                        setPaymentAppsPrefStatus(pref, hasPaymentApps);
                    }
                });
    }

    private void setPaymentAppsPrefStatus(Preference pref, boolean enabled) {
        if (enabled) {
            pref.setSummary(null);
            pref.setEnabled(true);
        } else {
            pref.setSummary(R.string.payment_no_apps_summary);
            pref.setEnabled(false);
        }
    }

    private boolean isBiometricAvailable() {
        // Only Android versions 9 and above are supported.
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
            return false;
        }

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            BiometricManager biometricManager =
                    getStyledContext().getSystemService(BiometricManager.class);
            return biometricManager != null
                    && biometricManager.canAuthenticate() == BiometricManager.BIOMETRIC_SUCCESS;
        } else {
            // For API level < Q, we will use FingerprintManagerCompat to check enrolled
            // fingerprints. Note that for API level lower than 23, FingerprintManagerCompat behaves
            // like no fingerprint hardware and no enrolled fingerprints.
            FingerprintManagerCompat fingerprintManager =
                    FingerprintManagerCompat.from(getStyledContext());
            return fingerprintManager != null && fingerprintManager.isHardwareDetected()
                    && fingerprintManager.hasEnrolledFingerprints();
        }
    }

    /** Handle preference changes from mandatory reauth toggle */
    private boolean onMandatoryReauthSwitchToggled(Preference preference, Object newValue) {
        assert preference.getKey().equals(PREF_MANDATORY_REAUTH);
        // We require user authentication every time user trys to change this
        // preference. Set useLastValidAuth=false to skip the grace period.
        mReauthenticatorBridge.reauthenticate(success -> {
            if (success) {
                // Only set the preference to new value when user passes the
                // authentication.
                PersonalDataManager.setAutofillPaymentMethodsMandatoryReauth((boolean) newValue);
            }
        }, /*useLastValidAuth=*/false);
        // Returning false here holds the toggle to still display the old value while
        // waiting for biometric auth. Once biometric is completed (either succeed or
        // fail), OnResume will reload the page with the pref value, which will switch
        // to the new value if biometric auth succeeded.
        return false;
    }

    /**
     * Trigger additional authentication before user can view/edit local card. When authentication
     * passes, we show local card edit dialog. Stays on this page if authentication fails/cancelled.
     */
    private boolean authenticateBeforeShowingLocalCardEditDialog(Preference preference) {
        // mReauthenticatorBridge should be initiated already when determining whether to show the
        // mandatory reauth toggle.
        assert mReauthenticatorBridge != null;
        mReauthenticatorBridge.reauthenticate(success -> {
            if (success) {
                // Manually trigger the local card edit dialog after user passes the authentication.
                SettingsLauncher settingsLauncher = new SettingsLauncherImpl();
                settingsLauncher.launchSettingsActivity(
                        getActivity(), AutofillLocalCardEditor.class, preference.getExtras());
            }
        }, /*useLastValidAuth=*/false);
        return true;
    }

    @Override
    public void onPersonalDataChanged() {
        rebuildPage();
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        PersonalDataManager.getInstance().registerDataObserver(this);
    }

    @Override
    public void onDestroyView() {
        PersonalDataManager.getInstance().unregisterDataObserver(this);
        super.onDestroyView();
    }

    @Override
    public void setHelpAndFeedbackLauncher(HelpAndFeedbackLauncher helpAndFeedbackLauncher) {
        mHelpAndFeedbackLauncher = helpAndFeedbackLauncher;
    }
}
