#import "RNAppsFlyer.h"
#import "AppsFlyerAttribution.h"

@implementation RNAppsFlyer
@synthesize bridge = _bridge;

RCT_EXPORT_MODULE()

RCT_EXPORT_METHOD(initSdkWithCallBack: (NSDictionary*)initSdkOptions
                  successCallback :(RCTResponseSenderBlock)successCallback
                  errorCallback:(RCTResponseErrorBlock)errorCallback) {


    NSError* error = nil;
    error = [self callSdkInternal:initSdkOptions];
    if (error == nil){
        successCallback(@[AF_SUCCESS]);
    }else{
        errorCallback(error);
    }
}

RCT_EXPORT_METHOD(initSdkWithPromise: (NSDictionary*)initSdkOptions
                  initSdkWithPromiseWithResolver:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject) {

    NSError* error = nil;
    error = [self callSdkInternal:initSdkOptions];
    if (error == nil){
        resolve(@[AF_SUCCESS]);
    }else{
        reject([NSString stringWithFormat: @"%ld", (long)error.code], error.domain, error);    }
}

-(NSError *) callSdkInternal:(NSDictionary*)initSdkOptions {

    NSString* devKey = nil;
    NSString* appId = nil;
    BOOL isDebug = NO;
    BOOL isConversionData = YES;
    BOOL isDeepLinking = NO;
    BOOL isManualStart = NO;
    NSNumber* interval = 0;

    if (![initSdkOptions isKindOfClass:[NSNull class]]) {

        id isDebugValue = nil;
        id isConversionDataValue = nil;
        id isDeepLinkingValue = nil;
        devKey = (NSString*)[initSdkOptions objectForKey: afDevKey];
        appId = (NSString*)[initSdkOptions objectForKey: afAppId];
        interval = (NSNumber*)[initSdkOptions objectForKey: timeToWaitForATTUserAuthorization];
        isManualStart = [[initSdkOptions objectForKey:@"manualStart"] boolValue];
        [self setIsManualStart:isManualStart];

        isDebugValue = [initSdkOptions objectForKey: afIsDebug];
        if ([isDebugValue isKindOfClass:[NSNumber class]]) {
            // isDebug is a boolean that will come through as an NSNumber
            isDebug = [(NSNumber*)isDebugValue boolValue];
        }
        isConversionDataValue = [initSdkOptions objectForKey: afConversionData];

        if ([isConversionDataValue isKindOfClass:[NSNumber class]]) {
            isConversionData = [(NSNumber*)isConversionDataValue boolValue];
        }

        isDeepLinkingValue = [initSdkOptions objectForKey: afDeepLink];
        if ([isDeepLinkingValue isKindOfClass:[NSNumber class]]) {
            isDeepLinking = [(NSNumber*)isDeepLinkingValue boolValue];
        }
}
    NSError* error = nil;

    if (!devKey || [devKey isEqualToString:@""]) {
        error = [NSError errorWithDomain:NO_DEVKEY_FOUND code:0 userInfo:nil];

    }
    else if (!appId || [appId isEqualToString:@""]) {
        error = [NSError errorWithDomain:NO_APPID_FOUND code:1 userInfo:nil];
    }

    //DELIVERY-58378
    [AppsFlyerLib shared].appleAppID = appId;
    [AppsFlyerLib shared].appsFlyerDevKey = devKey;
    [AppsFlyerLib shared].isDebug = isDebug;
    
    if(error != nil){
        return error;
    }
    else{
        if(isConversionData == YES){
            [AppsFlyerLib shared].delegate = self;
        }
        if(isDeepLinking == YES){
            [AppsFlyerLib shared].deepLinkDelegate = self;
        }
#ifndef AFSDK_NO_IDFA
        if (interval != 0 && interval != nil){
            double timeoutInterval = [interval doubleValue];
            [[AppsFlyerLib shared] waitForATTUserAuthorizationWithTimeoutInterval:timeoutInterval];
        }
#endif
        BOOL isExpoApp = [self isExpoApp];
        [[AppsFlyerLib shared] setPluginInfoWith:isExpoApp ? AFSDKPluginExpo : AFSDKPluginReactNative
                                   pluginVersion:kAppsFlyerPluginVersion
                                additionalParams:nil];

      

        //post notification for the deep link object that the bridge is initialized and he can handle deep link
        [[AppsFlyerAttribution shared] setRNAFBridgeReady:YES];
        [[NSNotificationCenter defaultCenter] postNotificationName:RNAFBridgeInitializedNotification object:self];
        // Register for background-foreground transitions natively instead of doing this in JavaScript
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(sendLaunch:)
                                                     name:UIApplicationDidBecomeActiveNotification
                                                   object:nil];
        if (!isManualStart) {
            [[AppsFlyerLib shared] start];
        }
        return nil;
    }
}

