/*
 *  Copyright (C) 2016 Alessandro Yuichi Okimoto
 *
 *  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.
 *  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 *  Contact email: [email protected]
 */

package jp.alessandro.android.iab;

import android.app.Activity;
import android.app.PendingIntent;
import android.content.Intent;
import android.content.IntentSender;
import android.os.Bundle;
import android.os.RemoteException;

import com.android.vending.billing.IInAppBillingService;

import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;

import java.util.ArrayList;
import java.util.List;

import jp.alessandro.android.iab.util.DataConverter;

import static org.assertj.core.api.Java6Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;

/**
 * Created by Alessandro Yuichi Okimoto on 2017/02/19.
 */

@RunWith(RobolectricTestRunner.class)
@Config(manifest = Config.NONE, constants = BuildConfig.class)
public class PurchaseFlowLaunchTest {

    private static final String TYPE_IN_APP = "inapp";
    private static final String TYPE_SUBSCRIPTION = "subs";

    private final DataConverter mDataConverter = new DataConverter(Security.KEY_FACTORY_ALGORITHM, Security.KEY_FACTORY_ALGORITHM);
    private final BillingContext mBillingContext = mDataConverter.newBillingContext(RuntimeEnvironment.application);

    @Rule
    public MockitoRule mMockitoRule = MockitoJUnit.rule();

    @Mock
    IInAppBillingService mService;

    @Mock
    Activity mActivity;

    @Test
    public void startIntentSendIntentException() throws RemoteException, BillingException, IntentSender.SendIntentException {
        PurchaseFlowLauncher launcher = spy(new PurchaseFlowLauncher(mBillingContext, TYPE_IN_APP));
        int requestCode = 1001;
        PendingIntent pendingIntent = PendingIntent.getActivity(mBillingContext.getContext(), 1, new Intent(), 0);
        Bundle bundle = new Bundle();
        bundle.putLong(Constants.RESPONSE_CODE, 0L);
        bundle.putParcelable(Constants.RESPONSE_BUY_INTENT, pendingIntent);

        doThrow(new IntentSender.SendIntentException()).when(mActivity).startIntentSenderForResult(
                any(IntentSender.class), anyInt(), any(Intent.class), anyInt(), anyInt(), anyInt());

        when(mService.getBuyIntent(
                mBillingContext.getApiVersion(),
                mBillingContext.getContext().getPackageName(),
                "",
                TYPE_IN_APP,
                ""
        )).thenReturn(bundle);

        try {
            launcher.launch(mService, mActivity, requestCode, null, "", "");
        } catch (BillingException e) {
            assertThat(e.getErrorCode()).isEqualTo(Constants.ERROR_SEND_INTENT_FAILED);
        } finally {
            verify(mService).getBuyIntent(
                    mBillingContext.getApiVersion(),
                    mBillingContext.getContext().getPackageName(),
                    "",
                    TYPE_IN_APP,
                    ""
            );
            verifyNoMoreInteractions(mService);
        }
    }

    @Test
    public void startIntentSenderForResultError() throws RemoteException, BillingException {
        PurchaseFlowLauncher launcher = new PurchaseFlowLauncher(mBillingContext, TYPE_IN_APP);
        int requestCode = 1001;
        PendingIntent pendingIntent = PendingIntent.getActivity(mBillingContext.getContext(), 1, new Intent(), 0);

        Bundle bundle = new Bundle();
        bundle.putLong(Constants.RESPONSE_CODE, 0L);
        bundle.putParcelable(Constants.RESPONSE_BUY_INTENT, pendingIntent);

        pendingIntent.cancel();

        startIntentSender(bundle, launcher, requestCode, Constants.ERROR_SEND_INTENT_FAILED);
    }

    @Test
    public void startIntentSenderForResult() throws RemoteException, BillingException {
        PurchaseFlowLauncher launcher = new PurchaseFlowLauncher(mBillingContext, TYPE_IN_APP);
        int requestCode = 1001;
        PendingIntent pendingIntent = PendingIntent.getActivity(mBillingContext.getContext(), 1, new Intent(), 0);
        Bundle bundle = new Bundle();
        bundle.putLong(Constants.RESPONSE_CODE, 0L);
        bundle.putParcelable(Constants.RESPONSE_BUY_INTENT, pendingIntent);

        startIntentSender(bundle, launcher, requestCode, -1099);
    }

