package com.gettipsi.stripe.util;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.text.TextUtils;

import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.WritableNativeArray;
import com.facebook.react.bridge.WritableNativeMap;
import com.google.android.gms.identity.intents.model.CountrySpecification;
import com.google.android.gms.identity.intents.model.UserAddress;
import com.google.android.gms.wallet.PaymentData;
import com.stripe.android.PaymentIntentResult;
import com.stripe.android.SetupIntentResult;
import com.stripe.android.model.Address;
import com.stripe.android.model.BankAccount;
import com.stripe.android.model.BankAccountTokenParams;
import com.stripe.android.model.Card;
import com.stripe.android.model.PaymentIntent;
import com.stripe.android.model.PaymentMethod;
import com.stripe.android.model.SetupIntent;
import com.stripe.android.model.Source;
import com.stripe.android.model.Source.CodeVerification;
import com.stripe.android.model.Source.Owner;
import com.stripe.android.model.Source.Receiver;
import com.stripe.android.model.Source.Redirect;
import com.stripe.android.model.Token;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;

/**
 * Created by ngoriachev on 13/03/2018.
 */

public class Converters {

  public static WritableMap convertTokenToWritableMap(Token token) {
    WritableMap newToken = Arguments.createMap();

    if (token == null) return newToken;

    newToken.putString("tokenId", token.getId());
    newToken.putBoolean("livemode", token.getLivemode());
    newToken.putBoolean("used", token.getUsed());
    newToken.putDouble("created", token.getCreated().getTime());

    if (token.getCard() != null) {
      newToken.putMap("card", convertCardToWritableMap(token.getCard()));
    }
    if (token.getBankAccount() != null) {
      newToken.putMap("bankAccount", convertBankAccountToWritableMap(token.getBankAccount()));
    }

    return newToken;
  }

  public static WritableMap putExtraToTokenMap(final WritableMap tokenMap, UserAddress billingAddress, UserAddress shippingAddress, String emailAddress) {
    ArgCheck.nonNull(tokenMap);

    WritableMap extra = Arguments.createMap();

    //add email address to billing and shipping contact as per apple
    WritableMap billingContactMap = convertAddressToWritableMap(billingAddress);
    WritableMap shippingContactMap = convertAddressToWritableMap(shippingAddress);

    billingContactMap.putString("emailAddress", emailAddress);
    shippingContactMap.putString("emailAddress", emailAddress);


    extra.putMap("billingContact", billingContactMap);
    extra.putMap("shippingContact", shippingContactMap);

    tokenMap.putMap("extra", extra);

    return tokenMap;
  }

  private static WritableMap convertCardToWritableMap(final Card card) {
    WritableMap result = Arguments.createMap();

    if (card == null) return result;

    result.putString("cardId", card.getId());
    result.putString("number", card.getNumber());
    result.putString("cvc", card.getCvc() );
    result.putInt("expMonth", card.getExpMonth() );
    result.putInt("expYear", card.getExpYear() );
    result.putString("name", card.getName() );
    result.putString("addressLine1", card.getAddressLine1() );
    result.putString("addressLine2", card.getAddressLine2() );
    result.putString("addressCity", card.getAddressCity() );
    result.putString("addressState", card.getAddressState() );
    result.putString("addressZip", card.getAddressZip() );
    result.putString("addressCountry", card.getAddressCountry() );
    result.putString("last4", card.getLast4() );
    result.putString("fingerprint", card.getFingerprint() );
    result.putString("country", card.getCountry() );
    result.putString("currency", card.getCurrency() );

    return result;
  }

  public static WritableMap convertBankAccountToWritableMap(BankAccount account) {
    WritableMap result = Arguments.createMap();

    if (account == null) return result;

    result.putString("routingNumber", account.getRoutingNumber());
    result.putString("countryCode", account.getCountryCode());
    result.putString("currency", account.getCurrency());
    result.putString("accountHolderName", account.getAccountHolderName());
    result.putString("fingerprint", account.getFingerprint());
    result.putString("bankName", account.getBankName());
    result.putString("last4", account.getLast4());

    return result;
  }

  public static String getValue(final ReadableMap map, final String key, final String def) {
    if (map.hasKey(key)) {
      return map.getString(key);
    } else {
      // If map don't have some key - we must pass to constructor default value.
      return def;
    }
  }

