package net.openid.appauth.browser;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.eq;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import androidx.browser.customtabs.CustomTabsCallback;
import androidx.browser.customtabs.CustomTabsClient;
import androidx.browser.customtabs.CustomTabsService;
import androidx.browser.customtabs.CustomTabsServiceConnection;
import androidx.browser.customtabs.CustomTabsSession;
import java.util.List;
import net.openid.appauth.BuildConfig;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;

@RunWith(RobolectricTestRunner.class)
@Config(constants = BuildConfig.class, sdk=16)
public class CustomTabManagerTest {

    private static final String BROWSER_PACKAGE_NAME = "com.example.browser";

    @Mock
    Context mContext;

    @Captor
    ArgumentCaptor<Intent> mConnectIntentCaptor;

    @Captor
    ArgumentCaptor<CustomTabsServiceConnection> mConnectionCaptor;

    @Mock
    CustomTabsClient mClient;

    private CustomTabManager mManager;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        mManager = new CustomTabManager(mContext);
    }

    @SuppressWarnings("WrongConstant")
    @Test
    public void testBind() {
        startBind(true);
        provideClient();

        // the mock client should now be available on the manager
        assertThat(mManager.getClient()).isEqualTo(mClient);
    }

    @Test
    public void testBind_browserDoesNotSupportCustomTabs() {
        startBind(false);
        assertThat(mManager.getClient()).isEqualTo(null);
    }

    @Test
    public void testCreateSession() {
        startBind(true);
        provideClient();

        CustomTabsCallback mockCallbacks = Mockito.mock(CustomTabsCallback.class);
        CustomTabsSession mockSession = Mockito.mock(CustomTabsSession.class);

        Mockito.doReturn(mockSession).when(mClient).newSession(mockCallbacks);

        Uri launchUri1 = Uri.parse("https://idp.example.com");
        Uri launchUri2 = Uri.parse("https://another.example.com");
        Uri launchUri3 = Uri.parse("https://yetanother.example.com");

        Bundle launchUri2Bundle = new Bundle();
        launchUri2Bundle.putParcelable(CustomTabsService.KEY_URL, launchUri2);

        Bundle launchUri3Bundle = new Bundle();
        launchUri3Bundle.putParcelable(CustomTabsService.KEY_URL, launchUri3);

        CustomTabsSession session = mManager.createSession(
            mockCallbacks,
            launchUri1,
            launchUri2,
            launchUri3);

        assertThat(session).isEqualTo(mockSession);

        // upon creation of the session, the code should prime the session with the expected URIs
        @SuppressWarnings("unchecked")
        ArgumentCaptor<List<Bundle>> bundleCaptor = ArgumentCaptor.forClass(List.class);
        Mockito.verify(mockSession).mayLaunchUrl(
            eq(launchUri1),
            eq((Bundle)null),
            bundleCaptor.capture());

        List<Bundle> bundles = bundleCaptor.getValue();
        assertThat(bundles).hasSize(2);
        assertThat(bundles.get(0).get(CustomTabsService.KEY_URL)).isEqualTo(launchUri2);
        assertThat(bundles.get(1).get(CustomTabsService.KEY_URL)).isEqualTo(launchUri3);
    }

    @Test
    public void testCreateSession_browserDoesNotSupportCustomTabs() {
        startBind(false);
        assertThat(mManager.createSession(null)).isNull();
    }

    @Test
    public void testCreateSession_creationFails() {
        startBind(true);
        provideClient();

        // Emulate session creation failure - annoyingly the contract for this is just to return
        // null, rather than an exception with some useful context.
        CustomTabsCallback mockCallbacks = Mockito.mock(CustomTabsCallback.class);
        Mockito.doReturn(null).when(mClient).newSession(mockCallbacks);

        assertThat(mManager.createSession(mockCallbacks)).isNull();
    }

    @Test
    public void testDispose() {
        startBind(true);

    }

    @SuppressWarnings("WrongConstant")
    private void startBind(boolean succeed) {
        Mockito.doReturn(succeed).when(mContext).bindService(
            mConnectIntentCaptor.capture(),
            mConnectionCaptor.capture(),
            Mockito.anyInt());

        mManager.bind(BROWSER_PACKAGE_NAME);

        // check the service connection is made to the specified package
        Intent intent = mConnectIntentCaptor.getValue();
        assertThat(intent.getPackage()).isEqualTo(BROWSER_PACKAGE_NAME);
    }

    private void provideClient() {
        CustomTabsServiceConnection conn = mConnectionCaptor.getValue();
        conn.onCustomTabsServiceConnected(
            new ComponentName(BROWSER_PACKAGE_NAME, BROWSER_PACKAGE_NAME + ".CustomTabsService"),
            mClient);
    }
}