RCT_EXPORT_METHOD(startSdk) {
    [self setIsManualStart:NO];
    [[AppsFlyerLib shared] start];
}

RCT_EXPORT_METHOD(logEvent: (NSString *)eventName eventValues:(NSDictionary *)eventValues
                  successCallback :(RCTResponseSenderBlock)successCallback
                  errorCallback:(RCTResponseSenderBlock)errorCallback) {
    if (!eventName || [eventName isEqualToString:@""]) {
        NSError *error = [NSError errorWithDomain:NO_EVENT_NAME_FOUND code:2 userInfo:nil];
        errorCallback(@[error.localizedDescription]);
        return ;
    }

    [[AppsFlyerLib shared] logEventWithEventName:eventName eventValues:eventValues completionHandler:^(NSDictionary<NSString *,id> * _Nullable dictionary, NSError * _Nullable error) {
        if(error){
            errorCallback(@[error.localizedDescription]);
            return;
        }
        successCallback(@[AF_SUCCESS]);
    }];
}

RCT_EXPORT_METHOD(logEventWithPromise: (NSString *)eventName eventValues:(NSDictionary *)eventValues
                  logEventWithPromiseWithResolver:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject) {
    if (!eventName || [eventName isEqualToString:@""]) {
        NSError *error = [NSError errorWithDomain:NO_EVENT_NAME_FOUND code:2 userInfo:nil];
        reject([NSString stringWithFormat: @"%ld", (long)error.code], error.domain, error);
        return ;
    }

    [[AppsFlyerLib shared] logEventWithEventName:eventName eventValues:eventValues completionHandler:^(NSDictionary<NSString *,id> * _Nullable dictionary, NSError * _Nullable error) {
        if(error){
            reject([NSString stringWithFormat: @"%ld", (long)error.code], error.domain, error);
            return;
        }
        resolve(@[AF_SUCCESS]);
    }];
}


RCT_EXPORT_METHOD(logAdRevenue:(NSDictionary *)adRevenueDictionary)
{
    // Verify if the adRevenueDictionary is not empty
    if (adRevenueDictionary.count == 0) {
        NSLog(@"adRevenueData is missing, the data is mandatory to use this API.");
        return;
    }
    
    // Parse the fields from the adRevenueData object
    NSString *monetizationNetwork = adRevenueDictionary[@"monetizationNetwork"];
    if (!monetizationNetwork) {
        NSLog(@"monetizationNetwork is missing");
        return;
    }
    NSString *currencyIso4217Code = adRevenueDictionary[@"currencyIso4217Code"];
    if (!currencyIso4217Code) {
        NSLog(@"currencyIso4217Code is missing");
        return;
    }
    NSNumber *revenue = adRevenueDictionary[@"revenue"];
    if (!revenue) {
        NSLog(@"revenue is missing or not a number");
        return;
    }
    NSString *mediationNetworkString = adRevenueDictionary[@"mediationNetwork"];
    if (!mediationNetworkString) {
        NSLog(@"mediationNetwork is missing");
        return;
    }
    
    // Use literals to map the mediation network string to the corresponding enum value
    NSDictionary<NSString *, NSNumber *> *mediationNetworkMappings = @{
        @"google_admob": @(AppsFlyerAdRevenueMediationNetworkTypeGoogleAdMob),
        @"ironsource": @(AppsFlyerAdRevenueMediationNetworkTypeIronSource),
        @"applovin_max": @(AppsFlyerAdRevenueMediationNetworkTypeApplovinMax),
        @"fyber": @(AppsFlyerAdRevenueMediationNetworkTypeFyber),
        @"appodeal": @(AppsFlyerAdRevenueMediationNetworkTypeAppodeal),
        @"admost": @(AppsFlyerAdRevenueMediationNetworkTypeAdmost),
        @"topon": @(AppsFlyerAdRevenueMediationNetworkTypeTopon),
        @"tradplus": @(AppsFlyerAdRevenueMediationNetworkTypeTradplus),
        @"yandex": @(AppsFlyerAdRevenueMediationNetworkTypeYandex),
        @"chartboost": @(AppsFlyerAdRevenueMediationNetworkTypeChartBoost),
        @"unity": @(AppsFlyerAdRevenueMediationNetworkTypeUnity),
        @"topon_pte": @(AppsFlyerAdRevenueMediationNetworkTypeToponPte),
        @"custom_mediation": @(AppsFlyerAdRevenueMediationNetworkTypeCustom),
        @"direct_monetization_network": @(AppsFlyerAdRevenueMediationNetworkTypeDirectMonetization),
    };
    
    NSNumber *mediationNetworkEnumNumber = mediationNetworkMappings[mediationNetworkString.lowercaseString];
    if (!mediationNetworkEnumNumber) {
        NSLog(@"Invalid mediation network");
        return;
    }
    
    AppsFlyerAdRevenueMediationNetworkType mediationNetworkEnum = (AppsFlyerAdRevenueMediationNetworkType)[mediationNetworkEnumNumber integerValue];
    
    // If AFAdRevenueData class is available and has the appropriate initializer:
    AFAdRevenueData *adRevenueData = [[AFAdRevenueData alloc] initWithMonetizationNetwork:monetizationNetwork
                                                                         mediationNetwork:mediationNetworkEnum
                                                                      currencyIso4217Code:currencyIso4217Code
                                                                             eventRevenue:revenue];
    
    NSDictionary *additionalParameters = adRevenueDictionary[@"additionalParameters"];
    // Log the ad revenue to the AppsFlyer SDK
    [[AppsFlyerLib shared] logAdRevenue:adRevenueData additionalParameters:additionalParameters];
}

