//
//  Pushwoosh.m
//  Pushwoosh React Native Plugin
//  (c) Pushwoosh 2016
//

#import "Pushwoosh.h"

#import <React/RCTUtils.h>
#import <React/RCTBridge.h>
#import "PWEventDispatcher.h"
#import <React/RCTEventDispatcher.h>
#import <React/RCTConvert.h>
#import <React/RCTLinkingManager.h>

#import <UserNotifications/UserNotifications.h>
#import <PushwooshFramework/PushNotificationManager.h>
#import <PushwooshCore/PushwooshConfig.h>

#import <objc/runtime.h>
#import <objc/message.h>

#define kPushwooshPluginImplementationInfoPlistKey @"Pushwoosh_PLUGIN_NOTIFICATION_HANDLER"

#define PW_COMMUNICATION_ENABLED_KEY @"PushwooshCommunicationEnabled"
#define PW_COMMUNICATION_ENABLED_PLIST_KEY @"Pushwoosh_ALLOW_SERVER_COMMUNICATION"


static id objectOrNull(id object) {
    if (object) {
        return object;
    } else {
        return [NSNull null];
    }
}

static NSDictionary * gStartPushData = nil;
static NSURL * gPushDeepLinkURL = nil;  // Deep link URL for New Architecture support
static NSString * const kRegistrationSuccesEvent = @"PWRegistrationSuccess";
static NSString * const kRegistrationErrorEvent = @"PWRegistrationError";
static NSString * const kPushReceivedEvent = @"PWPushReceived";
static NSString * const kPushOpenEvent = @"PWPushOpen";

static NSString * const kPushOpenJSEvent = @"pushOpened";
static NSString * const kPushReceivedJSEvent = @"pushReceived";

@interface PushwooshPlugin (InnerPushwooshPlugin)

- (void) application:(UIApplication *)application pwplugin_didRegisterWithDeviceToken:(NSData *)deviceToken;
- (void) application:(UIApplication *)application pwplugin_didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler;
- (void) application:(UIApplication *)application pwplugin_didFailToRegisterForRemoteNotificationsWithError:(NSError *)error;

@end

@interface RCTLinkingManager (PushwooshSwizzle)
- (void)pwplugin_original_getInitialURL:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject;
@end

// Swizzled getInitialURL for RCTLinkingManager (New Architecture support)
static void pwplugin_getInitialURL(id self, SEL _cmd, RCTPromiseResolveBlock resolve, RCTPromiseRejectBlock reject) {
    // If we have a saved deep link URL from push notification, return it
    if (gPushDeepLinkURL) {
        NSURL *url = gPushDeepLinkURL;
        gPushDeepLinkURL = nil;  // Clear after use (one-time)
        resolve(url.absoluteString);
        return;
    }

    // Otherwise call the original implementation
    SEL originalSelector = @selector(pwplugin_original_getInitialURL:reject:);
    if ([self respondsToSelector:originalSelector]) {
        void (*originalIMP)(id, SEL, RCTPromiseResolveBlock, RCTPromiseRejectBlock);
        originalIMP = (void (*)(id, SEL, RCTPromiseResolveBlock, RCTPromiseRejectBlock))objc_msgSend;
        originalIMP(self, originalSelector, resolve, reject);
    } else {
        resolve([NSNull null]);
    }
}

void pushwoosh_swizzle(Class class, SEL fromChange, SEL toChange, IMP impl, const char * signature) {
    Method method = nil;
    method = class_getInstanceMethod(class, fromChange);
    
    if (method) {
        //method exists add a new method and swap with original
        class_addMethod(class, toChange, impl, signature);
        method_exchangeImplementations(class_getInstanceMethod(class, fromChange), class_getInstanceMethod(class, toChange));
    } else {
        //just add as orignal method
        class_addMethod(class, fromChange, impl, signature);
    }
}

@implementation PushwooshPlugin {
    NSString *lastPushwooshHash;
    
    API_AVAILABLE(ios(10))
    __weak id<UNUserNotificationCenterDelegate> _originalNotificationCenterDelegate;
    API_AVAILABLE(ios(10))
    struct {
        unsigned int willPresentNotification : 1;
        unsigned int didReceiveNotificationResponse : 1;
        unsigned int openSettingsForNotification : 1;
    } _originalNotificationCenterDelegateResponds;
}

#pragma mark - Pushwoosh RCTBridgeModule

RCT_EXPORT_MODULE(Pushwoosh);

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

+ (NSString *)getPluginImplementationInfoPlistKey {
    return [[NSBundle mainBundle] objectForInfoDictionaryKey:kPushwooshPluginImplementationInfoPlistKey];
}