  public static Boolean getValue(final ReadableMap map, final String key, final Boolean def) {
    if (map.hasKey(key)) {
      return map.getBoolean(key);
    } else {
      // If map don't have some key - we must pass to constructor default value.
      return def;
    }
  }

  public static ReadableArray getValue(final ReadableMap map, final String key, final ReadableArray def) {
    if (map.hasKey(key)) {
      return map.getArray(key);
    } else {
      // If map don't have some key - we must pass to constructor default value.
      return def;
    }
  }

  public static String getValue(final ReadableMap map, final String key) {
    return getValue(map, key, (String) null);
  }

  public static Collection<String> getAllowedShippingCountryCodes(final ReadableMap map) {
    ArrayList<String> allowedCountryCodesForShipping = new ArrayList<>();
    ReadableArray countries = getValue(map, "shipping_countries", (ReadableArray) null);

    if (countries != null){
      for (int i = 0; i < countries.size(); i++) {
        String code = countries.getString(i);
        allowedCountryCodesForShipping.add(code);
      }
    }

    return allowedCountryCodesForShipping;
  }

  public static ArrayList<CountrySpecification> getAllowedShippingCountries(final ReadableMap map) {
    ArrayList<CountrySpecification> allowedCountriesForShipping = new ArrayList<>();
    ReadableArray countries = getValue(map, "shipping_countries", (ReadableArray) null);

    if (countries != null){
      for (int i = 0; i < countries.size(); i++) {
        String code = countries.getString(i);
        allowedCountriesForShipping.add(new CountrySpecification(code));
      }
    }

    return allowedCountriesForShipping;
  }

  public static Card createCard(final ReadableMap cardData) {
    return new Card.Builder(
      cardData.getString("number"),
      cardData.getInt("expMonth"),
      cardData.getInt("expYear"),
      getValue(cardData, "cvc"))
      .name(getValue(cardData, "name"))
      .addressLine1(getValue(cardData, "addressLine1"))
      .addressLine2(getValue(cardData, "addressLine2"))
      .addressCity(getValue(cardData, "addressCity"))
      .addressState(getValue(cardData, "addressState"))
      .addressZip(getValue(cardData, "addressZip"))
      .addressCountry(getValue(cardData, "addressCountry"))
      .last4(getValue(cardData, "last4"))
      .fingerprint(getValue(cardData, "fingerprint"))
      .country(getValue(cardData, "country"))
      .currency(getValue(cardData, "currency"))
      .id(getValue(cardData, "id"))
      .build();
  }



  @NonNull
  public static WritableMap convertSourceToWritableMap(@Nullable Source source) {
    WritableMap newSource = Arguments.createMap();

    if (source == null) {
      return newSource;
    }

    newSource.putString("sourceId", source.getId());
    newSource.putInt("amount", source.getAmount().intValue());
    newSource.putInt("created", source.getCreated().intValue());
    newSource.putString("currency", source.getCurrency());
    newSource.putString("flow", String.valueOf(source.getFlow()));
    newSource.putBoolean("livemode", source.isLiveMode());
    newSource.putMap("metadata", stringMapToWritableMap(source.getMetaData()));
    newSource.putMap("owner", convertOwnerToWritableMap(source.getOwner()));
    newSource.putMap("receiver", convertReceiverToWritableMap(source.getReceiver()));
    newSource.putMap("redirect", convertRedirectToWritableMap(source.getRedirect()));
    newSource.putMap("sourceTypeData", mapToWritableMap(source.getSourceTypeData()));
    newSource.putString("status", String.valueOf(source.getStatus()));
    newSource.putString("type", source.getType());
    newSource.putString("typeRaw", source.getTypeRaw());
    newSource.putString("usage", String.valueOf(source.getUsage()));

    return newSource;
  }

  @NonNull
  public static WritableMap convertPaymentIntentResultToWritableMap(@Nullable PaymentIntentResult paymentIntentResult) {
    WritableMap wm = Arguments.createMap();

    if (paymentIntentResult == null) {
      wm.putString("status", "unknown");
      return wm;
    }

    PaymentIntent intent = paymentIntentResult.getIntent();
    wm.putString("status", intent.getStatus().toString());
    wm.putString("paymentIntentId", intent.getId());

//    String paymentMethodId = intent.getPaymentMethodId();
//    if (paymentMethodId != null) {
//      wm.putString("paymentMethodId", paymentMethodId);
//    }
    return wm;
  }


