package com.linx.lio;

import android.app.Activity;
import android.util.Log;

import org.apache.cordova.CallbackContext;
import org.jetbrains.annotations.NotNull;
import org.json.JSONException;
import org.json.JSONObject;

import java.util.Deque;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;

import cielo.orders.domain.Credentials;
import cielo.orders.domain.Order;
import cielo.orders.domain.Status;
import cielo.sdk.order.OrderManager;
import cielo.sdk.order.ServiceBindListener;
import cielo.sdk.order.cancellation.CancellationListener;
import cielo.sdk.order.payment.Payment;
import cielo.sdk.order.payment.PaymentCode;
import cielo.sdk.order.payment.PaymentError;
import cielo.sdk.order.payment.PaymentListener;

class LioPaymentManager {
    private final Map<NSU, Order> orderMap;
    private final OrderManager orderManager;
    private final Activity context;
    private AtomicBoolean bound;

    LioPaymentManager(@NotNull final Credentials credentials, @NotNull final Activity context) {
        this.orderMap = new HashMap<NSU, Order>();
        this.orderManager = new OrderManager(credentials, context);
        this.context = context;
        bound = new AtomicBoolean(false);
    }

    final void bind(@NotNull final CallbackContext callbackContext) {
        if (isBound()) {
            throw new IllegalStateException("LioPaymentManager is bound");
        }
        orderManager.bind(context, new LioBindListener(callbackContext));
    }

    final void unbind() {
        if (!isBound()) {
            throw new IllegalStateException("LiopaymentManager is not bound");
        }
        orderManager.unbind(); // no callbacks...
        bound.set(false);
    }

    final boolean isBound() {
        return bound.get();
    }

    final void placeOrder(@NotNull final PlaceOrderRequest placeOrderRequest,
                          @NotNull final CallbackContext callbackContext) {
        final NSU nsu = placeOrderRequest.getNsu();
        final Order order = orderManager.createDraftOrder(nsu.getValue());

        addItems(order, placeOrderRequest.getProductList());

        orderManager.placeOrder(order); // optional return value...

        if (order.getStatus().equals(Status.ENTERED)) {
            orderMap.put(nsu, order); // caching for later usage on payments
            callbackContext.success("PLACE_ORDER_SUCCESS");
        } else {
            callbackContext.error("PLACE_ORDER_ERROR");
        }
    }

    final void checkout(@NotNull final CheckoutRequest checkoutRequest,
                        @NotNull final CallbackContext callbackContext) {
        final NSU nsu = checkoutRequest.getNsu();
        final Order order = getOrder(nsu);
        final String orderId = order.getId();
        final long amount = checkoutRequest.getAmount();
        final int paymentMethod = checkoutRequest.getPaymentMethod();
        final PaymentListener paymentListener = new LioPaymentListener(nsu, callbackContext);

        if ((paymentMethod & 0x0010) == 0x0010) {
            orderManager.checkoutOrder(orderId, amount, parsePaymentCode(paymentMethod), paymentListener);
        } else if ((paymentMethod & 0x0020) == 0x0020) {
            final int installments = checkoutRequest.getInstallments();

            switch(paymentMethod) {
                case PaymentMethod.CREDIT_INSTALLMENTS_ADM:
                    orderManager.checkoutOrderAdm(orderId, amount, installments, paymentListener);
                    break;
                 case PaymentMethod.CREDIT_INSTALLMENTS_STORE:
                    orderManager.checkoutOrderStore(orderId, amount, installments, paymentListener);
                    break;
                default:
                    throw new RuntimeException("Payment method not implemented");
            }
        } else {
            throw new RuntimeException("Payment method not implemented");
        }
    }

    final void confirmOrder(@NotNull final ConfirmOrderRequest confirmOrderRequest,
                            @NotNull final CallbackContext callbackContext) {
        final NSU nsu = confirmOrderRequest.getNsu();
        final Order order = getOrder(nsu);

        order.markAsPaid();
        orderManager.updateOrder(order); // optional return...

        if (Status.PAID.equals(order.getStatus())) {
            orderMap.remove(nsu);
            callbackContext.success("ORDER_CONFIRMATION_SUCCESS");
        } else {
            callbackContext.error("ORDER_STATUS_UPDATE_ERROR");
        }
    }

    final void cancelOrder(@NotNull final CancelOrderRequest cancelOrderRequest,
                           @NotNull final CallbackContext callbackContext) {
        // TODO: REMOVE THE COMMENT BELOW WHEN CAIXA_RAPIDO IMPLEMENTS IT
        /*final NSU nsu = cancelOrderRequest.getNsu();
        final Order order = getOrder(nsu);

        final Deque<Payment> paymentDeque = new ArrayDeque<Payment>(order.getPayments());
        final Payment payment = paymentDeque.pop();

        orderManager.cancelOrder(context, order.getId(), payment, new LioCancellationListener(nsu,
                paymentDeque, callbackContext));*/
        // TODO: REMOVE THE CODE BELOW WHEN CAIXA_RAPIDO IMPLEMENTS CANCELLATION
        orderMap.remove(cancelOrderRequest.getNsu());
        callbackContext.success("ORDER_CANCELLATION_SUCCESS");
    }

    private Order getOrder(@NotNull final NSU nsu) {
        final Order order = orderMap.get(nsu);
        if (order == null) {
            throw new IllegalArgumentException("There is no order with the following nsu = " + nsu.getValue());
        }

        return order;
    }

