#import "ReactNativePayments.h"
#import <React/RCTUtils.h>
#import <React/RCTEventDispatcher.h>

@implementation ReactNativePayments
@synthesize bridge = _bridge;

RCT_EXPORT_MODULE()

- (dispatch_queue_t)methodQueue
{
    return dispatch_get_main_queue();
}

+ (BOOL)requiresMainQueueSetup
{
    return YES;
}

- (NSDictionary *)constantsToExport
{
    return @{
             @"canMakePayments": @([PKPaymentAuthorizationViewController canMakePayments]),
             @"supportedGateways": [GatewayManager getSupportedGateways]
             };
}

RCT_EXPORT_METHOD(canMakePaymentsUsingNetworks:
                  (NSArray *)paymentNetworks
                  callback:(RCTResponseSenderBlock)callback)
{
    callback(@[[NSNull null], @([PKPaymentAuthorizationViewController canMakePaymentsUsingNetworks:paymentNetworks])]);
}

RCT_EXPORT_METHOD(createPaymentRequest: (NSDictionary *)methodData
                  details: (NSDictionary *)details
                  options: (NSDictionary *)options
                  callback: (RCTResponseSenderBlock)callback)
{
    NSString *merchantId = methodData[@"merchantIdentifier"];
    NSDictionary *gatewayParameters = methodData[@"paymentMethodTokenizationParameters"][@"parameters"];

    if (gatewayParameters) {
        self.hasGatewayParameters = true;
        self.gatewayManager = [GatewayManager new];
        [self.gatewayManager configureGateway:gatewayParameters merchantIdentifier:merchantId];
    }

    self.paymentRequest = [[PKPaymentRequest alloc] init];
    self.paymentRequest.merchantIdentifier = merchantId;
    self.paymentRequest.merchantCapabilities = PKMerchantCapability3DS;
    self.paymentRequest.countryCode = methodData[@"countryCode"];
    self.paymentRequest.currencyCode = methodData[@"currencyCode"];
    self.paymentRequest.supportedNetworks = [self getSupportedNetworksFromMethodData:methodData];
    self.paymentRequest.paymentSummaryItems = [self getPaymentSummaryItemsFromDetails:details];
    self.paymentRequest.shippingMethods = [self getShippingMethodsFromDetails:details];

    [self setRequiredAddressFieldsFromOptions:options];

    // Set options so that we can later access it.
    self.initialOptions = options;

    callback(@[[NSNull null]]);
}

RCT_EXPORT_METHOD(show:(RCTResponseSenderBlock)callback)
{

    self.viewController = [[PKPaymentAuthorizationViewController alloc] initWithPaymentRequest: self.paymentRequest];
    self.viewController.delegate = self;

    dispatch_async(dispatch_get_main_queue(), ^{
        UIViewController *rootViewController = RCTPresentedViewController();
        [rootViewController presentViewController:self.viewController animated:YES completion:nil];
        callback(@[[NSNull null]]);
    });
}

RCT_EXPORT_METHOD(abort: (RCTResponseSenderBlock)callback)
{
    [self.viewController dismissViewControllerAnimated:YES completion:nil];

    callback(@[[NSNull null]]);
}

RCT_EXPORT_METHOD(complete: (NSString *)paymentStatus
                  callback: (RCTResponseSenderBlock)callback)
{
    if ([paymentStatus isEqualToString: @"success"]) {
        self.completion(PKPaymentAuthorizationStatusSuccess);
    } else {
        self.completion(PKPaymentAuthorizationStatusFailure);
    }

    callback(@[[NSNull null]]);
}


-(void) paymentAuthorizationViewControllerDidFinish:(PKPaymentAuthorizationViewController *)controller
{
    [controller dismissViewControllerAnimated:YES completion:nil];
    [self.bridge.eventDispatcher sendDeviceEventWithName:@"NativePayments:onuserdismiss" body:nil];
}

RCT_EXPORT_METHOD(handleDetailsUpdate: (NSDictionary *)details
                  callback: (RCTResponseSenderBlock)callback)