RCT_EXPORT_METHOD(getAppsFlyerUID: (RCTResponseSenderBlock)callback) {
    NSString *uid = [[AppsFlyerLib shared] getAppsFlyerUID];
    callback(@[[NSNull null], uid]);
}

RCT_EXPORT_METHOD(setCustomerUserId: (NSString *)userId callback:(RCTResponseSenderBlock)callback) {
    [[AppsFlyerLib shared] setCustomerUserID:userId];
    callback(@[AF_SUCCESS]);
}

RCT_EXPORT_METHOD(stop: (BOOL)isStopped callback:(RCTResponseSenderBlock)callback) {
    [AppsFlyerLib shared].isStopped  = isStopped;
     callback(@[AF_SUCCESS]);
}

RCT_EXPORT_METHOD(logLocation: (double)longitude latitude:(double)latitude callback:(RCTResponseSenderBlock)callback) {
    [[AppsFlyerLib shared] logLocation:longitude latitude:latitude];
    NSArray *events = @[[NSNumber numberWithDouble:longitude], [NSNumber numberWithDouble:latitude]];
    callback(@[AF_SUCCESS, events]);
}

RCT_EXPORT_METHOD(setUserEmails: (NSDictionary*)options
                  successCallback :(RCTResponseSenderBlock)successCallback
                  errorCallback:(RCTResponseErrorBlock)errorCallback) {
    NSArray *emails = nil;
    id emailsCryptTypeId = nil;
    EmailCryptType emailsCryptType = EmailCryptTypeNone;

    if (![options isKindOfClass:[NSNull class]]) {
        emails = (NSArray*)[options valueForKey: afEmails];

        emailsCryptTypeId = [options objectForKey: afEmailsCryptType];
        if ([emailsCryptTypeId isKindOfClass:[NSNumber class]]) {

            int _t = [emailsCryptTypeId intValue];

            switch (_t) {
                case EmailCryptTypeSHA256:
                    emailsCryptType = EmailCryptTypeSHA256;
                    break;
                default:
                    emailsCryptType = EmailCryptTypeNone;
            }
        }
    }

    NSError* error = nil;

    if (!emails || [emails count] == 0) {
        error = [NSError errorWithDomain:EMPTY_OR_CORRUPTED_LIST code:0 userInfo:nil];
    }

    if(error != nil){
        errorCallback(error);
    }
    else{
        [[AppsFlyerLib shared] setUserEmails:emails withCryptType:emailsCryptType];
        successCallback(@[AF_SUCCESS]);
    }
}

-(void)sendLaunch:(UIApplication *)application {
    [[NSNotificationCenter defaultCenter] postNotificationName:RNAFBridgeInitializedNotification object:self];
    if (![self isManualStart]) {
        [[AppsFlyerLib shared] start];
    }
}

