#import "RNHelpshift.h"
#import "RNHSUtils.h"
#import "RNHSCoreLogger.h"

@implementation RNHelpshift

NSString *NAME = @"Helpshift";
NSString *TAG = @"HelpshiftModule";

bool hasListeners;
NSDictionary *localApiConfig;

RCT_EXPORT_MODULE(HelpshiftPluginSdkxReactNative)

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

+ (BOOL)requiresMainQueueSetup
{
    return YES;
}

// Don't compile this code when we build for the old architecture.
#ifdef RCT_NEW_ARCH_ENABLED
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
(const facebook::react::ObjCTurboModule::InitParams &)params
{
    return std::make_shared<facebook::react::NativeHelpshiftPluginSdkxReactNativeSpecJSI>(params);
}
#endif

RCT_EXPORT_METHOD(install:(NSString *)platformId
                  domain :(NSString *)domain
                  config :(NSDictionary *)config)
{

    [Helpshift installWithPlatformId:platformId
                              domain:domain
                              config:config];
    Helpshift.sharedInstance.delegate = self;

    //set default value as false.
    bool enableLogging = false;
    if (config != nil && [config valueForKey:@"enableLogging"] != [NSNull null]) {
        enableLogging = [[config valueForKey:@"enableLogging"] boolValue];
    }

    [RNHSCoreLogger enableConsoleLogging:enableLogging];
    [RNHSCoreLogger logDebugWithTag:TAG message:[NSString stringWithFormat:@"React Install config: %@ \n domain: %@ \n platformId: %@", config, domain, platformId]];
    [Helpshift.sharedInstance setProactiveAPIConfigCollectorDelegate:self];
}

//
RCT_EXPORT_METHOD(showConversation:(NSDictionary *)config)
{
    [RNHSCoreLogger logDebugWithTag:TAG message:[NSString stringWithFormat:@"React showConversation called with config: %@", config]];
    [Helpshift showConversationWithConfig:config];
}

RCT_EXPORT_METHOD(showFAQsWithConfig:(NSDictionary *)config)
{
    [RNHSCoreLogger logDebugWithTag:TAG message:[NSString stringWithFormat:@"React showFAQs called with config : %@", config]];
    [Helpshift showFAQsWithConfig:config];
}

RCT_EXPORT_METHOD(showSingleFaqWithConfig:(NSString *)faqPublishID
                                   config:(NSDictionary *)config)
{
    [RNHSCoreLogger logDebugWithTag:TAG message:[NSString stringWithFormat:@"React showSingleFAQ called with config: %@ publish id: %@", config, faqPublishID]];
    [Helpshift showSingleFAQ:faqPublishID withConfig:config];
}

RCT_EXPORT_METHOD(showFAQSectionWithConfig:(NSString *)faqSectionPublishID
                                    config:(NSDictionary *)config)
{
    [RNHSCoreLogger logDebugWithTag:TAG message:[NSString stringWithFormat:@"React showFAQSection called with config: %@ section id: %@", config, faqSectionPublishID]];
    [Helpshift showFAQSection:faqSectionPublishID withConfig:config];
}

RCT_EXPORT_METHOD(closeSession)
{
    [RNHSCoreLogger logDebugWithTag:TAG message:[NSString stringWithFormat:@"React closeSession called"]];
    [Helpshift closeSession];
}

RCT_EXPORT_METHOD(setLanguage:(NSString *)languageCode)
{
    [RNHSCoreLogger logDebugWithTag:TAG message:[NSString stringWithFormat:@"React setLanguage called with language code: %@", languageCode]];
    [Helpshift setLanguage:languageCode];
}

RCT_EXPORT_METHOD(login:(NSDictionary *)userDetails callback:(RCTResponseSenderBlock)callback)
{
    [RNHSCoreLogger logDebugWithTag:TAG message:[NSString stringWithFormat:@"React login called with user data"]];
    callback(@[@([Helpshift loginUser:userDetails])]);
}

