package com.braintree.graphql.commands.impl;

import com.braintree.command.request.BrainTreeSubmitForSettlementTransactionRequest;
import com.braintree.commands.impl.DefaultBrainTreeLoggingHandler;
import com.braintree.configuration.service.BrainTreeConfigService;
import com.braintree.hybris.data.graphql.commands.request.BrainTreeCaptureTransactionInput;
import com.braintree.hybris.data.graphql.commands.response.BrainTreeErrorDefinition;
import com.braintree.hybris.data.graphql.commands.response.BrainTreeMonetaryAmount;
import com.braintree.hybris.data.graphql.commands.response.BrainTreePaymentStatus;
import com.braintree.hybris.data.graphql.commands.response.BrainTreeTransaction;
import com.braintree.hybris.data.graphql.commands.response.BrainTreeTransactionPayload;
import com.braintree.service.impl.DefaultBrainTreeDescriptorService;
import com.braintree.transaction.service.BrainTreeTransactionService;
import com.fasterxml.jackson.databind.ObjectMapper;
import de.hybris.bootstrap.annotations.UnitTest;
import de.hybris.platform.payment.AdapterException;
import de.hybris.platform.payment.commands.result.CaptureResult;
import de.hybris.platform.payment.dto.TransactionStatus;
import de.hybris.platform.payment.dto.TransactionStatusDetails;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.runners.MockitoJUnitRunner;

import java.io.IOException;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.mockito.Matchers.eq;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

@UnitTest
@RunWith(MockitoJUnitRunner.class)
public class DefaultBrainTreeGraphQLCaptureCommandTest {

    private static final String MERCHANT_TRANSACTION_CODE = "merchantTransactionCode";
    private static final String DEFINITION_FILE_NAME = "captureTransaction";
    private static final String TRANSACTION_ID = "transactionId";
    private static final String ORDER_ID = "orderId";
    private static final String AMOUNT = "12.44";
    private static final String INPUT_PARAMETER = "input";
    private static final String GRAPH_QL_TRANSACTION_ID = "graphQLTransactionId";
    private static final String MERCHANT_ACCOUNT_ID = "merchantAccountId";
    private static final String DATA_MAP_KEY = "data";
    private static final String CAPTURE_TRANSACTION_TYPE = "[CAPTURE TRANSACTION] ";
    private static final String USD = "USD";
    private static final String RESULT_ERRORS = "errors";
    private static final String ERROR_MESSAGE = "Error message";
    private static final String ERROR_MAP_KEY = "error";

    @Mock
    private BrainTreeTransactionService brainTreeTransactionService;

    @Mock
    private ObjectMapper objectMapper;

    @Mock
    private DefaultBrainTreeLoggingHandler loggingHandler;

    @Mock
    private DefaultBrainTreeDescriptorService descriptorService;

    @Mock
    BrainTreeConfigService configService;

    @Spy
    @InjectMocks
    private DefaultBrainTreeGraphQLCaptureCommand command;

    @Captor
    private ArgumentCaptor<Map<String, Object>> variables;

    private BrainTreeSubmitForSettlementTransactionRequest request;
    private BigDecimal bigDecimalAmount;
    private Map<String, Object> mapResult;

    @Before
    public void setUp() throws IOException {
        bigDecimalAmount = new BigDecimal(AMOUNT);

        request = new BrainTreeSubmitForSettlementTransactionRequest(MERCHANT_TRANSACTION_CODE);
        request.setTransactionId(TRANSACTION_ID);
        request.setOrderId(ORDER_ID);
        request.setAmount(bigDecimalAmount);

        mapResult = new HashMap<>();
        mapResult.put(DATA_MAP_KEY, new HashMap<>());

        doReturn(mapResult).when(command).makeGraphQlCall(eq(DEFINITION_FILE_NAME), any());
        when(brainTreeTransactionService.getGraphQLIdFromTransaction(TRANSACTION_ID)).thenReturn(GRAPH_QL_TRANSACTION_ID);
    }