RCT_EXPORT_METHOD(setAdditionalData: (NSDictionary *)additionalData callback:(RCTResponseSenderBlock)callback) {
    [[AppsFlyerLib shared] setAdditionalData:additionalData];
    callback(@[AF_SUCCESS]);
}

//USER INVITES

RCT_EXPORT_METHOD(setAppInviteOneLinkID: (NSString *)oneLinkID callback:(RCTResponseSenderBlock)callback) {
    [AppsFlyerLib shared].appInviteOneLinkID = oneLinkID;
    callback(@[AF_SUCCESS]);
}

RCT_EXPORT_METHOD(setCurrencyCode: (NSString *)currencyCode callback:(RCTResponseSenderBlock)callback) {
    [[AppsFlyerLib shared] setCurrencyCode:currencyCode];
     callback(@[AF_SUCCESS]);
}

RCT_EXPORT_METHOD(generateInviteLink: (NSDictionary *)inviteLinkOptions
                  successCallback:(RCTResponseSenderBlock)successCallback
                  errorCallback:(RCTResponseSenderBlock)errorCallback) {

    NSDictionary* customParams = (NSDictionary*)[inviteLinkOptions objectForKey: @"userParams"];

    if (![inviteLinkOptions isKindOfClass:[NSNull class]]) {

        [AppsFlyerShareInviteHelper generateInviteUrlWithLinkGenerator:^AppsFlyerLinkGenerator * _Nonnull(AppsFlyerLinkGenerator * _Nonnull generator) {

            [generator setChannel:[inviteLinkOptions objectForKey: @"channel"]];
            [generator setReferrerCustomerId:[inviteLinkOptions objectForKey: @"customerID"]];
            [generator setCampaign:[inviteLinkOptions objectForKey: @"campaign"]];
            [generator setReferrerName:[inviteLinkOptions objectForKey: @"referrerName"]];
            [generator setReferrerImageURL:[inviteLinkOptions objectForKey: @"referrerImageUrl"]];
            [generator setDeeplinkPath:[inviteLinkOptions objectForKey: @"deeplinkPath"]];
            [generator setBaseDeeplink:[inviteLinkOptions objectForKey: @"baseDeeplink"]];
            [generator setBrandDomain:[inviteLinkOptions objectForKey: @"brandDomain"]];


            if (![customParams isKindOfClass:[NSNull class]]) {
                [generator addParameters:customParams];
            }

            return generator;
        } completionHandler:^(NSURL * _Nullable url) {

            NSString * resultURL = url.absoluteString;
            if(resultURL != nil){
                successCallback(@[resultURL]);
            }
        }];
    }

}

//CROSS PROMOTION
RCT_EXPORT_METHOD(logCrossPromotionImpression: (NSString *)appId campaign:(NSString *)campaign parameters:(NSDictionary *)parameters) {
        [AppsFlyerCrossPromotionHelper logCrossPromoteImpression:appId campaign:campaign parameters:parameters];
}

RCT_EXPORT_METHOD(logCrossPromotionAndOpenStore: (NSString *)appID
                  campaign:(NSString *)campaign
                  customParams:(NSDictionary *)customParams) {

    [AppsFlyerCrossPromotionHelper logAndOpenStore: appID campaign:campaign parameters:customParams openStore:^(NSURLSession * _Nonnull urlSession, NSURL * _Nonnull clickURL) {
        dispatch_async(dispatch_get_main_queue(), ^{
            [[UIApplication sharedApplication] openURL:clickURL options:@{} completionHandler:^(BOOL success) {
                NSLog(@"AppsFlyer openAppStoreForAppID completionHandler result %d",success);
            }];
        });
    }];
}