RCT_EXPORT_METHOD(requestUnreadMessageCount:(BOOL)shouldFetchFromServer)
{
    [RNHSCoreLogger logDebugWithTag:TAG message:[NSString stringWithFormat:@"React requestUnreadMessageCount called with fetchFromServerFlag %@",shouldFetchFromServer ? @"YES": @"NO"] ];
    [Helpshift requestUnreadMessageCount:shouldFetchFromServer];
}

RCT_EXPORT_METHOD(clearAnonymousUserOnLogin:(BOOL)shouldClear)
{
    [RNHSCoreLogger logDebugWithTag:TAG message:[NSString stringWithFormat:@"React clearAnonymousUserOnLogin called with param: %@", shouldClear ? @"Yes" : @"No"]];
    [Helpshift clearAnonymousUserOnLogin:shouldClear];
}

RCT_EXPORT_METHOD(logout)
{
    [RNHSCoreLogger logDebugWithTag:TAG message:@"React logout called"];
    [Helpshift logout];
}

RCT_EXPORT_METHOD(leaveBreadCrumb:(NSString *)breadCrumb)
{
    [RNHSCoreLogger logDebugWithTag:TAG message:[NSString stringWithFormat:@"React leaveBreadCrumb called with value: %@", breadCrumb]];
    [Helpshift leaveBreadcrumb:breadCrumb];
}

RCT_EXPORT_METHOD(addUserTrail:(NSString *)userTrail)
{
    [RNHSCoreLogger logDebugWithTag:TAG message:[NSString stringWithFormat:@"React userTrail called with trail: %@", userTrail]];
    [Helpshift addUserTrail:userTrail];
}

RCT_EXPORT_METHOD(clearBreadcrumbs)
{
    [RNHSCoreLogger logDebugWithTag:TAG message:@"React clearBreadCrumbs called"];
    [Helpshift clearBreadcrumbs];
}

RCT_EXPORT_METHOD(handleProactive:(NSString *)proactiveLink config:(NSDictionary *)config)
{
    [RNHSCoreLogger logDebugWithTag:TAG message:[NSString stringWithFormat:@"React handleProactiveLink called with handleProactiveLink: %@ and with localConfig: %@ ", proactiveLink, config]];

    localApiConfig = config;
    [Helpshift handleProactiveLink:proactiveLink];
}

RCT_EXPORT_METHOD(setProactiveConfig:(NSDictionary *)config)
{
    [RNHSCoreLogger logDebugWithTag:TAG message:[NSString stringWithFormat:@"React setProactiveConfig called with config: %@ ", config]];
    localApiConfig = config;
}

-(nonnull NSDictionary *) getAPIConfig {
    if (localApiConfig == nil){
        return @{};;
    }
    return localApiConfig;;
}

// Will be called when this module's first listener is added.
-(void)startObserving {
    hasListeners = YES;
    // Set up any upstream listeners or background tasks as necessary
}

// Will be called when this module's last listener is removed, or on dealloc.
-(void)stopObserving {
    hasListeners = NO;
    // Remove upstream listeners, stop unnecessary background tasks
}

- (NSArray<NSString *> *)supportedEvents
{
    return @[
        @"onEventOccurred",
        @"onUserAuthenticationFailure"
    ];
}

- (void) authenticationFailedForUserWithReason:(HelpshiftAuthenticationFailureReason)reason {
    NSLog(@"Authentication failure, reason is :%lu",(unsigned long) reason);

    NSString *reasonString = [self stringForAuthFailureReason:reason];

    #ifdef RCT_NEW_ARCH_ENABLED
        [self emitUserAuthFailureEvent: @{@"eventName": reasonString, @"eventData": @{}}];
    #else
        [self sendEventWithName:@"onUserAuthenticationFailure" body:@{@"eventName": reasonString, @"eventData": @{}}];
    #endif
}

