package com.braintreepayments.api;

import android.text.TextUtils;

import androidx.test.runner.AndroidJUnit4;

import com.braintreepayments.api.exceptions.ErrorWithResponse;
import com.braintreepayments.api.interfaces.BraintreeErrorListener;
import com.braintreepayments.api.interfaces.PaymentMethodNonceCreatedListener;
import com.braintreepayments.api.interfaces.UnionPayListener;
import com.braintreepayments.api.models.CardNonce;
import com.braintreepayments.api.models.PaymentMethodNonce;
import com.braintreepayments.api.models.UnionPayCapabilities;
import com.braintreepayments.api.models.UnionPayCardBuilder;
import com.braintreepayments.api.test.BraintreeActivityTestRule;
import com.braintreepayments.api.test.TestActivity;
import com.braintreepayments.api.test.TestClientTokenBuilder;
import com.braintreepayments.testutils.CardNumber;
import com.braintreepayments.testutils.ExpirationDateHelper;

import org.junit.Before;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import java.util.concurrent.CountDownLatch;

import static com.braintreepayments.api.BraintreeFragmentTestUtils.getFragmentWithAuthorization;
import static com.braintreepayments.api.test.Assertions.assertIsANonce;
import static com.braintreepayments.testutils.CardNumber.UNIONPAY_CREDIT;
import static com.braintreepayments.testutils.CardNumber.UNIONPAY_DEBIT;
import static com.braintreepayments.testutils.CardNumber.UNIONPAY_INTEGRATION_CREDIT;
import static com.braintreepayments.testutils.CardNumber.UNIONPAY_INTEGRATION_DEBIT;
import static com.braintreepayments.testutils.CardNumber.UNIONPAY_NOT_ACCEPTED;
import static com.braintreepayments.testutils.CardNumber.UNIONPAY_SINGLE_STEP_SALE;
import static com.braintreepayments.testutils.CardNumber.UNIONPAY_SMS_NOT_REQUIRED;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertTrue;
import static junit.framework.Assert.fail;

@RunWith(AndroidJUnit4.class)
public class UnionPayTest {

    @Rule
    public final BraintreeActivityTestRule<TestActivity> mActivityTestRule = new BraintreeActivityTestRule<>(TestActivity.class);

    private BraintreeFragment mBraintreeFragment;
    private CountDownLatch mCountDownLatch;

    @Before
    public void setUp() {
        mCountDownLatch = new CountDownLatch(1);
        mBraintreeFragment = getFragmentWithAuthorization(mActivityTestRule.getActivity(),
                new TestClientTokenBuilder().build());
    }

    @Test(timeout = 10000)
    public void fetchCapabilities_whenDebit_isDebitIsTrue() throws InterruptedException {
        mBraintreeFragment.addListener(new UnionPayListener() {
            @Override
            public void onCapabilitiesFetched(UnionPayCapabilities unionPayCapabilities) {
                assertTrue(unionPayCapabilities.supportsTwoStepAuthAndCapture());
                assertTrue(unionPayCapabilities.isDebit());
                assertTrue(unionPayCapabilities.isUnionPay());
                mCountDownLatch.countDown();
            }

            @Override
            public void onSmsCodeSent(String enrollmentId, boolean smsCodeRequired) {
                fail("Not expecting onSmsCodeSent");
            }
        });

        UnionPay.fetchCapabilities(mBraintreeFragment, UNIONPAY_DEBIT);

        mCountDownLatch.await();
    }

    @Test(timeout = 10000)
    public void fetchCapabilities_whenCredit_isDebitIsFalse() throws InterruptedException {
        mBraintreeFragment.addListener(new UnionPayListener() {
            @Override
            public void onCapabilitiesFetched(UnionPayCapabilities unionPayCapabilities) {
                assertTrue(unionPayCapabilities.supportsTwoStepAuthAndCapture());
                assertFalse(unionPayCapabilities.isDebit());
                assertTrue(unionPayCapabilities.isUnionPay());
                mCountDownLatch.countDown();
            }

            @Override
            public void onSmsCodeSent(String enrollmentId, boolean smsCodeRequired) {
                fail("Not expecting onSmsCodeSent");
            }
        });

        UnionPay.fetchCapabilities(mBraintreeFragment, UNIONPAY_CREDIT);

        mCountDownLatch.await();
    }

    @Test(timeout = 10000)
    public void fetchCapabilities_unionPayCredit_isSupported() throws InterruptedException {
        assertSupported(UNIONPAY_CREDIT, true);
    }

    @Test(timeout = 10000)
    public void fetchCapabilities_unionPayDebit_isSupported() throws InterruptedException {
        assertSupported(UNIONPAY_DEBIT, true);
    }

    @Test(timeout = 10000)
    public void fetchCapabilities_unionPaySingleStepSale_isSupported() throws InterruptedException {
        assertSupported(UNIONPAY_SINGLE_STEP_SALE, true);
    }