- (void)didResolveDeepLink:(AppsFlyerDeepLinkResult* _Nonnull) result {
    NSString *deepLinkStatus = nil;
    switch(result.status) {
        case AFSDKDeepLinkResultStatusFound:
            deepLinkStatus = @"FOUND";
            break;
        case AFSDKDeepLinkResultStatusNotFound:
            deepLinkStatus = @"NOT_FOUND";
            break;
        case AFSDKDeepLinkResultStatusFailure:
            deepLinkStatus = @"ERROR";
            break;
        default:
            [NSException raise:NSGenericException format:@"Unexpected FormatType."];
    }
        NSMutableDictionary* message = [[NSMutableDictionary alloc] initWithCapacity:5];
        message[@"status"] = ([deepLinkStatus isEqual:@"ERROR"] || [deepLinkStatus isEqual:@"NOT_FOUND"]) ? afFailure : afSuccess;
        message[@"deepLinkStatus"] = deepLinkStatus;
        message[@"type"] = afOnDeepLinking;
        message[@"isDeferred"] = result.deepLink.isDeferred ? @YES : @NO;
        if([deepLinkStatus  isEqual: @"ERROR"]){
            message[@"data"] = result.error.localizedDescription;
        }else if([deepLinkStatus  isEqual: @"NOT_FOUND"]){
            message[@"data"] = @"deep link not found";
        }else{
            message[@"data"] = result.deepLink.clickEvent;
        }

    [self performSelectorOnMainThread:@selector(handleCallback:) withObject:message waitUntilDone:NO];
}

-(void)onConversionDataSuccess:(NSDictionary*) installData {
    NSDictionary* message = @{
        @"status": afSuccess,
        @"type": afOnInstallConversionDataLoaded,
        @"data": [installData copy]
    };

    [self performSelectorOnMainThread:@selector(handleCallback:) withObject:message waitUntilDone:NO];
}


-(void)onConversionDataFail:(NSError *) _errorMessage {
    NSLog(@"[DEBUG] AppsFlyer = %@",_errorMessage.localizedDescription);
    NSDictionary* errorMessage = @{
        @"status": afFailure,
        @"type": afOnInstallConversionFailure,
        @"data": _errorMessage.localizedDescription
    };

    [self performSelectorOnMainThread:@selector(handleCallback:) withObject:errorMessage waitUntilDone:NO];

}


- (void) onAppOpenAttribution:(NSDictionary*) attributionData {
    NSDictionary* message = @{
        @"status": afSuccess,
        @"type": afOnAppOpenAttribution,
        @"data": attributionData
    };
    [self performSelectorOnMainThread:@selector(handleCallback:) withObject:message waitUntilDone:NO];
}

- (void) onAppOpenAttributionFailure:(NSError *)_errorMessage {
    NSLog(@"[DEBUG] AppsFlyer = %@",_errorMessage.localizedDescription);
    NSDictionary* errorMessage = @{
        @"status": afFailure,
        @"type": afOnAttributionFailure,
        @"data": _errorMessage.localizedDescription
    };
    [self performSelectorOnMainThread:@selector(handleCallback:) withObject:errorMessage waitUntilDone:NO];
}


-(void) handleCallback:(NSDictionary *) message {
    NSError *error;

    if ([NSJSONSerialization isValidJSONObject:message]) {
        NSData *jsonMessage = [NSJSONSerialization dataWithJSONObject:message
                                                              options:0
                                                                error:&error];
        if (jsonMessage) {
            NSString *jsonMessageStr = [[NSString alloc] initWithBytes:[jsonMessage bytes] length:[jsonMessage length] encoding:NSUTF8StringEncoding];

            NSString* status = (NSString*)[message objectForKey: @"status"];
            NSString* type = (NSString*)[message objectForKey: @"type"];

            if([status isEqualToString:afSuccess]){
                [self reportOnSuccess:jsonMessageStr type:type];
            }
            else{
                [self reportOnFailure:jsonMessageStr type:type];
            }

            NSLog(@"jsonMessageStr = %@",jsonMessageStr);
        } else {
            NSLog(@"%@",error);
        }
    }
    else{
        NSLog(@"failed to parse Response");
    }
}

- (NSArray<NSString *> *)supportedEvents {
    return @[afOnAttributionFailure,afOnAppOpenAttribution,afOnInstallConversionFailure, afOnInstallConversionDataLoaded, afOnDeepLinking, afOnValidationResult];
}

-(void) reportOnFailure:(NSString *)errorMessage type:(NSString*) type {
    @try {
        [self sendEventWithName:type body:errorMessage];
    } @catch (NSException *exception) {
        NSLog(@"AppsFlyer Debug: %@", exception);
    }
}

-(void) reportOnSuccess:(NSString *)data type:(NSString*) type {
    @try {
        [self sendEventWithName:type body:data];
    } @catch (NSException *exception) {
        NSLog(@"AppsFlyer Debug: %@", exception);
    }
}

- (BOOL)isExpoApp {
    return NSClassFromString(@"EXAppDelegateWrapper") != nil;
}