- (NSString *) stringForAuthFailureReason:(HelpshiftAuthenticationFailureReason)reason {
    switch (reason) {
        case HelpshiftAuthenticationFailureReasonAuthTokenNotProvided:
            return @"REASON_AUTH_TOKEN_NOT_PROVIDED";
        case HelpshiftAuthenticationFailureReasonInvalidAuthToken:
            return @"REASON_INVALID_AUTH_TOKEN";
        default:
            return @"UNKNOWN";
    }
}

- (void) handleHelpshiftEvent:(nonnull NSString *)eventName withData:(nullable NSDictionary *)data {
    NSLog(@"%@ name %@ data %@", NSStringFromSelector(_cmd), eventName, data);

    NSMutableDictionary *eventDict = [@{@"eventName": eventName} mutableCopy];
    if (data != nil) {
        eventDict[@"eventData"] = data;
    }

#ifdef RCT_NEW_ARCH_ENABLED
    [self emitHelpshiftEvents:eventDict];
#else

    if (!hasListeners) {
        return;
    }

    [self sendEventWithName:@"onEventOccurred" body:eventDict];

#endif

}

RCT_EXPORT_METHOD(registerPushToken:(NSString *)deviceToken)
{
    NSString *token = [deviceToken stringByReplacingOccurrencesOfString:@" " withString:@""];
    NSData *tokenToSend = [token hs_dataFromStringDescription];
    [RNHSCoreLogger logDebugWithTag:TAG message:[NSString stringWithFormat:@"React registerPushToken called with token string: %@ and token data: %@", token, tokenToSend]];

    [Helpshift registerDeviceToken:tokenToSend];
}

RCT_EXPORT_METHOD(handlePush:(NSDictionary *)userInfo isAppLaunch:(BOOL)isAppLaunch)
{
    if (!userInfo){
        [RNHSCoreLogger logDebugWithTag:TAG message:[NSString stringWithFormat:@"React handlePush notification dictionary is not valid: %@", userInfo]];
        return;
    }

    [RNHSCoreLogger logDebugWithTag:TAG message:[NSString stringWithFormat:@"React handlePush called with userInfo: %@", userInfo]];
    [Helpshift handleNotificationWithUserInfoDictionary:userInfo isAppLaunch:isAppLaunch];
}

RCT_EXPORT_METHOD(handleIOSForegroundNotification:(NSDictionary *)userInfo
                  callback:(RCTResponseSenderBlock)callback)
{
    [RNHSCoreLogger logDebugWithTag:TAG message:[NSString stringWithFormat:@"React handleForegroundNotification called with userInfo: %@", userInfo]];

    if (!userInfo) {
        callback(@[@(UNNotificationPresentationOptionNone)]);
        return;
    }

    [Helpshift handleForegroundNotification:[self modifyNotificationData:userInfo.copy]
                     withCompletionHandler:^(UNNotificationPresentationOptions options) {
        callback(@[@(options)]);
    }];
}

// Removes `identifier` for remote proactive push notifications.
// Proactive pushes are first received as remote notifications and then re-posted as local notifications (marked by `local-push`).
// Keeping the identifier on the remote payload causes conflicts in`react-native-notifications`, as both remote and local notifications end up sharing identifiers.
// We remove the identifier for the remote push.
// We do not consume `identifier` for local push notifications.
- (NSDictionary *) modifyNotificationData:(NSDictionary *)notificationData {
    if (![notificationData isKindOfClass:NSDictionary.class]) {
        return notificationData;
    }

    NSString *type = notificationData[@"type"];
    NSString *subType = notificationData[@"subType"];
    BOOL hasLocalPush = notificationData[@"local-push"] != nil;

    BOOL isProactivePush =
    [type isEqualToString:@"proactive"] &&
    [subType isEqualToString:@"push"];

    if (isProactivePush && !hasLocalPush) {
        NSMutableDictionary *modified = [notificationData mutableCopy];

        [RNHSCoreLogger logDebugWithTag:TAG message:@"[Helpshift] Removing identifier for remote proactive push."];
        [modified removeObjectForKey:@"identifier"];

        return modified;
    }

    return notificationData;
}