    private PaymentCode parsePaymentCode(@PaymentMethod final int paymentMethod) {
        switch(paymentMethod) {
            case PaymentMethod.DEBIT:
                return PaymentCode.DEBITO_AVISTA;
            case PaymentMethod.POST_DATED_DEBIT:
                return PaymentCode.DEBITO_PREDATADO;
            case PaymentMethod.CREDIT:
                return PaymentCode.CREDITO_AVISTA;
            case PaymentMethod.CREDIT_INSTALLMENTS_STORE:
                return PaymentCode.CREDITO_PARCELADO_LOJA;
            case PaymentMethod.CREDIT_INSTALLMENTS_ADM:
                return PaymentCode.CREDITO_PARCELADO_ADM;
            case PaymentMethod.PRE_AUTHORIZED:
                return PaymentCode.PRE_AUTORIZACAO;
            case PaymentMethod.FOOD_VOUCHER:
                return PaymentCode.VOUCHER_ALIMENTACAO;
            case PaymentMethod.MEAL_VOUCHER:
                return PaymentCode.VOUCHER_REFEICAO;
            default:
                throw new RuntimeException("Payment method not supported");
        }
    }

    private void addItems(@NotNull final Order order, @NotNull final List<Product> productList) {
        for (final Product product : productList) {
            addItem(order, product);
        }
    }

    private void addItem(@NotNull final Order order, @NotNull final Product product) {
        final Price price = product.getPrice();
        order.addItem(product.getSku(), product.getName(), price.getUnitPrice(),
                price.getQuantity(), price.getUnityOfMeasure());
    }

    private final class LioBindListener implements ServiceBindListener {
        private final CallbackContext callbackContext;

        private LioBindListener(@NotNull final CallbackContext callbackContext) {
            this.callbackContext = callbackContext;
        }

        @Override
        public void onServiceBound() {
            LioPaymentManager.this.bound.set(true);
            callbackContext.success("SUCCESS");
        }

        @Override
        public void onServiceBoundError(final Throwable throwable) {
            LioPaymentManager.this.bound.set(false);
            callbackContext.error(throwable.getMessage());
        }

        @Override
        public void onServiceUnbound() {
            LioPaymentManager.this.bound.set(false);
        }
    }

    private final class LioPaymentListener implements PaymentListener {
        private final CallbackContext callbackContext;
        private final NSU nsu;

        private LioPaymentListener(@NotNull final NSU nsu,
                                   @NotNull final CallbackContext callbackContext) {
            this.callbackContext = callbackContext;
            this.nsu = nsu;
        }

        @Override
        public void onCancel() {
            callbackContext.error("PAYMENT_CANCELLED");
        }

        @Override
        public void onError(final PaymentError paymentError) {
            Log.e("LIO", paymentError.getDescription());
            callbackContext.error(paymentError.getDescription());
        }

        @Override
        public void onPayment(final Order order) {
            final Payment payment = order.getPayments().get(order.getPayments().size() - 1);

            try {
                final JSONObject jsonObject = new JSONObject();
                jsonObject.accumulate("NumeroParcelas", payment.getInstallments());
                jsonObject.accumulate("NomeBandeiraCartao", payment.getBrand());
                jsonObject.accumulate("CodigoAutorizacao", 125);
                jsonObject.accumulate("NumeroCartao", payment.getPaymentFields().get("pan"));
                jsonObject.accumulate("NSU", payment.getAuthCode());
                jsonObject.accumulate("NSURede", payment.getCieloCode());
                jsonObject.accumulate("IndiceCodigoBandeira", 0);

                orderMap.put(nsu, order); // updating order
                callbackContext.success(jsonObject);
            } catch (final JSONException e) {
                Log.e("LIO", e.getMessage(), e);
                callbackContext.error("PAYMENT_PARSE_RET_ERROR");
            }
        }

        @Override
        public void onStart() {
            Log.d("LIO", "PAYMENT_STARTED");
        }
    }

    @SuppressWarnings("unused")
    private final class LioCancellationListener implements CancellationListener {
        private final NSU nsu;
        private final Deque<Payment> paymentDeque;
        private final CallbackContext callbackContext;

        private LioCancellationListener(@NotNull final NSU nsu,
                                        @NotNull final Deque<Payment> paymentDeque,
                                        @NotNull final CallbackContext callbackContext) {
            this.nsu = nsu;
            this.paymentDeque = paymentDeque;
            this.callbackContext = callbackContext;
        }

        @Override
        public void onCancel() {
            Log.e("LIO", "PAYMENT_CANCELLATION_CANCELLED");
            callbackContext.error("PAYMENT_CANCELLATION_CANCELLED");
        }

        @Override
        public void onError(final PaymentError paymentError) {
            Log.e("LIO", paymentError.getDescription());
            callbackContext.error(paymentError.getDescription());
        }

        @Override
        public void onSuccess(final Order order) {
            LioPaymentManager.this.orderMap.put(nsu, order); // updating order

            if (paymentDeque.isEmpty()) {
                order.cancel();
                orderManager.updateOrder(order);

                if (Status.CANCELED.equals(order.getStatus())) {
                    orderMap.remove(nsu);
                    callbackContext.success("ORDER_CANCELLATION_SUCCESS");
                } else {
                    callbackContext.error("ORDER_STATUS_UPDATE_ERROR");
                }
            } else {
                final Payment payment = paymentDeque.pop();
                orderManager.cancelOrder(LioPaymentManager.this.context, order.getId(), payment,
                        new LioCancellationListener(nsu, paymentDeque, callbackContext));
            }
        }
    }
}