RCT_EXPORT_METHOD(anonymizeUser: (BOOL)b callback:(RCTResponseSenderBlock)callback) {
    [AppsFlyerLib shared].anonymizeUser = b;
    callback(@[AF_SUCCESS]);
}

RCT_EXPORT_METHOD(updateServerUninstallToken: (NSString *)deviceToken callback:(RCTResponseSenderBlock)callback) {
    deviceToken = [deviceToken stringByReplacingOccurrencesOfString:@" " withString:@""];
    NSMutableData *deviceTokenData= [[NSMutableData alloc] init];
    unsigned char whole_byte;
    char byte_chars[3] = {'\0','\0','\0'};
    int i;
    for (i=0; i < [deviceToken length]/2; i++) {
        byte_chars[0] = [deviceToken characterAtIndex:i*2];
        byte_chars[1] = [deviceToken characterAtIndex:i*2+1];
        whole_byte = strtol(byte_chars, NULL, 16);
        [deviceTokenData appendBytes:&whole_byte length:1];
    }
    [[AppsFlyerLib shared] registerUninstall:deviceTokenData];
    callback(@[AF_SUCCESS]);
}

RCT_EXPORT_METHOD(setOneLinkCustomDomains:(NSArray *) domains
                  successCallback :(RCTResponseSenderBlock)successCallback
                  errorCallback:(RCTResponseErrorBlock)errorCallback) {
    [[AppsFlyerLib shared] setOneLinkCustomDomains:domains];
    successCallback(@[AF_SUCCESS]);
}

RCT_EXPORT_METHOD(setResolveDeepLinkURLs:(NSArray *) urls
                  successCallback :(RCTResponseSenderBlock)successCallback
                  errorCallback:(RCTResponseErrorBlock)errorCallback) {
    [[AppsFlyerLib shared] setResolveDeepLinkURLs:urls];
    successCallback(@[AF_SUCCESS]);
}

RCT_EXPORT_METHOD(performOnAppAttribution:(NSString *) urlString
                  successCallback :(RCTResponseSenderBlock)successCallback
                  errorCallback:(RCTResponseErrorBlock)errorCallback) {
    NSURL *url = [NSURL URLWithString:urlString];
    if (url == nil) {
        NSError* error = [NSError errorWithDomain:INVALID_URI code:0 userInfo:nil];
        errorCallback(error);
    } else {
        [[AppsFlyerLib shared] performOnAppAttributionWithURL:url];
        successCallback(@[AF_SUCCESS]);
    }
}

RCT_EXPORT_METHOD(setSharingFilterForPartners:(NSArray *)partners) {
    [[AppsFlyerLib shared] setSharingFilterForPartners:partners];
}

#ifndef AFSDK_NO_IDFA
RCT_EXPORT_METHOD(disableAdvertisingIdentifier:(BOOL)shouldDisable) {
    [AppsFlyerLib shared].disableAdvertisingIdentifier = shouldDisable;
}
#endif

RCT_EXPORT_METHOD(disableCollectASA: (BOOL)shouldDisable) {
    [AppsFlyerLib shared].disableCollectASA = shouldDisable;
}

RCT_EXPORT_METHOD(disableIDFVCollection: (BOOL)shouldDisable) {
    [[AppsFlyerLib shared] setDisableIDFVCollection:shouldDisable];
}

RCT_EXPORT_METHOD(disableAppSetId) {
    NSLog(@"disableAppSetId is only relevant for Android platform");
}

RCT_EXPORT_METHOD(setUseReceiptValidationSandbox: (BOOL)isSandbox) {
    [AppsFlyerLib shared].useReceiptValidationSandbox = isSandbox;
}

