//
//  KochavaTracker (ReactNative)
//
//  Copyright (c) 2018 - 2023 Kochava, Inc. All rights reserved.
//

#pragma mark - Import

#import "RNKochavaTracker.h"

#pragma mark - Util

// Interface for the kochavaTrackerUtil
@interface KochavaTrackerUtil : NSObject

@end

// Common utility functions used by all of the wrappers.
// Any changes to the methods in here must be propagated to the other wrappers.
@implementation KochavaTrackerUtil

// Log a message to the console.
+ (void)log:(nonnull NSString *)message {
    NSLog(@"KVA/Tracker: %@", message);
}

// Attempts to read an NSDictionary and returns nil if not one.
+ (nullable NSDictionary *)readNSDictionary:(nullable id)valueId {
    return [[NSDictionary class] performSelector:@selector(kva_from:) withObject:valueId];
}

// Attempts to read an NSArray and returns nil if not one.
+ (nullable NSArray *)readNSArray:(nullable id)valueId {
    return [[NSArray class] performSelector:@selector(kva_from:) withObject:valueId];
}

// Attempts to read an NSNumber and returns nil if not one.
+ (nullable NSNumber *)readNSNumber:(nullable id)valueId {
    return [[NSNumber class] performSelector:@selector(kva_from:) withObject:valueId];
}

// Attempts to read an NSString and returns nil if not one.
+ (nullable NSString *)readNSString:(nullable id)valueId {
    return [NSString kva_from:valueId];
}

// Attempts to read an NSObject and returns nil if not one.
+ (nullable NSObject *)readNSObject:(nullable id)valueId {
    return [valueId isKindOfClass:NSNull.self] ? nil : valueId;
}

// Converts an NSNumber to a double with fallback to a default value.
+ (double)convertNumberToDouble:(nullable NSNumber *)number defaultValue:(double)defaultValue {
    if(number != nil) {
        return [number doubleValue];
    }
    return defaultValue;
}

// Converts an NSNumber to a bool with fallback to a default value.
+ (BOOL)convertNumberToBool:(nullable NSNumber *)number defaultValue:(BOOL)defaultValue {
    if(number != nil) {
        return [number boolValue];
    }
    return defaultValue;
}

// Converts the deeplink result into an NSDictionary.
+ (nonnull NSDictionary *)convertDeeplinkToDictionary:(nonnull KVADeeplink *)deeplink {
    NSObject *object = [deeplink kva_asForContext:KVAContext.host];
    return [object isKindOfClass:NSDictionary.class] ? (NSDictionary *)object : @{};
}

// Converts the install attribution result into an NSDictionary.
+ (nonnull NSDictionary *)convertInstallAttributionToDictionary:(nonnull KVAAttributionResult *)installAttribution {
    if (KVATracker.shared.startedBool) {
        NSObject *object = [installAttribution kva_asForContext:KVAContext.host];
        return [object isKindOfClass:NSDictionary.class] ? (NSDictionary *)object : @{};
    } else {
        return @{
                @"retrieved": @(NO),
                @"raw": @{},
                @"attributed": @(NO),
                @"firstInstall": @(NO),
        };
    }
}

// Converts the config result into an NSDictionary.
+ (nonnull NSDictionary *)convertConfigToDictionary:(nonnull KVATrackerConfig *)config {
    return @{
            @"consentGdprApplies": @(config.consentGDPRAppliesBool),
    };
}

// Serialize an NSDictionary into a json serialized NSString.
+ (nullable NSString *)serializeJsonObject:(nullable NSDictionary *)dictionary {
    return [NSString kva_stringFromJSONObject:dictionary prettyPrintBool:NO];
}

// Parse a json serialized NSString into an NSArray.
+ (nullable NSArray *)parseJsonArray:(nullable NSString *)string {
    NSObject *object = [string kva_serializedJSONObjectWithPrintErrorsBool:YES];
    return ([object isKindOfClass:NSArray.class] ? (NSArray *) object : nil);
}

// Parse a json serialized NSString into an NSDictionary.
+ (nullable NSDictionary *)parseJsonObject:(nullable NSString *)string {
    NSObject *object = [string kva_serializedJSONObjectWithPrintErrorsBool:YES];
    return [object isKindOfClass:NSDictionary.class] ? (NSDictionary *) object : nil;
}

// Parse a NSString into a NSURL and logs a warning on failure.
+ (nullable NSURL *)parseNSURL:(nullable NSString *)string {
    NSURL *url = [NSURL URLWithString:string];
    if (url == nil && string.length > 0) {
        [KochavaTrackerUtil log:@"Warn: parseNSURL invalid input, not a valid URL"];
    }
    return url;
}