RCT_EXPORT_METHOD(init:(NSDictionary*)config success:(RCTResponseSenderBlock)success error:(RCTResponseSenderBlock)error) {
    NSString *appCode = config[@"pw_appid"];
    NSString *notificationHandling = config[@"pw_notification_handling"];
    
    if (!appCode || ![appCode isKindOfClass:[NSString class]]) {
        if (error) {
            error(@[ @"pw_appid is missing" ]);
        }
        
        return;
    }
    
    [PushNotificationManager initializeWithAppCode:appCode appName:nil];
    [[PushNotificationManager pushManager] sendAppOpen];
    [PushNotificationManager pushManager].delegate = self;

    [PushwooshPlugin swizzleNotificationSettingsHandler];

    // We set Pushwoosh UNUserNotificationCenter delegate unless CUSTOM is specified in the config
    if(![notificationHandling isEqualToString:@"CUSTOM"]) {
        if (![PushwooshPlugin getPluginImplementationInfoPlistKey]) {
            [UNUserNotificationCenter currentNotificationCenter].delegate = [PushNotificationManager pushManager].notificationCenterDelegate;
        } else {
            if (@available(iOS 10, *)) {
                BOOL shouldReplaceDelegate = YES;
                
                UNUserNotificationCenter *notificationCenter = [UNUserNotificationCenter currentNotificationCenter];
                
#if !TARGET_OS_OSX
                if ([notificationCenter.delegate conformsToProtocol:@protocol(PushNotificationDelegate)]) {
                    shouldReplaceDelegate = NO;
                }
#endif
                
                if (notificationCenter.delegate != nil && shouldReplaceDelegate) {
                    _originalNotificationCenterDelegate = notificationCenter.delegate;
                    _originalNotificationCenterDelegateResponds.openSettingsForNotification =
                    (unsigned int)[_originalNotificationCenterDelegate
                                   respondsToSelector:@selector(userNotificationCenter:openSettingsForNotification:)];
                    _originalNotificationCenterDelegateResponds.willPresentNotification =
                    (unsigned int)[_originalNotificationCenterDelegate
                                   respondsToSelector:@selector(userNotificationCenter:
                                                                willPresentNotification:withCompletionHandler:)];
                    _originalNotificationCenterDelegateResponds.didReceiveNotificationResponse =
                    (unsigned int)[_originalNotificationCenterDelegate
                                   respondsToSelector:@selector(userNotificationCenter:
                                                                didReceiveNotificationResponse:withCompletionHandler:)];
                }
                
                if (shouldReplaceDelegate) {
                    __strong PushwooshPlugin<UNUserNotificationCenterDelegate> *strongSelf = (PushwooshPlugin<UNUserNotificationCenterDelegate> *)self;
                    notificationCenter.delegate = (id<UNUserNotificationCenterDelegate>)strongSelf;
                }
            }
        }
    }

    if (success) {
        success(@[]);
    }
    
    if (gStartPushData) {
        NSString *link = gStartPushData[@"l"];
        
        //get deeplink from the payload and write it to the launchOptions for proper RCTLinking behavior
        if (link) {
            NSMutableDictionary *launchOptions = self.bridge.launchOptions.mutableCopy;
            launchOptions[UIApplicationLaunchOptionsURLKey] = [NSURL URLWithString:link];
            [self.bridge setValue:launchOptions forKey:@"launchOptions"];
        }
        
        [self sendJSEvent:kPushReceivedJSEvent withArgs:gStartPushData];
        [self sendJSEvent:kPushOpenJSEvent withArgs:gStartPushData];
    } else if([PushNotificationManager pushManager].launchNotification) {
        [self sendJSEvent:kPushReceivedJSEvent withArgs:[PushNotificationManager pushManager].launchNotification];
        [self sendJSEvent:kPushOpenJSEvent withArgs:[PushNotificationManager pushManager].launchNotification];
    }
}

RCT_EXPORT_METHOD(register:(RCTResponseSenderBlock)success error:(RCTResponseSenderBlock)error) {
    [[PWEventDispatcher sharedDispatcher] subscribe:success toEvent:kRegistrationSuccesEvent];
    [[PWEventDispatcher sharedDispatcher] subscribe:error toEvent:kRegistrationErrorEvent];
    
    [[PushNotificationManager pushManager] registerForPushNotifications];
}

RCT_EXPORT_METHOD(unregister:(RCTResponseSenderBlock)successCallback error:(RCTResponseSenderBlock)errorCallback) {
    [[PushNotificationManager pushManager] unregisterForPushNotificationsWithCompletion:^(NSError *error) {
        if (!error && successCallback) {
            successCallback(@[]);
        }
        
        if (error && errorCallback) {
            errorCallback(@[ objectOrNull([error localizedDescription]) ]);
        }
    }];
}

RCT_EXPORT_METHOD(onPushOpen:(RCTResponseSenderBlock)callback) {
    [[PWEventDispatcher sharedDispatcher] subscribe:callback toEvent:kPushOpenEvent];
    
    if (gStartPushData) {
        NSDictionary *pushData = gStartPushData;
        gStartPushData = nil;
        [[PWEventDispatcher sharedDispatcher] dispatchEvent:kPushOpenEvent withArgs:@[ objectOrNull(pushData) ]];
    }
}

RCT_EXPORT_METHOD(onPushReceived:(RCTResponseSenderBlock)callback) {
    [[PWEventDispatcher sharedDispatcher] subscribe:callback toEvent:kPushReceivedEvent];
    
    if (gStartPushData) {
        NSDictionary *pushData = gStartPushData;
        gStartPushData = nil;
        [[PWEventDispatcher sharedDispatcher] dispatchEvent:kPushReceivedEvent withArgs:@[ objectOrNull(pushData) ]];
    }
}

RCT_EXPORT_METHOD(getHwid:(RCTResponseSenderBlock)callback) {
    if (callback) {
        callback(@[ [[PushNotificationManager pushManager] getHWID] ]);
    }
}

RCT_EXPORT_METHOD(getUserId:(RCTResponseSenderBlock)callback) {
    if (callback) {
        callback(@[ [[Pushwoosh sharedInstance] getUserId] ]);
    }
}

RCT_EXPORT_METHOD(getPushToken:(RCTResponseSenderBlock)callback) {
    if (callback) {
        callback(@[ objectOrNull([[PushNotificationManager pushManager] getPushToken]) ]);
    }
}

RCT_EXPORT_METHOD(setEmails:(NSArray *)emails success:(RCTResponseSenderBlock)successCallback error:(RCTResponseSenderBlock)errorCallback) {
    __block NSError* gError = nil;
    [[Pushwoosh sharedInstance] setEmails:emails completion:^(NSError * _Nullable error) {
        if (error) {
            gError = error;
        }
    }];
    if (!gError && successCallback) {
        successCallback(@[]);
    }
    
    if (gError && errorCallback) {
        errorCallback(@[ objectOrNull([gError localizedDescription]) ]);
    }
}