{
    if (!self.shippingContactCompletion && !self.shippingMethodCompletion && !self.paymentMethodCompletion) {
        // TODO:
        // - Call callback with error saying shippingContactCompletion was never called;

        return;
    }

    NSArray<PKShippingMethod *> * shippingMethods = [self getShippingMethodsFromDetails:details];

    NSArray<PKPaymentSummaryItem *> * paymentSummaryItems = [self getPaymentSummaryItemsFromDetails:details];

    if (self.paymentMethodCompletion) {
        self.paymentMethodCompletion (paymentSummaryItems);
        self.paymentMethodCompletion = nil;
    }

    if (self.shippingMethodCompletion) {
        self.shippingMethodCompletion(
                                      PKPaymentAuthorizationStatusSuccess,
                                      paymentSummaryItems
                                      );

        // Invalidate `self.shippingMethodCompletion`
        self.shippingMethodCompletion = nil;
    }

    if (self.shippingContactCompletion) {
        // Display shipping address error when shipping is needed and shipping method count is below 1
        if ([self.initialOptions[@"requestShipping"] boolValue] && [shippingMethods count] == 0) {
            return self.shippingContactCompletion(
                                                  PKPaymentAuthorizationStatusInvalidShippingPostalAddress,
                                                  shippingMethods,
                                                  paymentSummaryItems
                                                  );
        } else {
            self.shippingContactCompletion(
                                           PKPaymentAuthorizationStatusSuccess,
                                           shippingMethods,
                                           paymentSummaryItems
                                           );
        }
        // Invalidate `aself.shippingContactCompletion`
        self.shippingContactCompletion = nil;

    }

    // Call callback
    callback(@[[NSNull null]]);

}

// DELEGATES
// ---------------
- (void) paymentAuthorizationViewController:(PKPaymentAuthorizationViewController *)controller
                        didAuthorizePayment:(PKPayment *)payment
                                 completion:(void (^)(PKPaymentAuthorizationStatus))completion
{
    // Store completion for later use
    self.completion = completion;

    if (self.hasGatewayParameters) {
        [self.gatewayManager createTokenWithPayment:payment completion:^(NSString * _Nullable token, NSError * _Nullable error) {
            if (error) {
                [self handleGatewayError:error];
                return;
            }

            [self handleUserAccept:payment paymentToken:token];
        }];
    } else {
        [self handleUserAccept:payment paymentToken:nil];
    }
}


// Shipping Contact
- (void) paymentAuthorizationViewController:(PKPaymentAuthorizationViewController *)controller
                   didSelectShippingContact:(PKContact *)contact
                                 completion:(nonnull void (^)(PKPaymentAuthorizationStatus, NSArray<PKShippingMethod *> * _Nonnull, NSArray<PKPaymentSummaryItem *> * _Nonnull))completion
{
    if ([self.initialOptions[@"requestShipping"] boolValue]) {
        self.shippingContactCompletion = completion;

        CNPostalAddress *postalAddress = contact.postalAddress;
        // street, subAdministrativeArea, and subLocality are supressed for privacy
        [self.bridge.eventDispatcher sendDeviceEventWithName:@"NativePayments:onshippingaddresschange"
                                                        body:@{
                                                            @"recipient": [NSNull null],
                                                            @"organization": [NSNull null],
                                                            @"addressLine": [NSNull null],
                                                            @"city": postalAddress.city,
                                                            @"region": postalAddress.state,
                                                            @"country": [postalAddress.ISOCountryCode uppercaseString],
                                                            @"postalCode": postalAddress.postalCode,
                                                            @"phone": [NSNull null],
                                                            @"languageCode": [NSNull null],
                                                            @"sortingCode": [NSNull null],
                                                            @"dependentLocality": [NSNull null]
                                                            }];
    }
}

// Shipping Method delegates
- (void)paymentAuthorizationViewController:(PKPaymentAuthorizationViewController *)controller
                   didSelectShippingMethod:(PKShippingMethod *)shippingMethod
                                completion:(void (^)(PKPaymentAuthorizationStatus, NSArray<PKPaymentSummaryItem *> * _Nonnull))completion
{
    self.shippingMethodCompletion = completion;

    [self.bridge.eventDispatcher sendDeviceEventWithName:@"NativePayments:onshippingoptionchange" body:@{
                                                                                                         @"selectedShippingOptionId": shippingMethod.identifier
                                                                                                         }];
}

// Payment method changes
 - (void)paymentAuthorizationViewController:(PKPaymentAuthorizationViewController *)controller
                     didSelectPaymentMethod:(PKPaymentMethod *)paymentMethod
                                 completion:(nonnull void (^)(NSArray<PKPaymentSummaryItem *> * _Nonnull))completion
 {
     self.paymentMethodCompletion = completion;
     NSString *type = [self paymentMethodTypeToString:paymentMethod.type];
     [self.bridge.eventDispatcher sendDeviceEventWithName:@"NativePayments:onpaymentmethodchange" body:@{
                                                                                                          @"paymentMethodType": type
                                                                                                          }];
 }