// Builds and sends an event given an event info dictionary.
+ (void)buildAndSendEvent:(nullable NSDictionary *)eventInfo {
    if(eventInfo == nil) {
        return;
    }
    NSString *name = [KochavaTrackerUtil readNSString:eventInfo[@"name"]];
    NSDictionary *data = [KochavaTrackerUtil readNSDictionary:eventInfo[@"data"]];
    NSString *iosAppStoreReceiptBase64String = [KochavaTrackerUtil readNSString:eventInfo[@"iosAppStoreReceiptBase64String"]];
    if (name.length > 0) {
        KVAEvent *event = [[KVAEvent alloc] initCustomWithEventName:name];
        if (data != nil) {
            event.infoDictionary = data;
        }
        if (iosAppStoreReceiptBase64String.length > 0) {
            event.appStoreReceiptBase64EncodedString = iosAppStoreReceiptBase64String;
        }
        [event send];
    } else {
        [KochavaTrackerUtil log:@"Warn: sendEventWithEvent invalid input"];
    }
}

@end

#pragma mark - Methods

@implementation RNKochavaTracker

// Set the logging parameters before any other access to the SDK.
+ (void) initialize {
    KVALog.shared.osLogEnabledBool = false;
    KVALog.shared.printLinesIndividuallyBool = true;
}

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

RCT_EXPORT_MODULE()

// List of the events that can be sent from the native layer.
- (NSArray<NSString *> *)supportedEvents {
    return @[@"KochavaTrackerInitCompleted"];
}

// void executeAdvancedInstruction(string name, string value)
RCT_EXPORT_METHOD(executeAdvancedInstruction:(NSString *)name value:(NSString *)value) {
    [KVATracker.shared.networking executeAdvancedInstructionWithUniversalIdentifier:name parameter:value prerequisiteTaskIdentifierArray:nil];
}

// void setLogLevel(LogLevel logLevel)
RCT_EXPORT_METHOD(setLogLevel:(NSString *)logLevel) {
    KVALog.shared.level = [KVALogLevel kva_from:logLevel];
}

// void setSleep(bool sleep)
RCT_EXPORT_METHOD(setSleep:(BOOL)sleep) {
    KVATracker.shared.sleepBool = sleep;
}

// void setAppLimitAdTracking(bool appLimitAdTracking)
RCT_EXPORT_METHOD(setAppLimitAdTracking:(BOOL)appLimitAdTracking) {
    KVATracker.shared.appLimitAdTracking.boolean = appLimitAdTracking;
}

// void registerCustomDeviceIdentifier(string name, string value)
RCT_EXPORT_METHOD(registerCustomDeviceIdentifier:(NSString *)name value:(NSString *)value) {
    [KVATracker.shared.customIdentifiers registerWithName:name identifier:value];
}

// void registerCustomStringValue(string name, string value)
RCT_EXPORT_METHOD(registerCustomStringValue:(NSString *)name value:(NSString *)value) {
    [KVACustomValue registerWithName:name value:value];
}

// void registerCustomBoolValue(string name, bool value)
RCT_EXPORT_METHOD(registerCustomBoolValue:(NSString *)name value:(NSNumber *)value) {
    [KVACustomValue registerWithName:name value:value];
}

// void registerCustomNumberValue(string name, number value)
RCT_EXPORT_METHOD(registerCustomNumberValue:(NSString *)name value:(NSNumber *)value) {
    [KVACustomValue registerWithName:name value:value];
}

// void registerIdentityLink(string name, string value)
RCT_EXPORT_METHOD(registerIdentityLink:(NSString *)name value:(NSString *)value) {
    [KVATracker.shared.identityLink registerWithName:name identifier:value];
}

// void enableAndroidInstantApps(string instantAppGuid)
RCT_EXPORT_METHOD(enableAndroidInstantApps:(NSString *)instantAppGuid) {
    [KochavaTrackerUtil log:@"enableAndroidInstantApps API is not available on this platform."];
}

// void enableIosAppClips(string identifier)
RCT_EXPORT_METHOD(enableIosAppClips:(NSString *)identifier) {
    KVAAppGroups.shared.deviceAppGroupIdentifier = identifier;
}

// void enableIosAtt()
RCT_EXPORT_METHOD(enableIosAtt) {
    KVATracker.shared.appTrackingTransparency.enabledBool = true;
}

// void setIosAttAuthorizationWaitTime(double waitTime)
RCT_EXPORT_METHOD(setIosAttAuthorizationWaitTime:(double)waitTime) {
    KVATracker.shared.appTrackingTransparency.authorizationStatusWaitTimeInterval = waitTime;
}

// void setIosAttAuthorizationAutoRequest(bool autoRequest)
RCT_EXPORT_METHOD(setIosAttAuthorizationAutoRequest:(BOOL)autoRequest) {
    KVATracker.shared.appTrackingTransparency.autoRequestTrackingAuthorizationBool = autoRequest;
}