RCT_EXPORT_METHOD(setUserEmails:(NSString*)userId emails:(NSArray *)emails success:(RCTResponseSenderBlock)successCallback error:(RCTResponseSenderBlock)errorCallback) {
    __block NSError* gError = nil;
    [[Pushwoosh sharedInstance] setUser:userId emails:emails completion:^(NSError * _Nullable error) {
        if (error) {
            gError = error;
        }
    }];
    if (!gError && successCallback) {
        successCallback(@[]);
    }
    
    if (gError && errorCallback) {
        errorCallback(@[ objectOrNull([gError localizedDescription]) ]);
    }
}

RCT_EXPORT_METHOD(setTags:(NSDictionary*)tags success:(RCTResponseSenderBlock)successCallback error:(RCTResponseSenderBlock)errorCallback) {
    [[PushNotificationManager pushManager] setTags:tags withCompletion:^(NSError* error) {
        if (!error && successCallback) {
            successCallback(@[]);
        }
        
        if (error && errorCallback) {
            errorCallback(@[ objectOrNull([error localizedDescription]) ]);
        }
    }];
}

RCT_EXPORT_METHOD(getTags:(RCTResponseSenderBlock)successCallback error:(RCTResponseSenderBlock)errorCallback) {
    [[PushNotificationManager pushManager] loadTags:^(NSDictionary* tags) {
        if (successCallback) {
            successCallback(@[ tags ]);
        }
    } error:^(NSError *error) {
        if (errorCallback) {
            errorCallback(@[ objectOrNull([error localizedDescription]) ]);
        }
    }];
}

RCT_EXPORT_METHOD(setShowPushnotificationAlert:(BOOL)showPushnotificationAlert) {
    [[PushNotificationManager pushManager] setShowPushnotificationAlert:showPushnotificationAlert];
}

RCT_EXPORT_METHOD(getShowPushnotificationAlert:(RCTResponseSenderBlock)callback) {
    if(callback) {
        callback(@[ @([PushNotificationManager pushManager].showPushnotificationAlert) ]);
    }
}

RCT_EXPORT_METHOD(setUserId:(NSString*)userId success:(RCTResponseSenderBlock)successCallback error:(RCTResponseSenderBlock)errorCallback) {
    
    [[Pushwoosh sharedInstance] setUserId:userId completion:^(NSError *error) {
        if (!error && successCallback) {
            successCallback(@[]);
        }
        
        if (error && errorCallback) {
            errorCallback(@[ objectOrNull([error localizedDescription]) ]);
        }
    }];
}

RCT_EXPORT_METHOD(postEvent:(NSString*)event withAttributes:(NSDictionary*)attributes) {
    [[PWInAppManager sharedManager] postEvent:event withAttributes:attributes];
}

RCT_EXPORT_METHOD(setApplicationIconBadgeNumber:(nonnull NSNumber*)badgeNumber) {
    dispatch_async(dispatch_get_main_queue(), ^{
        [UIApplication sharedApplication].applicationIconBadgeNumber = [badgeNumber integerValue];
    });
}

RCT_EXPORT_METHOD(setLanguage:(NSString *)language) {
    [PushNotificationManager pushManager].language = language;
}

RCT_EXPORT_METHOD(getApplicationIconBadgeNumber:(RCTResponseSenderBlock)callback) {
    if(callback) {
        dispatch_async(dispatch_get_main_queue(), ^{
           callback(@[ @([UIApplication sharedApplication].applicationIconBadgeNumber) ]);
        });
    }
}

RCT_EXPORT_METHOD(addToApplicationIconBadgeNumber:(nonnull NSNumber*)badgeNumber) {
    dispatch_async(dispatch_get_main_queue(), ^{
        [UIApplication sharedApplication].applicationIconBadgeNumber += [badgeNumber integerValue];
    });
}
    
RCT_EXPORT_METHOD(presentInboxUI:(NSDictionary *)styleDictionary) {
    NSString *resourceBundlePath = [[NSBundle mainBundle] pathForResource:@"PushwooshInboxBundle" ofType:@"bundle"];
    if (![NSBundle bundleWithPath:resourceBundlePath]) {
        NSLog(@"[Pushwoosh][presentInboxUI] Error: PushwooshInboxBundle.bundle not found. Please launch \"node node_modules/pushwoosh-react-native-plugin/scripts/add_inbox_ios_resources.js\" from project root directory or manually add node_modules/pushwoosh-react-native-plugin/src/ios/PushwooshInboxBundle.bundle to your project.");
    } else {
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            PWIInboxViewController *inboxViewController = [PWIInboxUI createInboxControllerWithStyle:[self inboxStyleForDictionary:styleDictionary]];
            inboxViewController.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(@"Close", @"Close") style:UIBarButtonItemStylePlain target:self action:@selector(closeInbox)];
            [[PushwooshPlugin findRootViewController] presentViewController:[[UINavigationController alloc] initWithRootViewController:inboxViewController] animated:YES completion:nil];
            
            __weak typeof (self) wself = self;
            inboxViewController.onMessageClickBlock = ^(NSObject<PWInboxMessageProtocol> *message) {
                if (message.type == PWInboxMessageTypeDeeplink) {
                    [wself closeInbox];
                }
            };
        }];
    }
}

RCT_EXPORT_METHOD(messagesWithNoActionPerformedCount:(RCTResponseSenderBlock)callback) {
    [PWInbox messagesWithNoActionPerformedCountWithCompletion:^(NSInteger count, NSError *error) {
        if (callback) {
            callback(@[ @(count) ]);
        }
    }];
}

RCT_EXPORT_METHOD(unreadMessagesCount:(RCTResponseSenderBlock)callback) {
    [PWInbox unreadMessagesCountWithCompletion:^(NSInteger count, NSError *error) {
        if (callback) {
            callback(@[ @(count) ]);
        }
    }];
}