    @Test(timeout = 10000)
    public void fetchCapabilities_unionPayIntegrationCredit_isSupported() throws InterruptedException {
        assertSupported(UNIONPAY_INTEGRATION_CREDIT, true);
    }

    @Test(timeout = 10000)
    public void fetchCapabilities_unionPayIntegrationDebit_isSupported() throws InterruptedException {
        assertSupported(UNIONPAY_INTEGRATION_DEBIT, true);
    }

    @Test(timeout = 10000)
    public void fetchCapabilities_unionPaySmsNotRequired_isNotSupported() throws InterruptedException {
        assertSupported(UNIONPAY_NOT_ACCEPTED, false);
    }

    @Test(timeout = 10000)
    public void fetchCapabilities_whenSingleStepSale_twoStepAuthAndCaptureIsFalse() throws InterruptedException {
        mBraintreeFragment.addListener(new UnionPayListener() {
            @Override
            public void onCapabilitiesFetched(UnionPayCapabilities unionPayCapabilities) {
                assertTrue(unionPayCapabilities.isSupported());
                assertFalse(unionPayCapabilities.supportsTwoStepAuthAndCapture());
                assertTrue(unionPayCapabilities.isDebit());
                assertTrue(unionPayCapabilities.isUnionPay());
                mCountDownLatch.countDown();
            }

            @Override
            public void onSmsCodeSent(String enrollmentId, boolean smsCodeRequired) {
                fail("Not expecting onSmsCodeSent");
            }
        });

        UnionPay.fetchCapabilities(mBraintreeFragment, UNIONPAY_SINGLE_STEP_SALE);

        mCountDownLatch.await();
    }

    @Test(timeout = 10000)
    public void enroll_whenIsUnionPay_returnsEnrollmentId() throws InterruptedException {
        String cardNumber = UNIONPAY_CREDIT;
        final UnionPayCardBuilder unionPayCardBuilder = new UnionPayCardBuilder()
                .cardNumber(cardNumber)
                .expirationMonth("12")
                .expirationYear(ExpirationDateHelper.validExpirationYear())
                .mobileCountryCode("62")
                .mobilePhoneNumber("11111111111");

        mBraintreeFragment.addListener(new UnionPayListener() {
            @Override
            public void onCapabilitiesFetched(UnionPayCapabilities unionPayCapabilities) {
                assertTrue(unionPayCapabilities.isUnionPay());
                UnionPay.enroll(mBraintreeFragment, unionPayCardBuilder);
            }

            @Override
            public void onSmsCodeSent(String enrollmentId, boolean smsCodeRequired) {
                assertFalse(TextUtils.isEmpty(enrollmentId));
                assertTrue(smsCodeRequired);
                mCountDownLatch.countDown();
            }
        });

        UnionPay.fetchCapabilities(mBraintreeFragment, cardNumber);

        mCountDownLatch.await();
    }

    @Test(timeout = 10000)
    public void enroll_whenIsUnionPayFalse_willError() throws InterruptedException {
        String cardNumber = CardNumber.VISA;
        final UnionPayCardBuilder unionPayCardBuilder = new UnionPayCardBuilder()
                .cardNumber(cardNumber)
                .expirationMonth("12")
                .expirationYear(ExpirationDateHelper.validExpirationYear())
                .mobileCountryCode("62")
                .mobilePhoneNumber("11111111111");

        mBraintreeFragment.addListener(new UnionPayListener() {
            @Override
            public void onCapabilitiesFetched(UnionPayCapabilities capabilities) {
                assertFalse(capabilities.isUnionPay());
                UnionPay.enroll(mBraintreeFragment, unionPayCardBuilder);
            }

            @Override
            public void onSmsCodeSent(String enrollmentId, boolean smsCodeRequired) {
                fail("Not expecting onSmsCodeSent");
            }
        });

        mBraintreeFragment.addListener(new BraintreeErrorListener() {
            @Override
            public void onError(Exception error) {
                assertTrue(error instanceof ErrorWithResponse);
                assertEquals("UnionPay Enrollment is invalid", error.getMessage());
                mCountDownLatch.countDown();
            }
        });

        UnionPay.fetchCapabilities(mBraintreeFragment, cardNumber);
        mCountDownLatch.await();
    }