// PRIVATE METHODS
// https://developer.apple.com/reference/passkit/pkpaymentnetwork
// ---------------
- (NSArray *_Nonnull)getSupportedNetworksFromMethodData:(NSDictionary *_Nonnull)methodData
{
    NSMutableDictionary *supportedNetworksMapping = [[NSMutableDictionary alloc] init];

    CGFloat iOSVersion = [[[UIDevice currentDevice] systemVersion] floatValue];

    if (iOSVersion >= 8) {
        [supportedNetworksMapping setObject:PKPaymentNetworkAmex forKey:@"amex"];
        [supportedNetworksMapping setObject:PKPaymentNetworkMasterCard forKey:@"mastercard"];
        [supportedNetworksMapping setObject:PKPaymentNetworkVisa forKey:@"visa"];
    }

    if (iOSVersion >= 9) {
        [supportedNetworksMapping setObject:PKPaymentNetworkDiscover forKey:@"discover"];
        [supportedNetworksMapping setObject:PKPaymentNetworkPrivateLabel forKey:@"privatelabel"];
    }

    if (iOSVersion >= 9.2) {
        [supportedNetworksMapping setObject:PKPaymentNetworkChinaUnionPay forKey:@"chinaunionpay"];
        [supportedNetworksMapping setObject:PKPaymentNetworkInterac forKey:@"interac"];
    }

    if (iOSVersion >= 10.1) {
        [supportedNetworksMapping setObject:PKPaymentNetworkJCB forKey:@"jcb"];
        [supportedNetworksMapping setObject:PKPaymentNetworkSuica forKey:@"suica"];
    }

    if (iOSVersion >= 10.3) {
        [supportedNetworksMapping setObject:PKPaymentNetworkCarteBancaire forKey:@"cartebancaires"];
        [supportedNetworksMapping setObject:PKPaymentNetworkIDCredit forKey:@"idcredit"];
        [supportedNetworksMapping setObject:PKPaymentNetworkQuicPay forKey:@"quicpay"];
    }

    if (iOSVersion >= 11) {
        [supportedNetworksMapping setObject:PKPaymentNetworkCarteBancaires forKey:@"cartebancaires"];
    }

    if (iOSVersion >= 12.1) {
        [supportedNetworksMapping setObject:PKPaymentNetworkMada forKey:@"mada"];
    }

    if (iOSVersion >= 12.1) {
        [supportedNetworksMapping setObject:PKPaymentNetworkMada forKey:@"mada"];
    }

    // Setup supportedNetworks
    NSArray *jsSupportedNetworks = methodData[@"supportedNetworks"];
    NSMutableArray *supportedNetworks = [NSMutableArray array];
    for (NSString *supportedNetwork in jsSupportedNetworks) {
        [supportedNetworks addObject: supportedNetworksMapping[supportedNetwork]];
    }

    return supportedNetworks;
}

- (NSArray<PKPaymentSummaryItem *> *_Nonnull)getPaymentSummaryItemsFromDetails:(NSDictionary *_Nonnull)details
{
    // Setup `paymentSummaryItems` array
    NSMutableArray <PKPaymentSummaryItem *> * paymentSummaryItems = [NSMutableArray array];

    // Add `displayItems` to `paymentSummaryItems`
    NSArray *displayItems = details[@"displayItems"];
    if (displayItems.count > 0) {
        for (NSDictionary *displayItem in displayItems) {
            [paymentSummaryItems addObject: [self convertDisplayItemToPaymentSummaryItem:displayItem]];
        }
    }

    // Add total to `paymentSummaryItems`
    NSDictionary *total = details[@"total"];
    [paymentSummaryItems addObject: [self convertDisplayItemToPaymentSummaryItem:total]];

    return paymentSummaryItems;
}

- (NSArray<PKShippingMethod *> *_Nonnull)getShippingMethodsFromDetails:(NSDictionary *_Nonnull)details
{
    // Setup `shippingMethods` array
    NSMutableArray <PKShippingMethod *> * shippingMethods = nil;
    if ([self.initialOptions[@"requestShipping"] boolValue]) {
        shippingMethods = [NSMutableArray array];

        // Add `shippingOptions` to `shippingMethods`
        NSArray *shippingOptions = details[@"shippingOptions"];
        if (shippingOptions.count > 0) {
            for (NSDictionary *shippingOption in shippingOptions) {
                [shippingMethods addObject: [self convertShippingOptionToShippingMethod:shippingOption]];
            }
        }
    }

    return shippingMethods;
}