    private void startIntentSender(Bundle bundle,
                                   PurchaseFlowLauncher launcher,
                                   int requestCode,
                                   int errorCode) throws RemoteException {

        when(mService.getBuyIntent(
                mBillingContext.getApiVersion(),
                mBillingContext.getContext().getPackageName(),
                "",
                TYPE_IN_APP,
                ""
        )).thenReturn(bundle);

        try {
            launcher.launch(mService, mActivity, requestCode, null, "", "");
        } catch (BillingException e) {
            assertThat(e.getErrorCode()).isEqualTo(errorCode);
        } finally {
            verify(mService).getBuyIntent(
                    mBillingContext.getApiVersion(),
                    mBillingContext.getContext().getPackageName(),
                    "",
                    TYPE_IN_APP,
                    ""
            );
            verifyNoMoreInteractions(mService);
        }
    }

    @Test
    public void bundleResponseNull() throws RemoteException {
        PurchaseFlowLauncher launcher = new PurchaseFlowLauncher(mBillingContext, TYPE_IN_APP);
        int requestCode = 1001;

        try {
            launcher.launch(mService, mActivity, requestCode, null, "", "");
        } catch (BillingException e) {
            assertThat(e.getErrorCode()).isEqualTo(Constants.ERROR_UNEXPECTED_TYPE);
            assertThat(e.getMessage()).isEqualTo(Constants.ERROR_MSG_UNEXPECTED_BUNDLE_RESPONSE_NULL);
        } finally {
            verify(mService).getBuyIntent(
                    mBillingContext.getApiVersion(),
                    mBillingContext.getContext().getPackageName(),
                    "",
                    TYPE_IN_APP,
                    ""
            );
            verifyNoMoreInteractions(mService);
        }
    }

    @Test
    public void remoteExceptionOnLaunch() throws RemoteException {
        PurchaseFlowLauncher launcher = new PurchaseFlowLauncher(mBillingContext, TYPE_IN_APP);
        int requestCode = 1001;

        when(mService.getBuyIntent(
                mBillingContext.getApiVersion(),
                mBillingContext.getContext().getPackageName(),
                "",
                TYPE_IN_APP,
                ""
        )).thenThrow(RemoteException.class);

        try {
            launcher.launch(mService, mActivity, requestCode, new ArrayList<String>(), "", "");
        } catch (BillingException e) {
            assertThat(e.getErrorCode()).isEqualTo(Constants.ERROR_REMOTE_EXCEPTION);
        } finally {
            verify(mService).getBuyIntent(
                    mBillingContext.getApiVersion(),
                    mBillingContext.getContext().getPackageName(),
                    "",
                    TYPE_IN_APP,
                    ""
            );
            verifyNoMoreInteractions(mService);
        }
    }

    @Test
    public void pendingIntentNullUpdateSubscription() throws RemoteException {
        PurchaseFlowLauncher launcher = new PurchaseFlowLauncher(mBillingContext, TYPE_SUBSCRIPTION);
        int requestCode = 1001;
        Bundle bundle = new Bundle();
        bundle.putLong(Constants.RESPONSE_CODE, 0L);
        List<String> oldSkus = new ArrayList<>();
        oldSkus.add("test");

        when(mService.getBuyIntentToReplaceSkus(
                BillingApi.VERSION_5.getValue(),
                mBillingContext.getContext().getPackageName(),
                oldSkus,
                "",
                TYPE_SUBSCRIPTION,
                ""
        )).thenReturn(bundle);

        try {
            launcher.launch(mService, mActivity, requestCode, oldSkus, "", "");
        } catch (BillingException e) {
            assertThat(e.getErrorCode()).isEqualTo(Constants.ERROR_PENDING_INTENT);
            assertThat(e.getMessage()).isEqualTo(Constants.ERROR_MSG_PENDING_INTENT);
        } finally {
            verify(mService).getBuyIntentToReplaceSkus(
                    BillingApi.VERSION_5.getValue(),
                    mBillingContext.getContext().getPackageName(),
                    oldSkus,
                    "",
                    TYPE_SUBSCRIPTION,
                    ""
            );
            verifyNoMoreInteractions(mService);
        }
    }