    @Test
    public void shouldCreateVariablesMap() throws IOException {
        //given
        when(objectMapper.convertValue(any(), eq(BrainTreeTransactionPayload.class))).thenReturn(null);

        //when
        command.makeApiCall(request);

        //then
        verify(command).makeGraphQlCall(eq(DEFINITION_FILE_NAME), variables.capture());

        BrainTreeCaptureTransactionInput inputResult = (BrainTreeCaptureTransactionInput) variables.getValue().get(INPUT_PARAMETER);
        assertNotNull(inputResult);
        assertEquals(inputResult.getTransactionId(), GRAPH_QL_TRANSACTION_ID);
        assertNotNull(inputResult.getTransaction());
        assertEquals(ORDER_ID, inputResult.getTransaction().getOrderId());
        assertEquals(bigDecimalAmount, inputResult.getTransaction().getAmount());
    }

    @Test
    public void shouldTranslateResponseWhenSuccessfulTransaction() throws IOException {
        //given
        BrainTreeMonetaryAmount monetaryAmount = new BrainTreeMonetaryAmount();
        monetaryAmount.setValue(AMOUNT);
        monetaryAmount.setCurrencyCode(USD);

        BrainTreeTransaction transaction = new BrainTreeTransaction();
        transaction.setStatus(BrainTreePaymentStatus.AUTHORIZED);
        transaction.setAmount(monetaryAmount);
        transaction.setMerchantAccountId(MERCHANT_ACCOUNT_ID);
        transaction.setId(TRANSACTION_ID);

        BrainTreeTransactionPayload payload = new BrainTreeTransactionPayload();
        payload.setTransaction(transaction);

        doNothing().when(loggingHandler).handleResult(CAPTURE_TRANSACTION_TYPE, transaction);
        when(objectMapper.convertValue(any(), eq(BrainTreeTransactionPayload.class))).thenReturn(payload);

        //when
        CaptureResult captureResult = command.makeApiCall(request);

        //then
        verify(objectMapper).convertValue(any(), eq(BrainTreeTransactionPayload.class));
        assertEquals(TransactionStatus.ACCEPTED, captureResult.getTransactionStatus());
        assertEquals(TransactionStatusDetails.SUCCESFULL, captureResult.getTransactionStatusDetails());
        assertEquals(TRANSACTION_ID, captureResult.getRequestToken());
        assertEquals(TRANSACTION_ID, captureResult.getRequestId());
        assertEquals(MERCHANT_ACCOUNT_ID, captureResult.getMerchantTransactionCode());
        assertNotNull(captureResult.getCurrency());
        assertEquals(USD, captureResult.getCurrency().getCurrencyCode());
        assertNotNull(captureResult.getTotalAmount());
        assertEquals(AMOUNT, captureResult.getTotalAmount().toString());
    }

    @Test
    public void shouldTranslateResponseWhenFailedTransaction() {
        //given
        when(objectMapper.convertValue(any(), eq(BrainTreeTransactionPayload.class))).thenReturn(null);

        //when
        CaptureResult captureResult = command.makeApiCall(request);

        //then
        verify(objectMapper).convertValue(any(), eq(BrainTreeTransactionPayload.class));
        assertEquals(TransactionStatus.REJECTED, captureResult.getTransactionStatus());
        assertEquals(TransactionStatusDetails.BANK_DECLINE, captureResult.getTransactionStatusDetails());
        assertNull(captureResult.getRequestToken());
        assertNull(captureResult.getRequestId());
        assertNull(captureResult.getMerchantTransactionCode());
        assertNull(captureResult.getCurrency());
        assertNull(captureResult.getTotalAmount());
    }