RCT_EXPORT_METHOD(validateAndLogInAppPurchase: (NSDictionary*)purchaseInfo
                  successCallback :(RCTResponseSenderBlock)successCallback
                  errorCallback:(RCTResponseErrorBlock)errorCallback) {
    NSString* productIdentifier = nil;
       NSString* tranactionId = nil;
       NSString* price = nil;
       NSString* currency = nil;
       NSDictionary* additionalParameters = nil;
       NSError* error = nil;


       if(![purchaseInfo isKindOfClass: [NSNull class]]){
           productIdentifier = (NSString*)[purchaseInfo objectForKey: afProductIdentifier];
           tranactionId = (NSString*)[purchaseInfo objectForKey: afTransactionId];
           price = (NSString*)[purchaseInfo objectForKey: afPrice];
           currency = (NSString*)[purchaseInfo objectForKey: afCurrency];
           additionalParameters = (NSDictionary*)[purchaseInfo objectForKey: afAdditionalParameters];

        [[AppsFlyerLib shared] validateAndLogInAppPurchase:productIdentifier price:price currency:currency transactionId:tranactionId additionalParameters:additionalParameters success:^(NSDictionary * _Nonnull response) {
            successCallback(@[@"In App Purchase Validation completed successfully!"]);
        } failure:^(NSError * _Nullable error, id  _Nullable reponse) {
            errorCallback(error);
        }];

    }else{
        error = [NSError errorWithDomain:NO_PARAMETERS_ERROR code:0 userInfo:nil];
        errorCallback(error);
    }

}

RCT_EXPORT_METHOD(validateAndLogInAppPurchaseV2: (NSDictionary*)purchaseDetails
                  additionalParameters:(NSDictionary*)additionalParameters) {
    
    if (![self validatePurchaseDetails:purchaseDetails]) {
        return;
    }
    
    NSString* purchaseType = [purchaseDetails objectForKey:@"purchaseType"];
    NSString* productId = [purchaseDetails objectForKey:@"productId"];
    NSString* transactionId = [purchaseDetails objectForKey:@"transactionId"];
    
    AFSDKPurchaseType afPurchaseType = [self convertPurchaseType:purchaseType];
    AFSDKPurchaseDetails* details = [[AFSDKPurchaseDetails alloc] initWithProductId:productId transactionId:transactionId purchaseType:afPurchaseType];
    NSDictionary<NSString*, NSString*>* stringMap = [self convertToStringMap:additionalParameters];

    [[AppsFlyerLib shared] validateAndLogInAppPurchase:details purchaseAdditionalDetails:stringMap completion:^(NSDictionary * _Nullable response, NSError * _Nullable error) {
            if (error == nil) {
                [self handleValidationResponse:response];
            } else {
                [self sendValidationError:error.localizedDescription];
            }
        }];
}

// MARK: - Helper Methods

- (BOOL)validatePurchaseDetails:(NSDictionary*)purchaseDetails {
    if (!purchaseDetails || [purchaseDetails isKindOfClass:[NSNull class]]) {
        [self sendValidationError:@"Purchase details are required"];
        return NO;
    }
    
    NSString* purchaseType = [purchaseDetails objectForKey:@"purchaseType"];
    NSString* productId = [purchaseDetails objectForKey:@"productId"];
    NSString* transactionId = [purchaseDetails objectForKey:@"transactionId"];
    
    if (!purchaseType || !productId || !transactionId) {
        [self sendValidationError:@"purchaseType, productId, and transactionId are required"];
        return NO;
    }
    
    return YES;
}

- (AFSDKPurchaseType)convertPurchaseType:(NSString*)purchaseType {
    if ([purchaseType isEqualToString:@"subscription"]) {
        return AFSDKPurchaseTypeSubscription;
    } else {
        return AFSDKPurchaseTypeOneTimePurchase;
    }
}

- (NSDictionary<NSString*, NSString*>*)convertToStringMap:(NSDictionary*)additionalParameters {
    NSMutableDictionary<NSString*, NSString*>* stringMap = [[NSMutableDictionary alloc] init];
    if (additionalParameters && ![additionalParameters isKindOfClass:[NSNull class]]) {
        for (NSString* key in additionalParameters) {
            id value = additionalParameters[key];
            if (value && ![value isKindOfClass:[NSNull class]]) {
                stringMap[key] = [value description];
            }
        }
    }
    return stringMap;
}

- (void)handleValidationResponse:(NSDictionary*)response {
    NSString* firstKey = [[response allKeys] firstObject];
    if (firstKey && response[firstKey] && [response[firstKey] isKindOfClass:[NSDictionary class]]) {
        NSDictionary* productData = response[firstKey];
        BOOL valid = [productData[@"result"] boolValue];
        
        NSString* responseJson = [self convertToJsonString:response];
        [self sendEventWithName:afOnValidationResult body:responseJson];
    } else {
        [self sendValidationError:@"Invalid response format"];
    }
}