    @Test(timeout = 10000)
    public void enroll_whenSmsCodeRequiredFalse_onSmsCodeSentReturnsFalse() throws InterruptedException {
        mCountDownLatch = new CountDownLatch(2);
        String cardNumber = UNIONPAY_SMS_NOT_REQUIRED;
        final UnionPayCardBuilder unionPayCardBuilder = new UnionPayCardBuilder()
                .cardNumber(cardNumber)
                .expirationMonth("12")
                .expirationYear(ExpirationDateHelper.validExpirationYear())
                .mobileCountryCode("62")
                .mobilePhoneNumber("11111111111");

        mBraintreeFragment.addListener(new UnionPayListener() {
            @Override
            public void onCapabilitiesFetched(UnionPayCapabilities capabilities) {
                assertTrue(capabilities.isUnionPay());
                assertTrue(capabilities.isSupported());
                UnionPay.enroll(mBraintreeFragment, unionPayCardBuilder);
                mCountDownLatch.countDown();
            }

            @Override
            public void onSmsCodeSent(String enrollmentId, boolean smsCodeRequired) {
                assertNotNull(enrollmentId);
                assertFalse(smsCodeRequired);
                mCountDownLatch.countDown();
            }
        });

        mBraintreeFragment.addListener(new BraintreeErrorListener() {
            @Override
            public void onError(Exception error) {
                fail("Not expecting error");
            }
        });

        UnionPay.fetchCapabilities(mBraintreeFragment, cardNumber);
        mCountDownLatch.await();
    }

    @Ignore("Sample merchant account is not set up for Union Pay")
    @Test(timeout = 10000)
    public void tokenize_unionPayCredit_withExpirationDate() throws InterruptedException {
        final UnionPayCardBuilder cardBuilder = new UnionPayCardBuilder()
                .cardNumber(CardNumber.UNIONPAY_CREDIT)
                .expirationDate("08/20")
                .cvv("123")
                .mobileCountryCode("62")
                .mobilePhoneNumber("1111111111");

        mBraintreeFragment.addListener(new UnionPayListener() {
            @Override
            public void onCapabilitiesFetched(UnionPayCapabilities capabilities) {}

            @Override
            public void onSmsCodeSent(String enrollmentId, boolean smsCodeRequired) {
                assertTrue(smsCodeRequired);
                cardBuilder.enrollmentId(enrollmentId);
                cardBuilder.smsCode("12345");
                UnionPay.tokenize(mBraintreeFragment, cardBuilder);

            }
        });

        mBraintreeFragment.addListener(new PaymentMethodNonceCreatedListener() {
            @Override
            public void onPaymentMethodNonceCreated(PaymentMethodNonce paymentMethodNonce) {
                assertIsANonce(paymentMethodNonce.getNonce());
                assertEquals("32", ((CardNonce) paymentMethodNonce).getLastTwo());
                mCountDownLatch.countDown();
            }
        });

        UnionPay.enroll(mBraintreeFragment, cardBuilder);

        mCountDownLatch.await();
    }

    @Ignore("Sample merchant account is not set up for Union Pay")
    @Test(timeout = 30000)
    public void tokenize_unionPayCredit_withExpirationMonthAndYear() throws InterruptedException {
        final UnionPayCardBuilder cardBuilder = new UnionPayCardBuilder()
                .cardNumber(CardNumber.UNIONPAY_CREDIT)
                .expirationMonth("08")
                .expirationYear("20")
                .cvv("123")
                .mobileCountryCode("62")
                .mobilePhoneNumber("1111111111");

        mBraintreeFragment.addListener(new UnionPayListener() {
            @Override
            public void onCapabilitiesFetched(UnionPayCapabilities capabilities) {}

            @Override
            public void onSmsCodeSent(String enrollmentId, boolean smsCodeRequired) {
                assertTrue(smsCodeRequired);
                cardBuilder.enrollmentId(enrollmentId);
                cardBuilder.smsCode("12345");
                UnionPay.tokenize(mBraintreeFragment, cardBuilder);
            }
        });

        mBraintreeFragment.addListener(new PaymentMethodNonceCreatedListener() {
            @Override
            public void onPaymentMethodNonceCreated(PaymentMethodNonce paymentMethodNonce) {
                assertIsANonce(paymentMethodNonce.getNonce());
                assertEquals("32", ((CardNonce) paymentMethodNonce).getLastTwo());
                mCountDownLatch.countDown();
            }
        });

        UnionPay.enroll(mBraintreeFragment, cardBuilder);

        mCountDownLatch.await();
    }

    private void assertSupported(final String cardNumber, final boolean expected) throws InterruptedException {
        final CountDownLatch countDownLatch = new CountDownLatch(1);
        mBraintreeFragment.addListener(new UnionPayListener() {
            @Override
            public void onCapabilitiesFetched(UnionPayCapabilities capabilities) {
                boolean actual = capabilities.isSupported();
                String message = String.format("Expected %s to be supported? %b, but was %b", cardNumber, expected, actual);
                assertEquals(message, expected, actual);
                countDownLatch.countDown();
            }

            @Override
            public void onSmsCodeSent(String enrollmentId, boolean smsCodeRequired) {
                fail("Not expecting onSmsCodeSent");
            }
        });
        UnionPay.fetchCapabilities(mBraintreeFragment, cardNumber);
        countDownLatch.await();
    }
}