  @NonNull
  public static WritableMap convertSetupIntentResultToWritableMap(@Nullable SetupIntentResult setupIntentResult) {
    WritableMap wm = Arguments.createMap();

    if (setupIntentResult == null) {
      wm.putString("status", "unknown");
      return wm;
    }

    SetupIntent intent = setupIntentResult.getIntent();
    wm.putString("status", intent.getStatus().toString());
    wm.putString("setupIntentId", intent.getId());

    String paymentMethodId = intent.getPaymentMethodId();
    if (paymentMethodId != null) {
      wm.putString("paymentMethodId", paymentMethodId);
    }
    return wm;
  }

  @NonNull
  public static WritableMap convertPaymentMethodToWritableMap(@Nullable PaymentMethod paymentMethod) {
    WritableMap wm = Arguments.createMap();

    if (paymentMethod == null) {
      return wm;
    }

    wm.putString("id", paymentMethod.id);
    wm.putInt("created", paymentMethod.created.intValue());
    wm.putBoolean("livemode", paymentMethod.liveMode);
    wm.putString("type", String.valueOf(paymentMethod.type));
    wm.putMap("billingDetails", convertBillingDetailsToWritableMap(paymentMethod.billingDetails));
    wm.putMap("card", convertPaymentMethodCardToWritableMap(paymentMethod.card));
    wm.putString("customerId", paymentMethod.customerId);

    // TODO support metadata
    return wm;
  }

  @NonNull
  public static WritableMap convertPaymentMethodCardToWritableMap(@Nullable final PaymentMethod.Card card) {
    WritableMap wm = Arguments.createMap();

    if (card == null) {
      return wm;
    }

    // Omitted (can be introduced later): card.checks, card.threeDSecureUsage, card.wallet

    wm.putString("brand", String.valueOf(card.brand));
    wm.putString("country", card.country);
    wm.putInt("expMonth", card.expiryMonth);
    wm.putInt("expYear", card.expiryYear);
    wm.putString("funding", card.funding);
    wm.putString("last4", card.last4);
    return wm;
  }

  @NonNull
  public static WritableMap convertBillingDetailsToWritableMap(@Nullable final PaymentMethod.BillingDetails billingDetails) {
    WritableMap wm = Arguments.createMap();

    if (billingDetails == null) {
      return wm;
    }

    wm.putMap("address", convertAddressToWritableMap(billingDetails.address));
    wm.putString("email", billingDetails.email);
    wm.putString("name", billingDetails.name);
    wm.putString("phone", billingDetails.phone);
    return wm;
  }


  @NonNull
  public static WritableMap stringMapToWritableMap(@Nullable Map<String, String> map) {
    WritableMap writableMap = Arguments.createMap();

    if (map == null) {
      return writableMap;
    }

    for (Map.Entry<String, String> entry : map.entrySet()) {
      writableMap.putString(entry.getKey(), entry.getValue());
    }

    return writableMap;
  }

  @NonNull
  public static WritableMap convertOwnerToWritableMap(@Nullable final Owner owner) {
    WritableMap map = Arguments.createMap();

    if (owner == null) {
      return map;
    }

    map.putMap("address", convertAddressToWritableMap(owner.getAddress()));
    map.putString("email", owner.getEmail());
    map.putString("name", owner.getName());
    map.putString("phone", owner.getPhone());
    map.putString("verifiedEmail", owner.getVerifiedEmail());
    map.putString("verifiedPhone", owner.getVerifiedPhone());
    map.putString("verifiedName", owner.getVerifiedName());
    map.putMap("verifiedAddress", convertAddressToWritableMap(owner.getVerifiedAddress()));

    return map;
  }

  @NonNull
  public static WritableMap convertAddressToWritableMap(@Nullable final Address address) {
    WritableMap map = Arguments.createMap();

    if (address == null) {
      return map;
    }

    map.putString("city", address.getCity());
    map.putString("country", address.getCountry());
    map.putString("line1", address.getLine1());
    map.putString("line2", address.getLine2());
    map.putString("postalCode", address.getPostalCode());
    map.putString("state", address.getState());

    return map;
  }