RCT_EXPORT_METHOD(messagesCount:(RCTResponseSenderBlock)callback) {
    [PWInbox messagesCountWithCompletion:^(NSInteger count, NSError *error) {
        if (callback) {
            callback(@[ @(count) ]);
        }
    }];
}

RCT_EXPORT_METHOD(loadMessages:(RCTResponseSenderBlock)success fail:(RCTResponseSenderBlock)fail) {
    [PWInbox loadMessagesWithCompletion:^(NSArray<NSObject<PWInboxMessageProtocol> *> *messages, NSError *error) {
        if (success) {
            NSMutableArray* array = [[NSMutableArray alloc] init];
                for (NSObject<PWInboxMessageProtocol>* message in messages) {
                    NSDictionary* dict = [self inboxMessageToDictionary:message];
                    [array addObject:dict];
                }
            success( @[ array ]);
        } else if (error != nil && fail != nil) {
            fail(@[ error ]);
        }
    }];
}

RCT_EXPORT_METHOD(readMessage:(NSString*)code) {
    NSArray* arr = [NSArray arrayWithObject:code];
    [PWInbox readMessagesWithCodes:arr];
}

RCT_EXPORT_METHOD(readMessages:(NSArray<NSString*>*)codes) {
    [PWInbox readMessagesWithCodes:codes];
}

RCT_EXPORT_METHOD(deleteMessage:(NSString*)code) {
    NSArray* arr = [NSArray arrayWithObject:code];
    [PWInbox deleteMessagesWithCodes:arr];
}

RCT_EXPORT_METHOD(deleteMessages:(NSArray<NSString*>*)codes) {
    [PWInbox deleteMessagesWithCodes:codes];
}

RCT_EXPORT_METHOD(performAction:(NSString*)code) {
    [PWInbox performActionForMessageWithCode:code];
}

#pragma mark -
#pragma mark - Swizzling

+ (void)swizzleNotificationSettingsHandler {
    if ([UIApplication sharedApplication].delegate == nil) {
        return;
    }
    
    if ([[[UIDevice currentDevice] systemVersion] floatValue] < 8.0) {
        return;
    }
    
    static Class appDelegateClass = nil;
    
    //do not swizzle the same class twice
    id delegate = [UIApplication sharedApplication].delegate;
    if(appDelegateClass == [delegate class]) {
        return;
    }
    
    appDelegateClass = [delegate class];
    
    pushwoosh_swizzle([delegate class], @selector(application:didRegisterForRemoteNotificationsWithDeviceToken:), @selector(application:pwplugin_didRegisterWithDeviceToken:), (IMP)pwplugin_didRegisterWithDeviceToken, "v@:::");
    pushwoosh_swizzle([delegate class], @selector(application:didFailToRegisterForRemoteNotificationsWithError:), @selector(application:pwplugin_didFailToRegisterForRemoteNotificationsWithError:), (IMP)pwplugin_didFailToRegisterForRemoteNotificationsWithError, "v@:::");
    pushwoosh_swizzle([delegate class], @selector(application:didReceiveRemoteNotification:fetchCompletionHandler:), @selector(application:pwplugin_didReceiveRemoteNotification:fetchCompletionHandler:), (IMP)pwplugin_didReceiveRemoteNotification, "v@::::");
}

void pwplugin_didReceiveRemoteNotification(id self, SEL _cmd, UIApplication * application, NSDictionary * userInfo, void (^completionHandler)(UIBackgroundFetchResult)) {
    if ([self respondsToSelector:@selector(application:pwplugin_didReceiveRemoteNotification:fetchCompletionHandler:)]) {
        [self application:application pwplugin_didReceiveRemoteNotification:userInfo fetchCompletionHandler:completionHandler];
    }
    
    [[Pushwoosh sharedInstance] handlePushReceived:userInfo];
}

void pwplugin_didRegisterWithDeviceToken(id self, SEL _cmd, id application, NSData *deviceToken) {
    if ([self respondsToSelector:@selector(application: pwplugin_didRegisterWithDeviceToken:)]) {
        [self application:application pwplugin_didRegisterWithDeviceToken:deviceToken];
    }
    
    [[Pushwoosh sharedInstance] handlePushRegistration:deviceToken];
}

void pwplugin_didFailToRegisterForRemoteNotificationsWithError(id self, SEL _cmd, UIApplication *application, NSError *error) {
    if ([self respondsToSelector:@selector(application:pwplugin_didFailToRegisterForRemoteNotificationsWithError:)]) {
        [self application:application pwplugin_didFailToRegisterForRemoteNotificationsWithError:error];
    }
    
    [[Pushwoosh sharedInstance] handlePushRegistrationFailure:error];
}

#pragma mark - UNUserNotificationCenter Delegate Methods
#pragma mark -

- (void)userNotificationCenter:(UNUserNotificationCenter *)center
       willPresentNotification:(UNNotification *)notification
         withCompletionHandler:
(void (^)(UNNotificationPresentationOptions options))completionHandler
API_AVAILABLE(ios(10.0)) {
    
    UNMutableNotificationContent *content = notification.request.content.mutableCopy;
    BOOL isPushwooshMessage = [PWMessage isPushwooshMessage:notification.request.content.userInfo];
    NSString *currentHash = content.userInfo[@"p"];
    BOOL isNeedToImplementInPlugin = [[PushwooshPlugin getPluginImplementationInfoPlistKey] boolValue];

    if (isPushwooshMessage && isNeedToImplementInPlugin && ![lastPushwooshHash isEqualToString:currentHash]) {
        lastPushwooshHash = currentHash;

        if ([PushNotificationManager pushManager].showPushnotificationAlert) {
            completionHandler(UNNotificationPresentationOptionSound | UNNotificationPresentationOptionBadge | UNNotificationPresentationOptionAlert);
        } else {
            [[PushNotificationManager pushManager] handlePushReceived:content.userInfo];
        }
        
        [self sendJSEvent:kPushReceivedJSEvent withArgs:content.userInfo];
    }
        
    if (_originalNotificationCenterDelegate != nil &&
        _originalNotificationCenterDelegateResponds.willPresentNotification) {
        
        dispatch_block_t presentationBlock = ^{
            [self->_originalNotificationCenterDelegate userNotificationCenter:center
                                                willPresentNotification:notification
                                                  withCompletionHandler:completionHandler];
        };
        
        if (isPushwooshMessage) {
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.59 * NSEC_PER_SEC)), dispatch_get_main_queue(), presentationBlock);
        } else {
            presentationBlock();
        }
    }
    
    if (_originalNotificationCenterDelegate != nil &&
        _originalNotificationCenterDelegateResponds.willPresentNotification) {
        [_originalNotificationCenterDelegate userNotificationCenter:center
                                            willPresentNotification:notification
                                              withCompletionHandler:completionHandler];
    }
}

- (BOOL)isContentAvailablePush:(NSDictionary *)userInfo {
    NSDictionary *apsDict = userInfo[@"aps"];
    return apsDict[@"content-available"] != nil;
}

- (NSDictionary *)pushPayloadFromContent:(UNNotificationContent *)content {
    return [[content.userInfo objectForKey:@"pw_push"] isKindOfClass:[NSDictionary class]] ? [content.userInfo objectForKey:@"pw_push"] : content.userInfo;
}

- (BOOL)isRemoteNotification:(UNNotification *)notification {
    return [notification.request.trigger isKindOfClass:[UNPushNotificationTrigger class]];
}

- (void)userNotificationCenter:(UNUserNotificationCenter *)center
didReceiveNotificationResponse:(UNNotificationResponse *)response
         withCompletionHandler:(void (^)(void))completionHandler
API_AVAILABLE(ios(10.0)) {
    BOOL isNeedToImplementInPlugin = [[PushwooshPlugin getPluginImplementationInfoPlistKey] boolValue];

    dispatch_block_t handlePushAcceptanceBlock = ^{
        if (![response.actionIdentifier isEqualToString:UNNotificationDismissActionIdentifier]) {
            if (![response.actionIdentifier isEqualToString:UNNotificationDefaultActionIdentifier] && [[PushNotificationManager pushManager].delegate respondsToSelector:@selector(onActionIdentifierReceived:withNotification:)]) {
                [[PushNotificationManager pushManager].delegate onActionIdentifierReceived:response.actionIdentifier withNotification:[self pushPayloadFromContent:response.notification.request.content]];
            }
        }
    };
    
    if ([self isRemoteNotification:response.notification] && [PWMessage isPushwooshMessage:response.notification.request.content.userInfo]) {
        NSDictionary *userInfo = response.notification.request.content.userInfo;

        if (isNeedToImplementInPlugin) {
            [[PushNotificationManager pushManager] handlePushAccepted:userInfo onStart:[self isOnStart:userInfo]];
        }

        handlePushAcceptanceBlock();
    } else if ([response.notification.request.content.userInfo objectForKey:@"pw_push"]) {
        handlePushAcceptanceBlock();
    }

    if (_originalNotificationCenterDelegate != nil &&
        _originalNotificationCenterDelegateResponds.didReceiveNotificationResponse) {
        [_originalNotificationCenterDelegate userNotificationCenter:center
                                     didReceiveNotificationResponse:response
                                              withCompletionHandler:completionHandler];
    } else {
        completionHandler();
    }
}

- (BOOL)isOnStart:(NSDictionary *)userInfo {
    NSDictionary *pushStartDictionary = [self startPushInfoFromInfoDictionary:userInfo];
    return pushStartDictionary != nil;
}

- (NSDictionary *)startPushInfoFromInfoDictionary:(NSDictionary *)userInfo {
    //try as launchOptions dictionary
    NSDictionary *pushDict = userInfo[UIApplicationLaunchOptionsRemoteNotificationKey];
    if (pushDict == nil) {
        id notification = userInfo[UIApplicationLaunchOptionsLocalNotificationKey];
        
        if (notification && [notification isKindOfClass:[UILocalNotification class]]) {
            pushDict = [notification userInfo];
            
            if (pushDict[@"pw_push"] == nil) {
                pushDict = nil;
            }
        }
    }

    return pushDict;
}

- (void)userNotificationCenter:(UNUserNotificationCenter *)center
   openSettingsForNotification:(nullable UNNotification *)notification
API_AVAILABLE(ios(10.0)) {
    if ([[PushNotificationManager pushManager].delegate respondsToSelector:@selector(pushManager:openSettingsForNotification:)]) {
        #pragma clang diagnostic push
        #pragma clang diagnostic ignored "-Wpartial-availability"
        [[PushNotificationManager pushManager].delegate pushManager:[PushNotificationManager pushManager] openSettingsForNotification:notification];
        #pragma clang diagnostic pop
    }

    if (_originalNotificationCenterDelegate != nil &&
        _originalNotificationCenterDelegateResponds.openSettingsForNotification) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunguarded-availability-new"
        [_originalNotificationCenterDelegate userNotificationCenter:center
                                        openSettingsForNotification:notification];
#pragma clang diagnostic pop
    }
}

