// Copyright 2022 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.tasks.tab_management;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.when;

import android.content.Context;

import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.SmallTest;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.robolectric.annotation.Config;

import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.base.test.util.HistogramWatcher;
import org.chromium.chrome.browser.tabmodel.TabModel;
import org.chromium.chrome.browser.tabmodel.TabModelSelector;
import org.chromium.chrome.tab_ui.R;
import org.chromium.ui.modaldialog.DialogDismissalCause;
import org.chromium.ui.modaldialog.ModalDialogManager;
import org.chromium.ui.modaldialog.ModalDialogProperties;
import org.chromium.ui.modelutil.PropertyModel;

/**
 * Tests fo the close all tabs dialog to confirm the close all tabs action.
 *
 * This test assumes that the Modal Dialog UI component is well tested responds correctly to user
 * inputs. It only tests the logic and properties of the dialog.
 */
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class CloseAllTabsDialogUnitTest {
    private static class MockModalDialogManager extends ModalDialogManager {
        private PropertyModel mDialogModel;
        private @ModalDialogManager.ModalDialogType int mDialogType;

        public MockModalDialogManager() {
            super(Mockito.mock(ModalDialogManager.Presenter.class), 0);
        }

        @Override
        public void showDialog(PropertyModel model,
                @ModalDialogManager.ModalDialogType int dialogType, boolean showNext) {
            mDialogModel = model;
            mDialogType = dialogType;
        }

        public PropertyModel getDialogModel() {
            return mDialogModel;
        }

        public @ModalDialogManager.ModalDialogType int getDialogType() {
            return mDialogType;
        }

        public void simulateButtonClick(@ModalDialogProperties.ButtonType int buttonType) {
            mDialogModel.get(ModalDialogProperties.CONTROLLER).onClick(mDialogModel, buttonType);
        }

        @Override
        public void dismissDialog(PropertyModel model, @DialogDismissalCause int dismissalCause) {
            assertEquals(model, mDialogModel);
            mDialogModel.get(ModalDialogProperties.CONTROLLER)
                    .onDismiss(mDialogModel, dismissalCause);
            mDialogModel = null;
            mDialogType = -1;
        }
    }

    private Context mContext;
    private MockModalDialogManager mMockModalDialogManager;
    private boolean mRunnableCalled;

    @Mock
    private TabModelSelector mTabModelSelectorMock;
    @Mock
    private TabModel mTabModelMock;
    @Mock
    private TabModel mIncognitoTabModelMock;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        when(mTabModelMock.isIncognito()).thenReturn(false);
        when(mIncognitoTabModelMock.isIncognito()).thenReturn(true);
        when(mTabModelSelectorMock.getModel(false)).thenReturn(mTabModelMock);
        when(mTabModelSelectorMock.getModel(true)).thenReturn(mIncognitoTabModelMock);
        mContext = ApplicationProvider.getApplicationContext();
        mMockModalDialogManager = new MockModalDialogManager();
        mRunnableCalled = false;
    }

    private ModalDialogManager getModalDialogManager() {
        return mMockModalDialogManager;
    }

    private void verifyModel(boolean isIncognito) {
        assertEquals(
                ModalDialogManager.ModalDialogType.APP, mMockModalDialogManager.getDialogType());

        final PropertyModel model = mMockModalDialogManager.getDialogModel();
        assertNotNull(model);
        assertEquals(mContext.getString(isIncognito ? R.string.close_all_tabs_dialog_title_incognito
                                                    : R.string.close_all_tabs_dialog_title),
                model.get(ModalDialogProperties.TITLE));
        assertEquals(CloseAllTabsDialog.getDialogDescriptionString(mContext, mTabModelSelectorMock),
                model.get(ModalDialogProperties.MESSAGE_PARAGRAPH_1));
        assertEquals(mContext.getString(R.string.menu_close_all_tabs),
                model.get(ModalDialogProperties.POSITIVE_BUTTON_TEXT));
        assertEquals(mContext.getString(R.string.cancel),
                model.get(ModalDialogProperties.NEGATIVE_BUTTON_TEXT));
        assertTrue(model.get(ModalDialogProperties.CANCEL_ON_TOUCH_OUTSIDE));
        assertEquals(ModalDialogProperties.ButtonStyles.PRIMARY_FILLED_NEGATIVE_OUTLINE,
                model.get(ModalDialogProperties.BUTTON_STYLES));
    }

    private void verifyDismissed() {
        assertNull(mMockModalDialogManager.getDialogModel());
        assertEquals(-1, mMockModalDialogManager.getDialogType());
    }

    private void setUpCurrentModelAndIncognitoCount(boolean incognito, int incognitoTabCount) {
        when(mTabModelSelectorMock.getCurrentModel())
                .thenReturn(incognito ? mIncognitoTabModelMock : mTabModelMock);
        when(mIncognitoTabModelMock.getCount()).thenReturn(incognitoTabCount);
    }

    @Test
    @SmallTest
    public void testDialog_RegularMode() {
        final boolean isIncognito = false;
        setUpCurrentModelAndIncognitoCount(isIncognito, 0);
        CloseAllTabsDialog.show(mContext, this::getModalDialogManager, mTabModelSelectorMock,
                () -> { mRunnableCalled = true; });
        verifyModel(isIncognito);
        HistogramWatcher histograms =
                HistogramWatcher.newBuilder()
                        .expectNoRecords("Tab.CloseAllTabsDialog.ClosedAllTabs.Incognito")
                        .expectBooleanRecord(
                                "Tab.CloseAllTabsDialog.ClosedAllTabs.NonIncognito", true)
                        .build();

        mMockModalDialogManager.simulateButtonClick(ModalDialogProperties.ButtonType.POSITIVE);
        assertTrue(mRunnableCalled);
        verifyDismissed();
        histograms.assertExpected();
    }

    @Test
    @SmallTest
    public void testDialog_RegularMode_OneIncognitoTab() {
        final boolean isIncognito = false;
        setUpCurrentModelAndIncognitoCount(isIncognito, 1);
        CloseAllTabsDialog.show(mContext, this::getModalDialogManager, mTabModelSelectorMock,
                () -> { mRunnableCalled = true; });
        verifyModel(isIncognito);
        HistogramWatcher histograms =
                HistogramWatcher.newBuilder()
                        .expectNoRecords("Tab.CloseAllTabsDialog.ClosedAllTabs.Incognito")
                        .expectBooleanRecord(
                                "Tab.CloseAllTabsDialog.ClosedAllTabs.NonIncognito", true)
                        .build();

        mMockModalDialogManager.simulateButtonClick(ModalDialogProperties.ButtonType.POSITIVE);
        assertTrue(mRunnableCalled);
        verifyDismissed();
        histograms.assertExpected();
    }

    @Test
    @SmallTest
    public void testDialog_RegularMode_TwoIncognitoTabs() {
        final boolean isIncognito = false;
        setUpCurrentModelAndIncognitoCount(isIncognito, 2);
        CloseAllTabsDialog.show(mContext, this::getModalDialogManager, mTabModelSelectorMock,
                () -> { mRunnableCalled = true; });
        verifyModel(isIncognito);
        HistogramWatcher histograms =
                HistogramWatcher.newBuilder()
                        .expectNoRecords("Tab.CloseAllTabsDialog.ClosedAllTabs.Incognito")
                        .expectBooleanRecord(
                                "Tab.CloseAllTabsDialog.ClosedAllTabs.NonIncognito", true)
                        .build();

        mMockModalDialogManager.simulateButtonClick(ModalDialogProperties.ButtonType.POSITIVE);
        assertTrue(mRunnableCalled);
        verifyDismissed();
        histograms.assertExpected();
    }

    @Test
    @SmallTest
    public void testDialog_IncognitoMode() {
        final boolean isIncognito = true;
        setUpCurrentModelAndIncognitoCount(isIncognito, 1);
        CloseAllTabsDialog.show(mContext, this::getModalDialogManager, mTabModelSelectorMock,
                () -> { mRunnableCalled = true; });
        verifyModel(isIncognito);
        HistogramWatcher histograms =
                HistogramWatcher.newBuilder()
                        .expectNoRecords("Tab.CloseAllTabsDialog.ClosedAllTabs.NonIncognito")
                        .expectBooleanRecord("Tab.CloseAllTabsDialog.ClosedAllTabs.Incognito", true)
                        .build();

        mMockModalDialogManager.simulateButtonClick(ModalDialogProperties.ButtonType.POSITIVE);
        assertTrue(mRunnableCalled);
        verifyDismissed();
        histograms.assertExpected();
    }

    @Test
    @SmallTest
    public void testDismissButton() {
        final boolean isIncognito = true;
        setUpCurrentModelAndIncognitoCount(isIncognito, 1);
        CloseAllTabsDialog.show(mContext, this::getModalDialogManager, mTabModelSelectorMock,
                () -> { mRunnableCalled = true; });
        verifyModel(isIncognito);
        HistogramWatcher histograms =
                HistogramWatcher.newBuilder()
                        .expectBooleanRecord(
                                "Tab.CloseAllTabsDialog.ClosedAllTabs.Incognito", false)
                        .expectNoRecords("Tab.CloseAllTabsDialog.ClosedAllTabs.NonIncognito")
                        .build();

        mMockModalDialogManager.simulateButtonClick(ModalDialogProperties.ButtonType.NEGATIVE);
        assertFalse(mRunnableCalled);
        verifyDismissed();
        histograms.assertExpected();
    }

    @Test
    @SmallTest
    public void testDismissNoButton() {
        final boolean isIncognito = false;
        setUpCurrentModelAndIncognitoCount(isIncognito, 0);
        CloseAllTabsDialog.show(mContext, this::getModalDialogManager, mTabModelSelectorMock,
                () -> { mRunnableCalled = true; });
        verifyModel(isIncognito);
        HistogramWatcher histograms =
                HistogramWatcher.newBuilder()
                        .expectNoRecords("Tab.CloseAllTabsDialog.ClosedAllTabs.Incognito")
                        .expectBooleanRecord(
                                "Tab.CloseAllTabsDialog.ClosedAllTabs.NonIncognito", false)
                        .build();

        mMockModalDialogManager.dismissDialog(mMockModalDialogManager.getDialogModel(),
                DialogDismissalCause.NAVIGATE_BACK_OR_TOUCH_OUTSIDE);
        assertFalse(mRunnableCalled);
        verifyDismissed();
        histograms.assertExpected();
    }
}