// void registerPrivacyProfile(string name, string[] keys)
RCT_EXPORT_METHOD(registerPrivacyProfile:(NSString *)name keysSerialized:(NSString *)keysSerialized) {
    NSArray *keys = [KochavaTrackerUtil parseJsonArray:keysSerialized];

    [KVAPrivacyProfile registerWithName:name payloadKeyStringArray:keys];
}

// void setPrivacyProfileEnabled(string name, bool enabled)
RCT_EXPORT_METHOD(setPrivacyProfileEnabled:(NSString *)name enabled:(BOOL)enabled) {
    [KVATracker.shared.privacy setEnabledBoolForProfileName:name enabledBool:enabled];
}

// void setInitCompletedListener(bool setListener)
RCT_EXPORT_METHOD(setInitCompletedListener:(BOOL *)setListener) {
    if(setListener) {
        KVATracker.shared.config.closure_didComplete = ^(KVATrackerConfig * _Nonnull config) {
            NSDictionary *configDictionary = [KochavaTrackerUtil convertConfigToDictionary:config];
            NSString *configString = [KochavaTrackerUtil serializeJsonObject:configDictionary] ?: @"{}";
            [self sendEventWithName:@"KochavaTrackerInitCompleted" body:configString];
        };
    } else {
        KVATracker.shared.config.closure_didComplete = nil;
    }
}

// void setIntelligentConsentGranted(bool granted)
RCT_EXPORT_METHOD(setIntelligentConsentGranted:(BOOL *)granted) {
    KVATracker.shared.privacy.intelligentConsent.grantedBoolNumber = [NSNumber numberWithBool:granted];
}

// bool getStarted()
RCT_EXPORT_METHOD(getStarted:(RCTPromiseResolveBlock)promiseResolve promiseReject:(RCTPromiseRejectBlock)promiseReject) {
    if(promiseResolve == nil) {
        return;
    }
    promiseResolve(@(KVATracker.shared.startedBool));
}

// void start(string androidAppGuid, string iosAppGuid, string partnerName)
RCT_EXPORT_METHOD(start:(NSString *)androidAppGuid iosAppGuid:(NSString *)iosAppGuid partnerName:(NSString *)partnerName) {
    if (iosAppGuid.length > 0) {
        [KVATracker.shared startWithAppGUIDString:iosAppGuid];
    } else if (partnerName.length > 0) {
        [KVATracker.shared startWithPartnerNameString:partnerName];
    } else {
        // Allow the native to log the error of no app guid.
        [KVATracker.shared startWithAppGUIDString:nil];
    }
}

// void shutdown(bool deleteData)
RCT_EXPORT_METHOD(shutdown:(BOOL)deleteData) {
    [KVATrackerProduct.shared shutdownWithDeleteLocalDataBool:deleteData];
}

// string getDeviceId()
RCT_EXPORT_METHOD(getDeviceId:(RCTPromiseResolveBlock)promiseResolve promiseReject:(RCTPromiseRejectBlock)promiseReject) {
    if(promiseResolve == nil) {
        return;
    }
    if(KVATracker.shared.startedBool) {
        promiseResolve(KVATracker.shared.deviceId.string ?: @"");
    } else {
        promiseResolve(@"");
    }
}

// InstallAttribution getInstallAttribution()
RCT_EXPORT_METHOD(getInstallAttribution:(RCTPromiseResolveBlock)promiseResolve promiseReject:(RCTPromiseRejectBlock)promiseReject) {
    if(promiseResolve == nil) {
        return;
    }

    NSDictionary *attributionDictionary = [KochavaTrackerUtil convertInstallAttributionToDictionary:KVATracker.shared.attribution.result];
    NSString *attributionSerialized = [KochavaTrackerUtil serializeJsonObject:attributionDictionary] ?: @"";
    promiseResolve(attributionSerialized);
}

// void retrieveInstallAttribution(Callback<InstallAttribution> callback)
RCT_EXPORT_METHOD(retrieveInstallAttribution:(RCTPromiseResolveBlock)promiseResolve promiseReject:(RCTPromiseRejectBlock)promiseReject) {
    if(promiseResolve == nil) {
        return;
    }

    [KVATracker.shared.attribution retrieveResultWithCompletionHandler:^(KVAAttributionResult *attribution) {
        NSDictionary *attributionDictionary = [KochavaTrackerUtil convertInstallAttributionToDictionary:attribution];
        NSString *attributionSerialized = [KochavaTrackerUtil serializeJsonObject:attributionDictionary] ?: @"";
        promiseResolve(attributionSerialized);
    }];
}