RCT_EXPORT_METHOD(handleIOSSilentNotification:(NSDictionary *)userInfo
                  callback:(RCTResponseSenderBlock)callback)
{
    [RNHSCoreLogger logDebugWithTag:TAG message:[NSString stringWithFormat:@"React handleSilentNotification called with userInfo: %@", userInfo]];

    if (!userInfo) {
        callback(@[@(UIBackgroundFetchResultFailed)]);
        return;
    }

    [Helpshift handleSilentBackgroundNotification:userInfo withCompletionHandler:^(UIBackgroundFetchResult result) {
        callback(@[@(result)]);
    }];
}

RCT_EXPORT_METHOD(handleIOSNotificationClick:(NSDictionary *)userInfo)
{
    [RNHSCoreLogger logDebugWithTag:TAG message:[NSString stringWithFormat:@"React handleNotificationClick called with userInfo: %@", userInfo]];
    [Helpshift handleBackgroundNotificationClick:userInfo withCompletionHandler:^(){}];
}

RCT_EXPORT_METHOD(pauseDisplayOfInAppNotification:(BOOL) shouldPauseInAppNotification)
{
    [RNHSCoreLogger logDebugWithTag:TAG message:[NSString stringWithFormat:@"React pause display of InAppNotification called with shouldPauseInAppNotification: %@",shouldPauseInAppNotification ? @"YES": @"NO"] ];
    [Helpshift pauseDisplayOfInAppNotification:shouldPauseInAppNotification];
}

RCT_EXPORT_METHOD(addDebugLog:(NSString *)level
                          tag:(NSString *)tag
                      message:(NSString *)message)
{
    NSString *formattedMessage = [NSString stringWithFormat:@"%@: %@: %@", level, tag, message];
    [RNHSCoreLogger logDebugWithTag:TAG message:[NSString stringWithFormat:@"React addDebugLog called"]];
    [Helpshift addLog:formattedMessage];
}

- (void)loginWithIdentity:(NSString *)jwt
                   config:(NSDictionary *)config
                  resolve:(RCTPromiseResolveBlock)resolve
                  reject:(RCTPromiseRejectBlock)reject
{
    @try {
        [Helpshift loginWithIdentity:jwt config:config success:^{
            resolve(@YES);
        } failure:^(NSString *reason, NSDictionary<NSString *, NSString *> *errors) {
            NSMutableDictionary *errorDict = [NSMutableDictionary dictionaryWithDictionary:errors ?: @{}];
            NSError *error = [NSError errorWithDomain:@"com.helpshift" code:0 userInfo:errorDict];
            reject(@"LOGIN_FAILED", reason, error);
        }];
    } @catch (NSException *exception) {
        NSDictionary *userInfo = @{ NSLocalizedDescriptionKey: exception.reason };
        NSError *error = [NSError errorWithDomain:@"com.helpshift" code:0 userInfo:userInfo];
        reject(@"LOGIN_FAILED", exception.reason, error);
    }
}

RCT_EXPORT_METHOD(loginWithIdentity:(NSString *)jwt
                  config:(NSDictionary *)config
                  resolver:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject) {
    [self loginWithIdentity:jwt config:config resolve:resolve reject:reject];
}

RCT_EXPORT_METHOD(addUserIdentities:(NSString *)identitiesJwt)
{
    [Helpshift addUserIdentities:identitiesJwt];
}

RCT_EXPORT_METHOD(updateMasterAttributes:(NSDictionary *)attributes) {
    [Helpshift updateMasterAttributes:attributes];
}

RCT_EXPORT_METHOD(setAndroidProactivePushNotificationDefaults:(NSDictionary *)config) {
    // no-op
}

RCT_EXPORT_METHOD(updateAppAttributes:(NSDictionary *)attributes) {
    [Helpshift updateAppAttributes:attributes];
}

@end