  @NonNull
  public static WritableMap convertReceiverToWritableMap(@Nullable final Receiver receiver) {
    WritableMap map = Arguments.createMap();

    if (receiver == null) {
      return map;
    }

    map.putInt("amountCharged", (int) receiver.getAmountCharged());
    map.putInt("amountReceived", (int) receiver.getAmountReceived());
    map.putInt("amountReturned", (int) receiver.getAmountReturned());
    map.putString("address", receiver.getAddress());

    return map;
  }

  @NonNull
  public static WritableMap convertRedirectToWritableMap(@Nullable Redirect redirect) {
    WritableMap map = Arguments.createMap();

    if (redirect == null) {
      return map;
    }

    map.putString("returnUrl", redirect.getReturnUrl());
    map.putString("status", String.valueOf(redirect.getStatus()));
    map.putString("url", redirect.getUrl());

    return map;
  }

  @NonNull
  public static WritableMap convertCodeVerificationToWritableMap(@Nullable CodeVerification codeVerification) {
    WritableMap map = Arguments.createMap();

    if (codeVerification == null) {
      return map;
    }

    map.putInt("attemptsRemaining", codeVerification.getAttemptsRemaining());
    map.putString("status", String.valueOf(codeVerification.getStatus()));

    return map;
  }

  @NonNull
  public static WritableMap mapToWritableMap(@Nullable Map<String, Object> map){
    WritableMap writableMap = Arguments.createMap();

    if (map == null) {
      return writableMap;
    }

    for (String key: map.keySet()) {
      pushRightTypeToMap(writableMap, key, map.get(key));
    }

    return writableMap;
  }

  public static void pushRightTypeToMap(@NonNull WritableMap map, @NonNull String key, @NonNull Object object) {
    Class argumentClass = object.getClass();
    if (argumentClass == Boolean.class) {
      map.putBoolean(key, (Boolean) object);
    } else if (argumentClass == Integer.class) {
      map.putDouble(key, ((Integer)object).doubleValue());
    } else if (argumentClass == Double.class) {
      map.putDouble(key, (Double) object);
    } else if (argumentClass == Float.class) {
      map.putDouble(key, ((Float)object).doubleValue());
    } else if (argumentClass == String.class) {
      map.putString(key, object.toString());
    } else if (argumentClass == WritableNativeMap.class) {
      map.putMap(key, (WritableNativeMap)object);
    } else if (argumentClass == WritableNativeArray.class) {
      map.putArray(key, (WritableNativeArray) object);
    } else {

    }
  }

  public static WritableMap convertAddressToWritableMap(final UserAddress address){
    WritableMap result = Arguments.createMap();

    if (address == null) return result;

    putIfNotEmpty(result, "address1", address.getAddress1());
    putIfNotEmpty(result, "address2", address.getAddress2());
    putIfNotEmpty(result, "address3", address.getAddress3());
    putIfNotEmpty(result, "address4", address.getAddress4());
    putIfNotEmpty(result, "address5", address.getAddress5());
    putIfNotEmpty(result, "administrativeArea", address.getAdministrativeArea());
    putIfNotEmpty(result, "companyName", address.getCompanyName());
    putIfNotEmpty(result, "countryCode", address.getCountryCode());
    putIfNotEmpty(result, "locality", address.getLocality());
    putIfNotEmpty(result, "name", address.getName());
    putIfNotEmpty(result, "phoneNumber", address.getPhoneNumber());
    putIfNotEmpty(result, "postalCode", address.getPostalCode());
    putIfNotEmpty(result, "sortingCode", address.getSortingCode());

    return result;
  }


  public static String getStringOrNull(@NonNull ReadableMap map, @NonNull String key) {
    return map.hasKey(key) ? map.getString(key) : null;
  }

  public static ReadableMap getMapOrNull(@NonNull ReadableMap map, @NonNull String key) {
    return map.hasKey(key) ? map.getMap(key) : null;
  }

  public static boolean getBooleanOrNull(@NonNull ReadableMap map, @NonNull String key, boolean defaultVal) {
    return map.hasKey(key) ? map.getBoolean(key) : defaultVal;
  }

  public static void putIfNotEmpty(final WritableMap map, final String key, final String value) {
    if (!TextUtils.isEmpty(value)) {
      map.putString(key, value);
    }
  }

  public static UserAddress getBillingAddress(PaymentData paymentData) {
    if (paymentData != null && paymentData.getCardInfo() != null) {
      return paymentData.getCardInfo().getBillingAddress();
    }

    return null;
  }

}