    @Test
    public void shouldTranslateResponseWhenNotSuccessfulTransaction() throws IOException {
        //given
        BrainTreeTransaction transaction = new BrainTreeTransaction();
        transaction.setStatus(BrainTreePaymentStatus.GATEWAY_REJECTED);

        BrainTreeTransactionPayload payload = new BrainTreeTransactionPayload();
        payload.setTransaction(transaction);

        when(objectMapper.convertValue(any(), eq(BrainTreeTransactionPayload.class))).thenReturn(payload);

        //when
        CaptureResult captureResult = command.makeApiCall(request);

        //then
        verify(objectMapper).convertValue(any(), eq(BrainTreeTransactionPayload.class));
        assertEquals(TransactionStatus.REJECTED, captureResult.getTransactionStatus());
        assertEquals(TransactionStatusDetails.BANK_DECLINE, captureResult.getTransactionStatusDetails());
        assertNull(captureResult.getRequestToken());
        assertNull(captureResult.getRequestId());
        assertNull(captureResult.getMerchantTransactionCode());
        assertNull(captureResult.getCurrency());
        assertNull(captureResult.getTotalAmount());
    }

    @Test
    public void shouldTranslateResponseWhenTransactionStatusIsNull() {
        //given
        BrainTreeTransaction transaction = new BrainTreeTransaction();

        BrainTreeTransactionPayload payload = new BrainTreeTransactionPayload();
        payload.setTransaction(transaction);

        when(objectMapper.convertValue(any(), eq(BrainTreeTransactionPayload.class))).thenReturn(payload);

        //when
        CaptureResult captureResult = command.makeApiCall(request);

        //then
        verify(objectMapper).convertValue(any(), eq(BrainTreeTransactionPayload.class));
        assertEquals(TransactionStatus.REJECTED, captureResult.getTransactionStatus());
        assertEquals(TransactionStatusDetails.BANK_DECLINE, captureResult.getTransactionStatusDetails());
        assertNull(captureResult.getRequestToken());
        assertNull(captureResult.getRequestId());
        assertNull(captureResult.getMerchantTransactionCode());
        assertNull(captureResult.getCurrency());
        assertNull(captureResult.getTotalAmount());
    }

    @Test
    public void shouldTranslateResponseWhenTransactionIsNull() {
        //given
        BrainTreeTransactionPayload payload = new BrainTreeTransactionPayload();

        when(objectMapper.convertValue(any(), eq(BrainTreeTransactionPayload.class))).thenReturn(payload);

        //when
        CaptureResult captureResult = command.makeApiCall(request);

        //then
        verify(objectMapper).convertValue(any(), eq(BrainTreeTransactionPayload.class));
        assertEquals(TransactionStatus.REJECTED, captureResult.getTransactionStatus());
        assertEquals(TransactionStatusDetails.BANK_DECLINE, captureResult.getTransactionStatusDetails());
        assertNull(captureResult.getRequestToken());
        assertNull(captureResult.getRequestId());
        assertNull(captureResult.getMerchantTransactionCode());
        assertNull(captureResult.getCurrency());
        assertNull(captureResult.getTotalAmount());
    }

    @Test(expected = AdapterException.class)
    public void shouldThrowAdapterExceptionIfErrorOccurInResult() throws IOException {
        //given
        BrainTreeErrorDefinition brainTreeError = new BrainTreeErrorDefinition();
        brainTreeError.setMessage(ERROR_MESSAGE);
        Map<String, Object> errorMap = Map.of(ERROR_MAP_KEY, brainTreeError);
        List<Map<String, Object>> errorList = new ArrayList<>();
        errorList.add(errorMap);
        mapResult.put(RESULT_ERRORS, errorList);

        when(objectMapper.convertValue(any(), eq(BrainTreeErrorDefinition.class))).thenReturn(brainTreeError);

        //when
        command.makeApiCall(request);

        //then
        verify(command).makeGraphQlCall(eq(DEFINITION_FILE_NAME), any());
    }

    @Test(expected = AdapterException.class)
    public void shouldAdapterExceptionIfIOExceptionOccur() throws IOException {
        doThrow(new IOException()).when(command).makeGraphQlCall(any(), any());

        //when
        command.makeApiCall(request);

        //then
        verify(command).makeGraphQlCall(eq(DEFINITION_FILE_NAME), any());
    }

}