- (NSString*)convertToJsonString:(NSDictionary*)dictionary {
    NSError* jsonError;
    NSData* jsonData = [NSJSONSerialization dataWithJSONObject:dictionary options:0 error:&jsonError];
    if (!jsonError && jsonData) {
        return [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
    }
    return @"{\"error\": \"Failed to serialize response\"}";
}

- (void)sendValidationError:(NSString*)errorMessage {
    NSString* errorJson = [NSString stringWithFormat:@"{\"error\": \"%@\"}", errorMessage];
    [self sendEventWithName:afOnValidationResult body:errorJson];
}

RCT_EXPORT_METHOD(sendPushNotificationData: (NSDictionary*)pushPayload errorCallBack:(RCTResponseErrorBlock)errorCallBack) {
    [[AppsFlyerLib shared] handlePushNotification:pushPayload];
}

RCT_EXPORT_METHOD(setHost: (NSString*)hostPrefix hostName: (NSString*)hostName successCallback :(RCTResponseSenderBlock)successCallback) {
    [[AppsFlyerLib shared] setHost:hostName withHostPrefix:hostPrefix];
    successCallback(@[AF_SUCCESS]);
}

RCT_EXPORT_METHOD(addPushNotificationDeepLinkPath: (NSArray*)path successCallback :(RCTResponseSenderBlock)successCallback
                  errorCallback:(RCTResponseErrorBlock)errorCallback) {
    [[AppsFlyerLib shared] addPushNotificationDeepLinkPath: path];
    successCallback(@[AF_SUCCESS]);
}

RCT_EXPORT_METHOD(disableSKAD: (BOOL)b ) {
    [AppsFlyerLib shared].disableSKAdNetwork = b;
    if (b){
        NSLog(@"[DEBUG] AppsFlyer: SKADNetwork is disabled");
    }else{
        NSLog(@"[DEBUG] AppsFlyer: SKADNetwork is enabled");
    }
}

RCT_EXPORT_METHOD(setCurrentDeviceLanguage: (NSString *)language ) {
    [[AppsFlyerLib shared] setCurrentDeviceLanguage:language];
}

RCT_EXPORT_METHOD(setPartnerData:(NSString *)partnerId partnerData:(NSDictionary *)partnerData) {
    [[AppsFlyerLib shared] setPartnerDataWithPartnerId:partnerId partnerInfo:partnerData];
}

RCT_EXPORT_METHOD(appendParametersToDeepLinkingURL:(NSString *)contains partnerData:(NSDictionary *)parameters) {
    [[AppsFlyerLib shared] appendParametersToDeepLinkingURLWithString:contains parameters:parameters];
}

RCT_EXPORT_METHOD(enableTCFDataCollection:(BOOL)enabled) {
    [[AppsFlyerLib shared] enableTCFDataCollection:enabled];
}

RCT_EXPORT_METHOD(setConsentData:(nullable NSDictionary *)consentDictionary) {
    if (consentDictionary && [consentDictionary isKindOfClass:[NSDictionary class]]) {
        NSNumber *isUserSubjectToGDPR = consentDictionary[@"isUserSubjectToGDPR"];
        if ([isUserSubjectToGDPR isKindOfClass:[NSNull class]]) {
            isUserSubjectToGDPR = nil;
        }
        
        NSNumber *hasConsentForDataUsage = consentDictionary[@"hasConsentForDataUsage"];
        if ([hasConsentForDataUsage isKindOfClass:[NSNull class]]) {
            hasConsentForDataUsage = nil;
        }
        
        NSNumber *hasConsentForAdsPersonalization = consentDictionary[@"hasConsentForAdsPersonalization"];
        if ([hasConsentForAdsPersonalization isKindOfClass:[NSNull class]]) {
            hasConsentForAdsPersonalization = nil;
        }
        
        NSNumber *hasConsentForAdStorage = consentDictionary[@"hasConsentForAdStorage"];
        if ([hasConsentForAdStorage isKindOfClass:[NSNull class]]) {
            hasConsentForAdStorage = nil;
        }
        
        AppsFlyerConsent *consentData = [[AppsFlyerConsent alloc] initWithIsUserSubjectToGDPR:isUserSubjectToGDPR
                                                                       hasConsentForDataUsage:hasConsentForDataUsage
                                                             hasConsentForAdsPersonalization:hasConsentForAdsPersonalization
                                                                       hasConsentForAdStorage:hasConsentForAdStorage];
        
        [[AppsFlyerLib shared] setConsentData:consentData];
    } else {
        NSLog(@"Invalid consentDictionary provided: %@", consentDictionary);
    }
}

@end
