package com.unity3d.services.purchasing.core.api;

import android.support.test.runner.AndroidJUnit4;

import com.unity3d.services.purchasing.core.IPurchasingAdapter;
import com.unity3d.services.purchasing.core.IRetrieveProductsListener;
import com.unity3d.services.purchasing.core.ITransactionListener;
import com.unity3d.services.purchasing.core.Product;
import com.unity3d.services.purchasing.core.Store;
import com.unity3d.services.purchasing.core.TransactionDetails;
import com.unity3d.services.purchasing.core.TransactionDetailsUtilities;
import com.unity3d.services.purchasing.core.TransactionError;
import com.unity3d.services.core.webview.WebViewApp;
import com.unity3d.services.core.webview.bridge.WebViewCallback;
import com.unity3d.services.purchasing.core.TransactionErrorDetails;
import com.unity3d.services.purchasing.core.TransactionErrorDetailsUtilities;
import com.unity3d.services.purchasing.core.properties.ClientProperties;
import com.unity3d.services.purchasing.core.PurchasingEvent;
import com.unity3d.services.monetization.core.utilities.JSONUtilities;
import com.unity3d.services.monetization.core.webview.WebViewEventCategory;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentMatcher;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

@RunWith(AndroidJUnit4.class)
public class CustomPurchasingTest {

    private IPurchasingAdapter purchasingAdapter;
    private WebViewApp webViewApp;

    @Before
    public void setUp() {
        purchasingAdapter = mock(IPurchasingAdapter.class);
        webViewApp = mock(WebViewApp.class);
        ClientProperties.setAdapter(purchasingAdapter);
        WebViewApp.setCurrentApp(webViewApp);
    }

    @After
    public void tearDown() {
        ClientProperties.setAdapter(null);
        WebViewApp.setCurrentApp(null);
    }

    @Test
    public void availableShouldReturnTrueIfPurchasingAdapterIsSet() {
        WebViewCallback callback = mock(WebViewCallback.class);
        CustomPurchasing.available(callback);
        verify(callback).invoke(true);
    }

    @Test
    public void availableShouldReturnFalseIfPurchasingAdapterIsNotSet() {
        WebViewCallback callback = mock(WebViewCallback.class);
        ClientProperties.setAdapter(null);
        CustomPurchasing.available(callback);
        verify(callback).invoke(false);
    }

    @Test
    public void retrieveCatalogsShouldCallThePurchasingAdapter() {
        WebViewCallback callback = mock(WebViewCallback.class);

        CustomPurchasing.refreshCatalog(callback);
        verify(callback).invoke();
        verify(purchasingAdapter).retrieveProducts(any(IRetrieveProductsListener.class));
    }

    @Test
    public void retrieveCatalogShouldSendCatalogEventToWebView() {
        doAnswer(new Answer() {
            @Override
            public Object answer(InvocationOnMock invocation) throws Throwable {
                IRetrieveProductsListener listener = invocation.getArgument(0);
                listener.onProductsRetrieved(Fixture.products);
                return null;
            }
        }).when(purchasingAdapter).retrieveProducts(any(IRetrieveProductsListener.class));

        WebViewCallback callback = mock(WebViewCallback.class);
        CustomPurchasing.refreshCatalog(callback);

        verify(webViewApp).sendEvent(eq(WebViewEventCategory.CUSTOM_PURCHASING),
                eq(PurchasingEvent.PRODUCTS_RETRIEVED),
                argThat(new JsonArrayMatcher(CustomPurchasing.getJSONArrayFromProductList(Fixture.products))));
    }

    @Test
    public void purchaseItemShouldCallThePurchasingAdapter() {
        WebViewCallback callback = mock(WebViewCallback.class);
        CustomPurchasing.purchaseItem(Fixture.productID, Fixture.purchaseExtras, callback);

        verify(purchasingAdapter).onPurchase(eq(Fixture.productID), any(ITransactionListener.class), eq(JSONUtilities.jsonObjectToMap(Fixture.purchaseExtras)));
    }