- (NSDictionary*)inboxMessageToDictionary:(NSObject<PWInboxMessageProtocol>*) message {
    NSMutableDictionary* dictionary = [[NSMutableDictionary alloc] init];
    [dictionary setValue:@(message.type) forKey:@"type"];
    [dictionary setValue:[self stringOrEmpty: message.imageUrl] forKey:@"imageUrl"];
    [dictionary setValue:[self stringOrEmpty: message.code] forKey:@"code"];
    [dictionary setValue:[self stringOrEmpty: message.title] forKey:@"title"];
    [dictionary setValue:[self stringOrEmpty: message.message] forKey:@"message"];
    [dictionary setValue:[self stringOrEmpty: [self dateToString:message.sendDate]] forKey:@"sendDate"];
    [dictionary setValue:@(message.isRead) forKey:@"isRead"];
    [dictionary setValue:@(message.isActionPerformed) forKey:@"isActionPerformed"];
    
    NSDictionary* actionParams = [NSDictionary dictionaryWithDictionary:message.actionParams];
    NSData* customData = [actionParams valueForKey:@"u"];
    [dictionary setValue:customData forKey:@"customData"];
    
    NSDictionary* result = [NSDictionary dictionaryWithDictionary:dictionary];
    return result;
}

- (NSString *)stringOrEmpty:(NSString *)string {
    return string != nil ? string : @"";
}

- (NSString*)dateToString:(NSDate*)date {
    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
    [formatter setDateFormat:@"yyyy-MM-dd'T'H:mm:ssZ"];
    return [formatter stringFromDate:date];
}

- (PWIInboxStyle *)inboxStyleForDictionary:(NSDictionary *)styleDictionary {
    PWIInboxStyle *style = [PWIInboxStyle defaultStyle];
    
    NSDictionary *defaultImageDict = styleDictionary[@"defaultImageIcon"];
    
    if (defaultImageDict) {
        style.defaultImageIcon = [RCTConvert UIImage:defaultImageDict];
    }
    
    NSString *dateFormat = styleDictionary[@"dateFormat"];
    
    if (dateFormat) {
        style.dateFormatterBlock = ^NSString *(NSDate *date, NSObject *owner) {
            NSDateFormatter *formatter = [NSDateFormatter new];
            formatter.dateFormat = dateFormat;
            return [formatter stringFromDate:date];
        };
    }
    
    NSDictionary *listErrorImageDict = styleDictionary[@"listErrorImage"];
    
    if (listErrorImageDict) {
        style.listErrorImage = [RCTConvert UIImage:listErrorImageDict];
    }
    
    NSDictionary *listEmptyImageDict = styleDictionary[@"listEmptyImage"];
    
    if (listEmptyImageDict) {
        style.listEmptyImage = [RCTConvert UIImage:listEmptyImageDict];
    }
    
    NSDictionary *unreadImageDict = styleDictionary[@"unreadImage"];
    
    if (unreadImageDict) {
        style.unreadImage = [RCTConvert UIImage:unreadImageDict];
    }
    
    NSString *listErrorMessage = styleDictionary[@"listErrorMessage"];
    
    if (listErrorMessage) {
        style.listErrorMessage = listErrorMessage;
    }
    
    NSString *listEmptyMessage = styleDictionary[@"listEmptyMessage"];
    
    if (listEmptyMessage) {
        style.listEmptyMessage = listEmptyMessage;
    }
    
    NSNumber *accentColorValue = styleDictionary[@"accentColor"];
    
    if (accentColorValue) {
        style.accentColor = [RCTConvert UIColor:accentColorValue];
    }
    
    NSNumber *defaultTextColorValue = styleDictionary[@"defaultTextColor"];
    
    if (defaultTextColorValue) {
        style.defaultTextColor = [RCTConvert UIColor:defaultTextColorValue];
    }
    
    NSNumber *backgroundColorValue = styleDictionary[@"backgroundColor"];
    
    if (backgroundColorValue) {
        style.backgroundColor = [RCTConvert UIColor:backgroundColorValue];
    }
    
    if (accentColorValue) {
        style.accentColor = [RCTConvert UIColor:accentColorValue];
    }
    
    NSNumber *highlightColorValue = styleDictionary[@"highlightColor"];
    
    if (highlightColorValue) {
        style.selectionColor = [RCTConvert UIColor:highlightColorValue];
    }
    
    NSNumber *titleColorValue = styleDictionary[@"titleColor"];
    
    if (titleColorValue) {
        style.titleColor = [RCTConvert UIColor:titleColorValue];
    }
    
    NSNumber *descriptionColorValue = styleDictionary[@"descriptionColor"];
    
    if (descriptionColorValue) {
        style.descriptionColor = [RCTConvert UIColor:descriptionColorValue];
    }
    
    NSNumber *dateColorValue = styleDictionary[@"dateColor"];
    
    if (dateColorValue) {
        style.dateColor = [RCTConvert UIColor:dateColorValue];
    }
    
    NSNumber *dividerColorValue = styleDictionary[@"dividerColor"];
    
    if (dividerColorValue) {
        style.separatorColor = [RCTConvert UIColor:dividerColorValue];
    }
    
    NSNumber *barBackgroundColor = styleDictionary[@"barBackgroundColor"];
    
    if (barBackgroundColor) {
        style.barBackgroundColor = [RCTConvert UIColor:barBackgroundColor];
    }
    
    NSNumber *barAccentColor = styleDictionary[@"barAccentColor"];
    
    if (barAccentColor) {
        style.barAccentColor = [RCTConvert UIColor:barAccentColor];
    }
    
    NSNumber *barTextColor = styleDictionary[@"barTextColor"];
    
    if (barTextColor) {
        style.barTextColor = [RCTConvert UIColor:barTextColor];
    }
    
    return style;
}

- (void)closeInbox {
    UIViewController *topViewController = [PushwooshPlugin findRootViewController];
    if ([topViewController isKindOfClass:[UINavigationController class]] && [((UINavigationController*)topViewController).viewControllers.firstObject isKindOfClass:[PWIInboxViewController class]]) {
        [topViewController dismissViewControllerAnimated:YES completion:nil];
    }
}