- (PKPaymentSummaryItem *_Nonnull)convertDisplayItemToPaymentSummaryItem:(NSDictionary *_Nonnull)displayItem;
{
    NSDecimalNumber *decimalNumberAmount = [NSDecimalNumber decimalNumberWithString:displayItem[@"amount"][@"value"]];
    PKPaymentSummaryItemType itemType = PKPaymentSummaryItemTypeFinal;
    if ([displayItem[@"pending"] respondsToSelector:@selector(boolValue)] && [displayItem[@"pending"] boolValue] == YES) {
        itemType = PKPaymentSummaryItemTypePending;
    }
    PKPaymentSummaryItem *paymentSummaryItem = [PKPaymentSummaryItem
                                                summaryItemWithLabel:displayItem[@"label"]
                                                amount:decimalNumberAmount
                                                type:itemType];

    return paymentSummaryItem;
}

- (PKShippingMethod *_Nonnull)convertShippingOptionToShippingMethod:(NSDictionary *_Nonnull)shippingOption
{
    PKShippingMethod *shippingMethod = [PKShippingMethod summaryItemWithLabel:shippingOption[@"label"] amount:[NSDecimalNumber decimalNumberWithString: shippingOption[@"amount"][@"value"]]];
    shippingMethod.identifier = shippingOption[@"id"];

    // shippingOption.detail is not part of the PaymentRequest spec.
    if ([shippingOption[@"detail"] isKindOfClass:[NSString class]]) {
        shippingMethod.detail = shippingOption[@"detail"];
    } else {
        shippingMethod.detail = @"";
    }

    return shippingMethod;
}

- (void)setRequiredAddressFieldsFromOptions:(NSDictionary *_Nonnull)options
{
    // Request Shipping
    if ([options[@"requestShipping"] boolValue]) {
        NSMutableSet <PKContactField> *shippingContactFields = [NSMutableSet setWithArray:@[PKContactFieldPostalAddress]];
        if ([options[@"requestPayerName"] boolValue]) {
            [shippingContactFields addObject:PKContactFieldName];
        }

        if ([options[@"requestPayerPhone"] boolValue]) {
            [shippingContactFields addObject:PKContactFieldPhoneNumber];
        }

        if ([options[@"requestPayerEmail"] boolValue]) {
            [shippingContactFields addObject:PKContactFieldEmailAddress];
        }
        self.paymentRequest.requiredShippingContactFields = shippingContactFields;
    }

    if ([options[@"requestBilling"] boolValue]) {
        self.paymentRequest.requiredBillingContactFields = [NSSet setWithArray:@[PKContactFieldPostalAddress]];
    }
}