    @Test
    public void pendingIntentNullWithLongResponseCode() throws RemoteException {
        PurchaseFlowLauncher launcher = new PurchaseFlowLauncher(mBillingContext, TYPE_IN_APP);
        int requestCode = 1001;
        Bundle bundle = new Bundle();
        bundle.putLong(Constants.RESPONSE_CODE, 0L);

        noPendingIntent(mActivity,
                launcher,
                bundle,
                requestCode,
                Constants.ERROR_PENDING_INTENT,
                Constants.ERROR_MSG_PENDING_INTENT);
    }

    @Test
    public void pendingIntentNullWithDifferentResponseCode() throws RemoteException {
        PurchaseFlowLauncher launcher = new PurchaseFlowLauncher(mBillingContext, TYPE_IN_APP);
        int requestCode = 1001;
        int responseCode = -100;
        Bundle bundle = new Bundle();
        bundle.putLong(Constants.RESPONSE_CODE, responseCode);

        noPendingIntent(mActivity,
                launcher,
                bundle,
                requestCode,
                responseCode,
                Constants.ERROR_MSG_UNABLE_TO_BUY);
    }

    @Test
    public void pendingIntentNullWithIntegerResponseCode()
            throws RemoteException, BillingException {

        PurchaseFlowLauncher launcher = new PurchaseFlowLauncher(mBillingContext, TYPE_IN_APP);
        int requestCode = 1001;
        Bundle bundle = new Bundle();
        bundle.putInt(Constants.RESPONSE_CODE, 0);

        noPendingIntent(mActivity,
                launcher,
                bundle,
                requestCode,
                Constants.ERROR_PENDING_INTENT,
                Constants.ERROR_MSG_PENDING_INTENT);
    }

    @Test
    public void noResponseCode() throws RemoteException {
        PurchaseFlowLauncher launcher = new PurchaseFlowLauncher(mBillingContext, TYPE_IN_APP);
        Bundle bundle = new Bundle();
        int requestCode = 1001;

        noPendingIntent(mActivity,
                launcher,
                bundle,
                requestCode,
                Constants.ERROR_PENDING_INTENT,
                Constants.ERROR_MSG_PENDING_INTENT);
    }

    @Test
    public void stringResponseCode() throws RemoteException {
        PurchaseFlowLauncher launcher = new PurchaseFlowLauncher(mBillingContext, TYPE_IN_APP);
        int requestCode = 1001;
        Bundle bundle = new Bundle();
        bundle.putString(Constants.RESPONSE_CODE, "0");

        noPendingIntent(mActivity,
                launcher,
                bundle,
                requestCode,
                Constants.ERROR_UNEXPECTED_TYPE,
                Constants.ERROR_MSG_UNEXPECTED_BUNDLE_RESPONSE);
    }

    @Test
    public void lostContext() throws RemoteException {
        PurchaseFlowLauncher launcher = new PurchaseFlowLauncher(mBillingContext, TYPE_IN_APP);
        int requestCode = 1001;
        Bundle bundle = new Bundle();
        bundle.putInt(Constants.RESPONSE_CODE, 0);

        noPendingIntent(null,
                launcher,
                bundle,
                requestCode,
                Constants.ERROR_LOST_CONTEXT,
                Constants.ERROR_MSG_LOST_CONTEXT);
    }

    private void noPendingIntent(Activity activity,
                                 PurchaseFlowLauncher launcher,
                                 Bundle bundle,
                                 int requestCode,
                                 int errorCode,
                                 String errorMessage) throws RemoteException {
        when(mService.getBuyIntent(
                mBillingContext.getApiVersion(),
                mBillingContext.getContext().getPackageName(),
                "",
                TYPE_IN_APP,
                ""
        )).thenReturn(bundle);

        try {
            launcher.launch(mService, activity, requestCode, null, "", "");
        } catch (BillingException e) {
            assertThat(e.getErrorCode()).isEqualTo(errorCode);
            assertThat(e.getMessage()).isEqualTo(errorMessage);
        } finally {
            verify(mService).getBuyIntent(
                    mBillingContext.getApiVersion(),
                    mBillingContext.getContext().getPackageName(),
                    "",
                    TYPE_IN_APP,
                    ""
            );
            verifyNoMoreInteractions(mService);
        }
    }
}