+ (UIViewController*)findRootViewController {
    UIApplication *sharedApplication = [UIApplication valueForKey:@"sharedApplication"];
    UIViewController *controller = sharedApplication.keyWindow.rootViewController;
    
    while (controller.presentedViewController) {
        controller = controller.presentedViewController;
    }
    return controller;
}

RCT_EXPORT_METHOD(isCommunicationEnabled:(RCTResponseSenderBlock)callback) {
    BOOL isCommunicationEnabled = [self getCommunicationEnabledState];

    callback(@[@(isCommunicationEnabled)]);
}

RCT_EXPORT_METHOD(setCommunicationEnabled:(BOOL)enabled success:(RCTResponseSenderBlock)successCallback error:(RCTResponseSenderBlock)errorCallback) {
    [[NSUserDefaults standardUserDefaults] setBool:enabled forKey:PW_COMMUNICATION_ENABLED_KEY];
        
    if (enabled) {
        [[Pushwoosh sharedInstance] startServerCommunication];
    } else {
        [[Pushwoosh sharedInstance] stopServerCommunication];
    }

    
    successCallback(@[]);
}

- (BOOL)getCommunicationEnabledState {
    NSBundle *bundle = [NSBundle mainBundle];
    NSObject *plistValue = [bundle objectForInfoDictionaryKey:PW_COMMUNICATION_ENABLED_PLIST_KEY];
    
    if (plistValue != nil) {
        if ([plistValue isKindOfClass:[NSNumber class]]) {
            return [(NSNumber *)plistValue boolValue];
        } else if ([plistValue isKindOfClass:[NSString class]]) {
            NSString *stringValue = [(NSString *)plistValue lowercaseString];
            return [stringValue isEqualToString:@"true"] || [stringValue isEqualToString:@"yes"] || [stringValue isEqualToString:@"1"];
        }
    }

    if ([[NSUserDefaults standardUserDefaults] objectForKey:PW_COMMUNICATION_ENABLED_KEY] != nil) {
        return [[NSUserDefaults standardUserDefaults] boolForKey:PW_COMMUNICATION_ENABLED_KEY];
    }
    
    return YES;
}


RCT_EXPORT_METHOD(createLocalNotification:(NSDictionary *)params){
    NSString *body = params[@"msg"];
    NSUInteger delay = [params[@"seconds"] unsignedIntegerValue];
    NSDictionary *userData = params[@"userData"];

    [self sendLocalNotificationWithBody:body delay:delay userData:userData];
}

- (void)sendLocalNotificationWithBody:(NSString *)body delay:(NSUInteger)delay userData:(NSDictionary *)userData {
    if (@available(iOS 10, *)) {
        UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
        UNMutableNotificationContent *content = [UNMutableNotificationContent new];
        content.body = body;
        content.sound = [UNNotificationSound defaultSound];
        content.userInfo = userData;
        UNTimeIntervalNotificationTrigger *trigger = delay > 0 ? [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:delay repeats:NO] : nil;
        NSString *identifier = @"LocalNotification";
        UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:identifier
                                                                              content:content
                                                                              trigger:trigger];
        
        [center addNotificationRequest:request withCompletionHandler:^(NSError *_Nullable error) {
            if (error != nil) {
                NSLog(@"Something went wrong: %@", error);
            }
        }];
    } else {
        UILocalNotification *localNotification = [[UILocalNotification alloc] init];
        localNotification.fireDate = [NSDate dateWithTimeIntervalSinceNow:delay];
        localNotification.alertBody = body;
        localNotification.timeZone = [NSTimeZone defaultTimeZone];
        localNotification.userInfo = userData;
        [[UIApplication sharedApplication] scheduleLocalNotification:localNotification];
    }
}

RCT_EXPORT_METHOD(clearLocalNotification){
    if (@available(iOS 10, *)) {
        UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
        [center removeAllDeliveredNotifications];
        [center removeAllPendingNotificationRequests];
    } else{
        [[UIApplication sharedApplication] cancelAllLocalNotifications];
    }
}

RCT_EXPORT_METHOD(clearNotificationCenter){
    [PushNotificationManager clearNotificationCenter];
}


RCT_EXPORT_METHOD(setReverseProxy:(NSString *)url headers:(NSDictionary *)headers) {
    [PushwooshConfig setReverseProxy:url headers:headers];
}

RCT_EXPORT_METHOD(enableHuaweiPushNotifications) {
    // available in Android only
}

RCT_EXPORT_METHOD(registerSMSNumber:(NSString *)phoneNumber) {
    [[Pushwoosh sharedInstance] registerSmsNumber:phoneNumber];
}

RCT_EXPORT_METHOD(registerWhatsappNumber:(NSString *)phoneNumber) {
    [[Pushwoosh sharedInstance] registerWhatsappNumber:phoneNumber];
}

RCT_EXPORT_METHOD(setRichMediaType:(nonnull NSNumber *)type) {
    PWRichMediaPresentationStyle style = [type integerValue] == 0
        ? PWRichMediaPresentationStyleModal
        : PWRichMediaPresentationStyleLegacy;
    [Pushwoosh.media setRichMediaPresentationStyle:style];
}

RCT_EXPORT_METHOD(getRichMediaType:(RCTResponseSenderBlock)callback) {
    PWRichMediaPresentationStyle style = [Pushwoosh.media richMediaPresentationStyle];
    if (callback) {
        callback(@[@(style)]);
    }
}

#pragma mark - PushNotificationDelegate

- (void)onDidRegisterForRemoteNotificationsWithDeviceToken:(NSString *)token {
    [[PWEventDispatcher sharedDispatcher] dispatchEvent:kRegistrationSuccesEvent withArgs:@[ objectOrNull(token) ]];
}

- (void)onDidFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
    [[PWEventDispatcher sharedDispatcher] dispatchEvent:kRegistrationErrorEvent withArgs:@[ objectOrNull([error localizedDescription]) ]];
}

