// Copyright 2020 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.optimization_guide;

import static org.mockito.AdditionalMatchers.aryEq;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

import androidx.test.filters.SmallTest;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import org.chromium.base.test.BaseJUnit4ClassRunner;
import org.chromium.base.test.UiThreadTest;
import org.chromium.base.test.util.Batch;
import org.chromium.base.test.util.Feature;
import org.chromium.base.test.util.JniMocker;
import org.chromium.components.optimization_guide.OptimizationGuideDecision;
import org.chromium.components.optimization_guide.proto.CommonTypesProto.RequestContext;
import org.chromium.components.optimization_guide.proto.HintsProto.OptimizationType;
import org.chromium.url.GURL;

import java.util.Arrays;

/**
 * Unit tests for OptimizationGuideBridge.
 */
@RunWith(BaseJUnit4ClassRunner.class)
@Batch(Batch.UNIT_TESTS)
public class OptimizationGuideBridgeUnitTest {
    private static final String TEST_URL = "https://testurl.com/";
    private static final String TEST_URL2 = "https://testurl2.com/";

    @Rule
    public JniMocker mocker = new JniMocker();

    @Mock
    OptimizationGuideBridge.Natives mOptimizationGuideBridgeJniMock;

    @Mock
    OptimizationGuideBridge.OptimizationGuideCallback mCallbackMock;

    @Mock
    OptimizationGuideBridge.OnDemandOptimizationGuideCallback mOnDemandCallbackMock;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        mocker.mock(OptimizationGuideBridgeJni.TEST_HOOKS, mOptimizationGuideBridgeJniMock);
    }

    @Test
    @SmallTest
    @UiThreadTest
    @Feature({"OptimizationHints"})
    public void testRegisterOptimizationTypes() {
        OptimizationGuideBridge bridge = new OptimizationGuideBridge(1);
        bridge.registerOptimizationTypes(Arrays.asList(new OptimizationType[] {
                OptimizationType.PERFORMANCE_HINTS, OptimizationType.DEFER_ALL_SCRIPT}));
        verify(mOptimizationGuideBridgeJniMock, times(1))
                .registerOptimizationTypes(eq(1L), aryEq(new int[] {6, 5}));
    }

    @Test
    @SmallTest
    @UiThreadTest
    @Feature({"OptimizationHints"})
    public void testRegisterOptimizationTypes_withoutNativeBridge() {
        OptimizationGuideBridge bridge = new OptimizationGuideBridge(0);
        bridge.registerOptimizationTypes(Arrays.asList(new OptimizationType[] {
                OptimizationType.PERFORMANCE_HINTS, OptimizationType.DEFER_ALL_SCRIPT}));
        verify(mOptimizationGuideBridgeJniMock, never())
                .registerOptimizationTypes(anyLong(), any(int[].class));
    }

    @Test
    @SmallTest
    @UiThreadTest
    @Feature({"OptimizationHints"})
    public void testRegisterOptimizationTypes_noOptimizationTypes() {
        OptimizationGuideBridge bridge = new OptimizationGuideBridge(1);
        bridge.registerOptimizationTypes(null);
        verify(mOptimizationGuideBridgeJniMock, times(1))
                .registerOptimizationTypes(eq(1L), aryEq(new int[0]));
    }

    @Test
    @SmallTest
    @UiThreadTest
    @Feature({"OptimizationHints"})
    public void testCanApplyOptimization_withoutNativeBridge() {
        GURL gurl = new GURL(TEST_URL);
        OptimizationGuideBridge bridge = new OptimizationGuideBridge(0);

        bridge.canApplyOptimization(gurl, OptimizationType.PERFORMANCE_HINTS, mCallbackMock);

        verify(mOptimizationGuideBridgeJniMock, never())
                .canApplyOptimization(anyLong(), any(), anyInt(),
                        any(OptimizationGuideBridge.OptimizationGuideCallback.class));
        verify(mCallbackMock)
                .onOptimizationGuideDecision(eq(OptimizationGuideDecision.UNKNOWN), isNull());
    }

    @Test
    @SmallTest
    @UiThreadTest
    @Feature({"OptimizationHints"})
    public void testCanApplyOptimization() {
        GURL gurl = new GURL(TEST_URL);
        OptimizationGuideBridge bridge = new OptimizationGuideBridge(1);

        bridge.canApplyOptimization(gurl, OptimizationType.PERFORMANCE_HINTS, mCallbackMock);

        verify(mOptimizationGuideBridgeJniMock, times(1))
                .canApplyOptimization(eq(1L), eq(gurl), eq(6), eq(mCallbackMock));
    }

    @Test
    @SmallTest
    @UiThreadTest
    @Feature({"OptimizationHints"})
    public void testCanApplyOptimizationOnDemand_withoutNativeBridge() {
        GURL gurl = new GURL(TEST_URL);
        GURL gurl2 = new GURL(TEST_URL2);
        OptimizationGuideBridge bridge = new OptimizationGuideBridge(0);

        bridge.canApplyOptimizationOnDemand(
                Arrays.asList(new GURL[] {gurl, gurl2}),
                Arrays.asList(new OptimizationType[] {
                        OptimizationType.PERFORMANCE_HINTS, OptimizationType.DEFER_ALL_SCRIPT}),
                RequestContext.CONTEXT_NEW_TAB_PAGE, mOnDemandCallbackMock);

        verify(mOptimizationGuideBridgeJniMock, never())
                .canApplyOptimizationOnDemand(anyLong(), any(), any(), anyInt(),
                        any(OptimizationGuideBridge.OnDemandOptimizationGuideCallback.class));
        verify(mOnDemandCallbackMock)
                .onOnDemandOptimizationGuideDecision(eq(gurl),
                        eq(OptimizationType.DEFER_ALL_SCRIPT),
                        eq(OptimizationGuideDecision.UNKNOWN), isNull());
        verify(mOnDemandCallbackMock)
                .onOnDemandOptimizationGuideDecision(eq(gurl),
                        eq(OptimizationType.PERFORMANCE_HINTS),
                        eq(OptimizationGuideDecision.UNKNOWN), isNull());
        verify(mOnDemandCallbackMock)
                .onOnDemandOptimizationGuideDecision(eq(gurl2),
                        eq(OptimizationType.DEFER_ALL_SCRIPT),
                        eq(OptimizationGuideDecision.UNKNOWN), isNull());
        verify(mOnDemandCallbackMock)
                .onOnDemandOptimizationGuideDecision(eq(gurl2),
                        eq(OptimizationType.PERFORMANCE_HINTS),
                        eq(OptimizationGuideDecision.UNKNOWN), isNull());
    }

    @Test
    @SmallTest
    @UiThreadTest
    @Feature({"OptimizationHints"})
    public void testCanApplyOptimizationOnDemand() {
        GURL gurl = new GURL(TEST_URL);
        GURL gurl2 = new GURL(TEST_URL2);
        OptimizationGuideBridge bridge = new OptimizationGuideBridge(1);

        bridge.canApplyOptimizationOnDemand(
                Arrays.asList(new GURL[] {gurl, gurl2}),
                Arrays.asList(new OptimizationType[] {
                        OptimizationType.PERFORMANCE_HINTS, OptimizationType.DEFER_ALL_SCRIPT}),
                RequestContext.CONTEXT_NEW_TAB_PAGE, mOnDemandCallbackMock);

        verify(mOptimizationGuideBridgeJniMock, times(1))
                .canApplyOptimizationOnDemand(eq(1L), aryEq(new GURL[] {gurl, gurl2}),
                        aryEq(new int[] {6, 5}), eq(9), eq(mOnDemandCallbackMock));
    }
}