    @Test
    public void onTransactionCompleteShouldSendTransactionCompleteEventToWebView() {
        doAnswer(new Answer() {
            @Override
            public Object answer(InvocationOnMock invocation) throws Throwable {
                ITransactionListener listener = invocation.getArgument(1);
                listener.onTransactionComplete(Fixture.transactionDetails);
                return null;
            }
        }).when(purchasingAdapter).onPurchase(eq(Fixture.productID), any(ITransactionListener.class), eq(JSONUtilities.jsonObjectToMap(Fixture.purchaseExtras)));

        WebViewCallback callback = mock(WebViewCallback.class);
        CustomPurchasing.purchaseItem(Fixture.productID, Fixture.purchaseExtras, callback);

        verify(webViewApp).sendEvent(eq(WebViewEventCategory.CUSTOM_PURCHASING),
                eq(PurchasingEvent.TRANSACTION_COMPLETE),
                argThat(new JsonObjectMatcher(TransactionDetailsUtilities.getJSONObjectForTransactionDetails(Fixture.transactionDetails))));
    }

    @Test
    public void onTransactionErrorShouldSendTransactionErrorEventToWebView() {
        final TransactionErrorDetails detail = TransactionErrorDetails.newBuilder()
                .withStore(Store.GOOGLE_PLAY)
                .withTransactionError(TransactionError.ITEM_UNAVAILABLE)
                .withExceptionMessage("test exception")
                .withStoreSpecificErrorCode("google failed")
                .build();
        doAnswer(new Answer() {
            @Override
            public Object answer(InvocationOnMock invocation) throws Throwable {
                ITransactionListener listener = invocation.getArgument(1);
                listener.onTransactionError(detail);
                return null;
            }
        }).when(purchasingAdapter).onPurchase(eq(Fixture.productID), any(ITransactionListener.class), eq(JSONUtilities.jsonObjectToMap(Fixture.purchaseExtras)));

        WebViewCallback callback = mock(WebViewCallback.class);
        CustomPurchasing.purchaseItem(Fixture.productID, Fixture.purchaseExtras, callback);

        ;
        verify(webViewApp).sendEvent(eq(WebViewEventCategory.CUSTOM_PURCHASING),
                eq(PurchasingEvent.TRANSACTION_ERROR),
                argThat(new JsonObjectMatcher(TransactionErrorDetailsUtilities.getJSONObjectForTransactionErrorDetails(detail))));
    }

    private static class Fixture {
        public static String productID = "DEADBEEF-BEEFCACE";
        public static List<Product> products;
        public static JSONObject purchaseExtras;
        public static TransactionDetails transactionDetails;
        public static TransactionError transactionError = TransactionError.UNKNOWN_ERROR;
        public static String transactionErrorMessage = "Someone set us up the bomb.";

        static {
            products = new ArrayList<>();
            products.add(Product.newBuilder()
                .withProductId("DEADBEEF-BEEFCACE")
                .withLocalizedTitle("Sword of a Thousand Truths")
                .withLocalizedPriceString("$9000.00")
                .build());

            purchaseExtras = new JSONObject();
            try {
                purchaseExtras.put("foo", "Bar");
            } catch (JSONException e) {
                e.printStackTrace();
            }

            transactionDetails = TransactionDetails.newBuilder()
                    .withProductId("foo")
                    .withTransactionId("abcd1234")
                    .withCurrency("USD")
                    .withPrice(new BigDecimal(23))
                    .withReceipt("Receipt=True")
                    .putExtra("foo", "bar")
                    .build();
        }
    }

    private static class JsonArrayMatcher implements ArgumentMatcher<JSONArray> {
        private final JSONArray src;

        private JsonArrayMatcher(JSONArray src) {
            this.src = src;
        }

        @Override
        public boolean matches(JSONArray o) {
            return src.toString().equals(o.toString());
        }
    }

    private static class JsonObjectMatcher implements ArgumentMatcher<JSONObject> {
        private final JSONObject src;

        private JsonObjectMatcher(JSONObject src) {
            this.src = src;
        }

        @Override
        public boolean matches(JSONObject o) {
            return src.toString().equals(o.toString());
        }
    }
}