- (NSString *_Nonnull)contactToString:(PKContact *_Nonnull)contact
{
    NSString *namePrefix = contact.name.namePrefix;
    NSString *givenName = contact.name.givenName;
    NSString *middleName = contact.name.middleName;
    NSString *familyName = contact.name.familyName;
    NSString *nameSuffix = contact.name.nameSuffix;
    NSString *nickname = contact.name.nickname;
    NSString *street = contact.postalAddress.street;
    NSString *subLocality = contact.postalAddress.subLocality;
    NSString *city = contact.postalAddress.city;
    NSString *subAdministrativeArea = contact.postalAddress.subAdministrativeArea;
    NSString *state = contact.postalAddress.state;
    NSString *postalCode = contact.postalAddress.postalCode;
    NSString *country = contact.postalAddress.country;
    NSString *ISOCountryCode = contact.postalAddress.ISOCountryCode;
    NSString *phoneNumber = contact.phoneNumber.stringValue;
    NSString *emailAddress = contact.emailAddress;

    NSDictionary *contactDict = @{
         @"name" : @{
                 @"namePrefix" : namePrefix ?: @"",
                 @"givenName" : givenName ?: @"",
                 @"middleName" : middleName ?: @"",
                 @"familyName" : familyName ?: @"",
                 @"nameSuffix" : nameSuffix ?: @"",
                 @"nickname" : nickname ?: @"",
         },
         @"postalAddress" : @{
                 @"street" : street ?: @"",
                 @"subLocality" : subLocality ?: @"",
                 @"city" : city ?: @"",
                 @"subAdministrativeArea" : subAdministrativeArea ?: @"",
                 @"state" : state ?: @"",
                 @"postalCode" : postalCode ?: @"",
                 @"country" : country ?: @"",
                 @"ISOCountryCode" : ISOCountryCode ?: @""
         },
         @"phoneNumber" : phoneNumber ?: @"",
         @"emailAddress" : emailAddress ?: @""
    };

    NSError *error;
    NSData *jsonData = [NSJSONSerialization dataWithJSONObject:contactDict options:0 error:&error];

    if (! jsonData) {
       return @"";
    } else {
       return [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
    }

}

- (NSDictionary *_Nonnull)paymentMethodToString:(PKPaymentMethod *_Nonnull)paymentMethod
{
    NSMutableDictionary *result = [[NSMutableDictionary alloc]initWithCapacity:4];

    if(paymentMethod.displayName) {
        [result setObject:paymentMethod.displayName forKey:@"displayName"];
    }
    if (paymentMethod.network) {
        [result setObject:paymentMethod.network forKey:@"network"];
    }
    NSString *type = [self paymentMethodTypeToString:paymentMethod.type];
    [result setObject:type forKey:@"type"];
    if(paymentMethod.paymentPass) {
        NSDictionary *paymentPass = [self paymentPassToDictionary:paymentMethod.paymentPass];
        [result setObject:paymentPass forKey:@"paymentPass"];
    }

    return result;
}

- (NSString *_Nonnull)paymentMethodTypeToString:(PKPaymentMethodType)paymentMethodType
{
    NSArray *arr = @[@"PKPaymentMethodTypeUnknown",
                     @"PKPaymentMethodTypeDebit",
                     @"PKPaymentMethodTypeCredit",
                     @"PKPaymentMethodTypePrepaid",
                     @"PKPaymentMethodTypeStore"];
    return (NSString *)[arr objectAtIndex:paymentMethodType];
}

- (NSDictionary *_Nonnull)paymentPassToDictionary:(PKPaymentPass *_Nonnull)paymentPass
{
    return @{
        @"primaryAccountIdentifier" : paymentPass.primaryAccountIdentifier,
        @"primaryAccountNumberSuffix" : paymentPass.primaryAccountNumberSuffix,
        @"deviceAccountIdentifier" : paymentPass.deviceAccountIdentifier,
        @"deviceAccountNumberSuffix" : paymentPass.deviceAccountNumberSuffix,
        @"activationState" : [self paymentPassActivationStateToString:paymentPass.activationState]
    };
}

- (NSString *_Nonnull)paymentPassActivationStateToString:(PKPaymentPassActivationState)paymentPassActivationState
{
    NSArray *arr = @[@"PKPaymentPassActivationStateActivated",
                     @"PKPaymentPassActivationStateRequiresActivation",
                     @"PKPaymentPassActivationStateActivating",
                     @"PKPaymentPassActivationStateSuspended",
                     @"PKPaymentPassActivationStateDeactivated"];
    return (NSString *)[arr objectAtIndex:paymentPassActivationState];
}

- (void)handleUserAccept:(PKPayment *_Nonnull)payment
            paymentToken:(NSString *_Nullable)token
{
    NSMutableDictionary *paymentResponse = [[NSMutableDictionary alloc]initWithCapacity:6];

    NSString *transactionId = payment.token.transactionIdentifier;
    [paymentResponse setObject:transactionId forKey:@"transactionIdentifier"];

    NSString *paymentData = [[NSString alloc] initWithData:payment.token.paymentData encoding:NSUTF8StringEncoding];
    [paymentResponse setObject:paymentData forKey:@"paymentData"];

    NSDictionary *paymentMethod = [self paymentMethodToString:payment.token.paymentMethod];
    [paymentResponse setObject:paymentMethod forKey:@"paymentMethod"];

    if (token) {
        [paymentResponse setObject:token forKey:@"paymentToken"];
    }

    if (payment.billingContact) {
        paymentResponse[@"billingContact"] = [self contactToString:payment.billingContact];
    }

    if (payment.shippingContact) {
        paymentResponse[@"shippingContact"] = [self contactToString:payment.shippingContact];
    }

    [self.bridge.eventDispatcher sendDeviceEventWithName:@"NativePayments:onuseraccept"
                                                    body:paymentResponse
     ];
}

- (void)handleGatewayError:(NSError *_Nonnull)error
{
    [self.bridge.eventDispatcher sendDeviceEventWithName:@"NativePayments:ongatewayerror"
                                                    body: @{
                                                            @"error": [error localizedDescription]
                                                            }
     ];
}

@end