// void processDeeplink(string path, Callback<Deeplink> callback)
RCT_EXPORT_METHOD(processDeeplink:(NSString *)pathString promiseResolve:(RCTPromiseResolveBlock)promiseResolve promiseReject:(RCTPromiseRejectBlock)promiseReject) {
    if(promiseResolve == nil) {
        return;
    }
    NSURL *path = [KochavaTrackerUtil parseNSURL:pathString];

    [KVADeeplink processWithURL:path closure_didComplete:^(KVADeeplink *_Nonnull deeplink) {
        NSDictionary *deeplinkDictionary = [KochavaTrackerUtil convertDeeplinkToDictionary:deeplink];
        NSString *deeplinkSerialized = [KochavaTrackerUtil serializeJsonObject:deeplinkDictionary] ?: @"";
        promiseResolve(deeplinkSerialized);
    }];
}

// void processDeeplinkWithOverrideTimeout(string path, double timeout, Callback<Deeplink> callback)
RCT_EXPORT_METHOD(processDeeplinkWithOverrideTimeout:(NSString *)pathString timeout:(double)timeout promiseResolve:(RCTPromiseResolveBlock)promiseResolve promiseReject:(RCTPromiseRejectBlock)promiseReject) {
    if(promiseResolve == nil) {
        return;
    }
    NSURL *path = [KochavaTrackerUtil parseNSURL:pathString];

    [KVADeeplink processWithURL:path timeoutTimeInterval:timeout closure_didComplete:^(KVADeeplink *_Nonnull deeplink) {
        NSDictionary *deeplinkDictionary = [KochavaTrackerUtil convertDeeplinkToDictionary:deeplink];
        NSString *deeplinkSerialized = [KochavaTrackerUtil serializeJsonObject:deeplinkDictionary] ?: @"";
        promiseResolve(deeplinkSerialized);
    }];
}

// void registerPushToken(string token)
RCT_EXPORT_METHOD(registerPushToken:(NSString *)token) {
    [KVAPushNotificationsToken registerWithDataHexString:token];
}

// void setPushEnabled(bool enabled)
RCT_EXPORT_METHOD(setPushEnabled:(BOOL)enabled) {
    KVATracker.shared.pushNotifications.enabledBool = enabled;
}

// void registerDefaultEventStringParameter(string name, string value)
RCT_EXPORT_METHOD(registerDefaultEventStringParameter:(NSString *)name value:(NSString *)value) {
    [KVAEventDefaultParameter registerWithName:name value:value];
}

// void registerDefaultEventBoolParameter(string name, bool value)
RCT_EXPORT_METHOD(registerDefaultEventBoolParameter:(NSString *)name value:(NSNumber *)value) {
    [KVAEventDefaultParameter registerWithName:name value:value];
}

// void registerDefaultEventNumberParameter(string name, number value)
RCT_EXPORT_METHOD(registerDefaultEventNumberParameter:(NSString *)name value:(NSNumber *)value) {
    [KVAEventDefaultParameter registerWithName:name value:value];
}

// void registerDefaultEventUserId(string value)
RCT_EXPORT_METHOD(registerDefaultEventUserId:(NSString *)value) {
    [KVAEventDefaultParameter registerWithUserIdString:value];
}

// void sendEvent(string name)
RCT_EXPORT_METHOD(sendEvent:(NSString *)name) {
    if(name.length > 0) {
        [KVAEvent sendCustomWithEventName:name];
    } else {
        [KochavaTrackerUtil log:@"Warn: sendEvent invalid input"];
    }
}

// void sendEventWithString(string name, string data)
RCT_EXPORT_METHOD(sendEventWithString:(NSString *)name data:(NSString *)data) {
    if(name.length > 0) {
        [KVAEvent sendCustomWithEventName:name infoString:data];
    } else {
        [KochavaTrackerUtil log:@"Warn: sendEventWithString invalid input"];
    }
}

// void sendEventWithDictionary(string name, object data)
RCT_EXPORT_METHOD(sendEventWithDictionary:(NSString *)name dataSerialized:(NSString *)dataSerialized) {
    NSDictionary *data = [KochavaTrackerUtil parseJsonObject:dataSerialized];

    if (name.length > 0) {
        [KVAEvent sendCustomWithEventName:name infoDictionary:data];
    } else {
        [KochavaTrackerUtil log:@"Warn: sendEventWithDictionary invalid input"];
    }
}

// void sendEventWithEvent(Event event)
RCT_EXPORT_METHOD(sendEventWithEvent:(NSString *)eventSerialized) {
    NSDictionary *eventInfo = [KochavaTrackerUtil parseJsonObject:eventSerialized];

    [KochavaTrackerUtil buildAndSendEvent:eventInfo];
}

@end