- (void)onPushReceived:(PushNotificationManager *)pushManager withNotification:(NSDictionary *)pushNotification onStart:(BOOL)onStart {
    [[PWEventDispatcher sharedDispatcher] dispatchEvent:kPushReceivedEvent withArgs:@[ objectOrNull(pushNotification) ]];
    
    [self sendJSEvent:kPushReceivedJSEvent withArgs:pushNotification];
}

- (void)onPushAccepted:(PushNotificationManager *)manager withNotification:(NSDictionary *)pushNotification onStart:(BOOL)onStart {
    [[PWEventDispatcher sharedDispatcher] dispatchEvent:kPushOpenEvent withArgs:@[ objectOrNull(pushNotification) ]];
    
    [self sendJSEvent:kPushOpenJSEvent withArgs:pushNotification];
}

#pragma mark - RCTEventEmitter

- (void)sendJSEvent:(NSString*)event withArgs:(NSDictionary*)args {
//    [self sendEventWithName:event body:args];
    [self.bridge.eventDispatcher sendDeviceEventWithName:event body:args];
}

- (NSArray<NSString *> *)supportedEvents {
    return @[ kPushOpenJSEvent, kPushReceivedJSEvent ];
}

@end

@implementation UIApplication (InternalPushRuntime)

- (BOOL)pushwooshUseRuntimeMagic {
    return YES;
}

// Just keep the launch notification until the module starts and callback functions initalizes
- (void)onPushReceived:(PushNotificationManager *)manager withNotification:(NSDictionary *)pushNotification onStart:(BOOL)onStart {
    if (onStart) {
        gStartPushData = pushNotification;
        // Save deep link URL for New Architecture (Linking.getInitialURL support)
        NSString *link = pushNotification[@"l"];
        if (link) {
            gPushDeepLinkURL = [NSURL URLWithString:link];
        }
    }
}
- (void)onPushAccepted:(PushNotificationManager *)manager withNotification:(NSDictionary *)pushNotification onStart:(BOOL)onStart {
    if (onStart) {
        gStartPushData = pushNotification;
        // Save deep link URL for New Architecture (Linking.getInitialURL support)
        NSString *link = pushNotification[@"l"];
        if (link) {
            gPushDeepLinkURL = [NSURL URLWithString:link];
        }
    }
}

- (NSObject<PushNotificationDelegate> *)getPushwooshDelegate {
    return (NSObject<PushNotificationDelegate> *)self;
}

@end

#pragma mark - RCTLinkingManager Swizzle for New Architecture

// Temporary delegate to capture push notification on cold start before React Native initializes
API_AVAILABLE(ios(10.0))
@interface PWEarlyNotificationDelegate : NSObject <UNUserNotificationCenterDelegate>
@property (nonatomic, weak) id<UNUserNotificationCenterDelegate> originalDelegate;
@end

@implementation PWEarlyNotificationDelegate

- (void)userNotificationCenter:(UNUserNotificationCenter *)center
didReceiveNotificationResponse:(UNNotificationResponse *)response
         withCompletionHandler:(void (^)(void))completionHandler API_AVAILABLE(ios(10.0)) {
    // Capture deep link from push notification on cold start
    if ([response.notification.request.trigger isKindOfClass:[UNPushNotificationTrigger class]]) {
        NSDictionary *userInfo = response.notification.request.content.userInfo;
        NSString *link = userInfo[@"l"];
        if (link) {
            gPushDeepLinkURL = [NSURL URLWithString:link];
        }
    }

    // Forward to original delegate
    if (self.originalDelegate && [self.originalDelegate respondsToSelector:@selector(userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:)]) {
        [self.originalDelegate userNotificationCenter:center didReceiveNotificationResponse:response withCompletionHandler:completionHandler];
    } else {
        completionHandler();
    }
}

- (void)userNotificationCenter:(UNUserNotificationCenter *)center
       willPresentNotification:(UNNotification *)notification
         withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler API_AVAILABLE(ios(10.0)) {
    // Forward to original delegate
    if (self.originalDelegate && [self.originalDelegate respondsToSelector:@selector(userNotificationCenter:willPresentNotification:withCompletionHandler:)]) {
        [self.originalDelegate userNotificationCenter:center willPresentNotification:notification withCompletionHandler:completionHandler];
    } else {
        completionHandler(UNNotificationPresentationOptionNone);
    }
}

@end

static PWEarlyNotificationDelegate *gEarlyDelegate = nil;

@implementation RCTLinkingManager (PushwooshDeepLink)

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        // Swizzle getInitialURL:reject: to support deep links from push on New Architecture
        pushwoosh_swizzle(
            [RCTLinkingManager class],
            @selector(getInitialURL:reject:),
            @selector(pwplugin_original_getInitialURL:reject:),
            (IMP)pwplugin_getInitialURL,
            "v@:@@"
        );

        // Set up early notification delegate to capture push on cold start
        if (@available(iOS 10.0, *)) {
            dispatch_async(dispatch_get_main_queue(), ^{
                UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
                gEarlyDelegate = [[PWEarlyNotificationDelegate alloc] init];
                gEarlyDelegate.originalDelegate = center.delegate;
                center.delegate = gEarlyDelegate;
            });
        }

        // Also check launchOptions when app finishes launching
        [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidFinishLaunchingNotification
                                                          object:nil
                                                           queue:nil
                                                      usingBlock:^(NSNotification *notification) {
            NSDictionary *launchOptions = notification.userInfo;
            NSDictionary *remoteNotification = launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey];
            if (remoteNotification) {
                NSString *link = remoteNotification[@"l"];
                if (link && !gPushDeepLinkURL) {
                    gPushDeepLinkURL = [NSURL URLWithString:link];
                }
            }
        }];
    });
}

@end
