#import "QuantumMetricLibrary.h"


@implementation QuantumMetricLibrary

RCT_EXPORT_MODULE()

static QuantumMetricLibrary *sharedInstance = nil;

+ (id)sharedInstanceIfExists {
    return sharedInstance;
}

-(id)init {
    if (self = [super init]) {
//        NSLog(@"QuantumMetricLibrary init");
        
        if (!sharedInstance) {
            sharedInstance = self;
        }
        
        //ask the native SDK what version it is, if it's version 0.3. or below, start automatically
        Class qmnative = NSClassFromString(@"QMNative");
        if (qmnative) {
            //good, don't do anything if no QMNative class
            if ([qmnative respondsToSelector:NSSelectorFromString(@"libraryVersionString")]) {
                NSString *version = [qmnative performSelector:NSSelectorFromString(@"libraryVersionString")];
                //NSLog(@"qmnativesdk version: %@", version);
                //0.3.81 first supported this
                if ([self version:version isEqualToOrGreaterThan:@"0.3.81"]) {
                    //this means the native SDK will start us
                    //NSLog(@"QML: native sdk will start us");
                } else {
                    //this means we need to start ourselves with the default config (which was the previous behavior)
                    //NSLog(@"QML: native sdk will not start us, starting ourselves");
                     [NSTimer scheduledTimerWithTimeInterval:2.0 repeats:false block:^(NSTimer * _Nonnull timer) {
                         [self sendStartConfigToJS:@{}];
                     }];
                }
            } else {
                //something fishy, don't start
            }
        }
    }
    return self;
}

-(BOOL)version:(NSString*)version isEqualToOrGreaterThan:(NSString*)minVersion {
    if (minVersion.length == 0) {
        return true;
    }
    NSString *iOSVersion = version;
    NSArray *minArray = [minVersion componentsSeparatedByString:@"."];
    NSArray *thisVersionArray = [iOSVersion componentsSeparatedByString:@"."];
    for (int i = 0; i<5; i++) {
        int minInt = 0;
        int thisInt = 0;
        
        if (i < minArray.count) {
            minInt = [[minArray objectAtIndex:i] intValue];
        }
        if (i < thisVersionArray.count) {
            thisInt = [[thisVersionArray objectAtIndex:i] intValue];
        }
        if (thisInt > minInt) {
            return true;
        } else if (thisInt == minInt) {
            continue; //need to check next digit
        } else {
            return false;
        }
    }
    return true;
}

+ (BOOL)requiresMainQueueSetup {
    return false;
}

RCT_EXPORT_METHOD(sendEventNative:(NSInteger)eventId value:(NSString*)value json:(NSString *)json flags:(NSInteger)flags)
{
    NSDictionary *jsonDict = nil;

    if (json != nil && json.length > 0) {
        NSError *error = nil;
        jsonDict = [NSJSONSerialization JSONObjectWithData:[json dataUsingEncoding:NSUTF8StringEncoding] options:0 error:&error];

        if (error) {
            NSLog(@"Failed to parse JSON: %@", error.localizedDescription);
            return;
        }
    }

//    NSLog(@"sendEventNative %i %@ %@ %i", eventId, value, jsonDict, flags);

    Class qmNativeClass = NSClassFromString(@"QMNative");
    
    SEL sendEventSel;
    if (jsonDict == nil) {
        sendEventSel = NSSelectorFromString(@"sendEventWithID:value:flags:");
    } else {
        sendEventSel = NSSelectorFromString(@"sendEventWithID:value:json:flags:");
    }

    if ([qmNativeClass respondsToSelector:sendEventSel]) {
        NSMethodSignature *signature = [qmNativeClass methodSignatureForSelector:sendEventSel];
        NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
        [invocation setTarget: qmNativeClass];
        [invocation setSelector:sendEventSel];
        [invocation setArgument:&eventId atIndex:2];
        [invocation setArgument:&value atIndex:3];

        if (jsonDict != nil) {
            [invocation setArgument:&jsonDict atIndex:4];
            [invocation setArgument:&flags atIndex:5];
        } else {
            [invocation setArgument:&flags atIndex:4];
        }

        [invocation invoke];
        NSLog(@"invoked real sendEvent");
    } else {
        NSLog(@"QMNative did not respond to sendEventWithID:value:json:flags: selector");
    }
}

RCT_EXPORT_METHOD(sendNewPageNamedNative:(NSString*)pageName)
{
//    NSLog(@"sendNewPageNamed: %@", pageName);
//  RCTLogInfo(@"Pretending to create sendEvent %i value: %@ flags: %i", eventId, value, flags);
    
    //call sendNewPageNamed on the QM Native SDK
    
    Class qmNativeClass = NSClassFromString(@"QMNative");
    SEL sendNewPageSel = NSSelectorFromString(@"sendNewPageNamed:");
    
    if ([qmNativeClass respondsToSelector:sendNewPageSel]) {
        NSMethodSignature *signature = [qmNativeClass methodSignatureForSelector:sendNewPageSel];
        NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
        [invocation setTarget: qmNativeClass];
        [invocation setSelector:sendNewPageSel];
        [invocation setArgument:&pageName atIndex:2];
        [invocation invoke];
        NSLog(@"invoked real sendNewPageNamed:");
    } else {
        NSLog(@"QMNative did not respond to sendNewPageNamed: selector");
    }
}

RCT_EXPORT_METHOD(pauseQMNative) 
{
    Class qmNativeClass = NSClassFromString(@"QMNative");
    SEL pauseQMSelector = NSSelectorFromString(@"pauseSession");

    if ([qmNativeClass respondsToSelector:pauseQMSelector]) {
        NSMethodSignature *signature = [qmNativeClass methodSignatureForSelector:pauseQMSelector];
        NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
        [invocation setTarget: qmNativeClass];
        [invocation setSelector:pauseQMSelector];
        [invocation invoke];
        NSLog(@"invoked real pauseSession");
    } else {
        NSLog(@"QMNative did not respond to pauseSession selector");
    }
}

RCT_EXPORT_METHOD(resumeQMNative)
{
    Class qmNativeClass = NSClassFromString(@"QMNative");
    SEL restartQMSelector = NSSelectorFromString(@"restartQM");

    if ([qmNativeClass respondsToSelector:restartQMSelector]) {
        NSMethodSignature *signature = [qmNativeClass methodSignatureForSelector:restartQMSelector];
        NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
        [invocation setTarget: qmNativeClass];
        [invocation setSelector:restartQMSelector];
        [invocation invoke];
        NSLog(@"invoked real pauseQM");
    } else {
        NSLog(@"QMNative did not respond to pauseQM selector");
    }
}

RCT_EXPORT_METHOD(stopQMNative)
{
    Class qmNativeClass = NSClassFromString(@"QMNative");
    SEL endSessionSelector = NSSelectorFromString(@"endSession");

    if ([qmNativeClass respondsToSelector:endSessionSelector]) {
        NSMethodSignature *signature = [qmNativeClass methodSignatureForSelector:endSessionSelector];
        NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
        [invocation setTarget: qmNativeClass];
        [invocation setSelector:endSessionSelector];
        [invocation invoke];
        NSLog(@"invoked real endSession");
    } else {
        NSLog(@"QMNative did not respond to endSession selector");
    }
}

RCT_EXPORT_METHOD(resetSessionQMNative:(BOOL)resetUser)
{
    Class qmNativeClass = NSClassFromString(@"QMNative");
    SEL newSessionSel = NSSelectorFromString(@"newSession:");
    
    if ([qmNativeClass respondsToSelector:newSessionSel]) {
        NSMethodSignature *signature = [qmNativeClass methodSignatureForSelector:newSessionSel];
        NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
        [invocation setTarget: qmNativeClass];
        [invocation setSelector:newSessionSel];
        [invocation setArgument:&resetUser atIndex:2];
        [invocation invoke];
        NSLog(@"invoked real newSession:");
    } else {
        NSLog(@"QMNative did not respond to sendNewPageNamed: selector");
    }
}

RCT_EXPORT_METHOD(setSessionCookieCallbackQMNative)
{
    Class qmNativeClass = NSClassFromString(@"QMNative");
    SEL callbackSel = NSSelectorFromString(@"gotSessionCookieCallback:");

    if ([qmNativeClass respondsToSelector:callbackSel]) {
        NSMethodSignature *signature = [qmNativeClass methodSignatureForSelector:callbackSel];
        NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];

        void (^callback)(NSString *sessionCookie, NSString *userString) = ^(NSString *sessionCookie, NSString *userString) {
            [self sendSessionCookie:sessionCookie andUser:userString];
        };

        [invocation setTarget:qmNativeClass];
        [invocation setSelector:callbackSel];
        [invocation setArgument:&callback atIndex:2];
        [invocation invoke];
        NSLog(@"invoked real gotSessionCookieCallback:");
    } else {
        NSLog(@"QMNative did not respond to gotSessionCookieCallback: selector");
    }
}

RCT_EXPORT_METHOD(initializeNative:(NSString *)subscription uid:(NSString *)uid)
{
    Class qmNativeClass = NSClassFromString(@"QMNative");
    SEL initSelector = NSSelectorFromString(@"initializeWithSubscription:uid:");

    if ([qmNativeClass respondsToSelector:initSelector]) {
        NSMethodSignature *signature = [qmNativeClass methodSignatureForSelector:initSelector];
        NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
        [invocation setTarget: qmNativeClass];
        [invocation setSelector:initSelector];
        [invocation setArgument:&subscription atIndex:2];
        [invocation setArgument:&uid atIndex:3];
        [invocation invoke];
        NSLog(@"invoked real initWithSub");
    } else {
        NSLog(@"QMNative did not respond to initWithSub selector");
    }
}

RCT_EXPORT_METHOD(initializeWithOptionsNative:(NSString *)subscription uid:(NSString *)uid options:(id)options)
{
    Class qmNativeClass = NSClassFromString(@"QMNative");
    SEL initSelector = NSSelectorFromString(@"initializeWithSubscription:uid:");

    if ([qmNativeClass respondsToSelector:initSelector]) {
        NSMethodSignature *signature = [qmNativeClass methodSignatureForSelector:initSelector];
        NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
        [invocation setTarget: qmNativeClass];
        [invocation setSelector:initSelector];
        [invocation setArgument:&subscription atIndex:2];
        [invocation setArgument:&uid atIndex:3];
        [invocation invoke];
        NSLog(@"invoked real initWithSub");

        NSDictionary *config = (NSDictionary *)options;
        if (config) {
            BOOL testConfigEnabled = false;
            
            if ([config valueForKey:@"testModeEnabled"]) {
                testConfigEnabled = [[config valueForKey:@"testModeEnabled"] boolValue];
            }

            [self enableTestConfigNative:testConfigEnabled];
            
            if ([config valueForKey:@"sslPinningEnabled"] && [[config valueForKey:@"sslPinningEnabled"] boolValue]) {
                NSString *certificateKey = [config valueForKey:@"certificateKey"];
                if (certificateKey) {
                    SEL addCertificateKeySelector = NSSelectorFromString(@"addCertificateKey:");
                    if ([qmNativeClass respondsToSelector:addCertificateKeySelector]) {
                        NSMethodSignature *signature = [qmNativeClass methodSignatureForSelector:addCertificateKeySelector];
                        NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
                        [invocation setTarget: qmNativeClass];
                        [invocation setSelector:addCertificateKeySelector];
                        [invocation setArgument:&certificateKey atIndex:2];
                        [invocation invoke];
                        NSLog(@"SSL Certificate Key added");
                    }
                }

                SEL sslPinningEnabledSelector = NSSelectorFromString(@"enableSSLCertificatePinning");
                if ([qmNativeClass respondsToSelector:sslPinningEnabledSelector]) {
                    NSMethodSignature *signature = [qmNativeClass methodSignatureForSelector:sslPinningEnabledSelector];
                    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
                    [invocation setTarget: qmNativeClass];
                    [invocation setSelector:sslPinningEnabledSelector];
                    [invocation invoke];
                    NSLog(@"invoked enableSSLCertificatePinning based on options");
                }
            }
            
            if ([config valueForKey:@"crashReportingEnabled"] && [[config valueForKey:@"crashReportingEnabled"] boolValue] == false) {
                SEL disableCrashReportingSelector = NSSelectorFromString(@"disableCrashReporting");
                if ([qmNativeClass respondsToSelector:disableCrashReportingSelector]) {
                    NSMethodSignature *signature = [qmNativeClass methodSignatureForSelector:disableCrashReportingSelector];
                    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
                    [invocation setTarget: qmNativeClass];
                    [invocation setSelector:disableCrashReportingSelector];
                    [invocation invoke];
                    NSLog(@"invoked real disableCrashReporting");
                }
            }
        }
        // This is for testing HookXHR
        // [self sendStartConfigToJS:@{
        //     @"hookXHR": @YES,
        //     @"hookFetch": @NO
        // }];
        
        NSLog(@"QMNative finished initializing with options");
    } else {
        NSLog(@"QMNative did not respond to initWithSub selector");
    }
}

RCT_EXPORT_METHOD(enableTestConfigNative:(BOOL)enabled) {
    Class qmNativeClass = NSClassFromString(@"QMNative");
    SEL testConfigSelector = NSSelectorFromString(@"enableTestConfig:");

    if ([qmNativeClass respondsToSelector:testConfigSelector]) {
        NSMethodSignature *signature = [qmNativeClass methodSignatureForSelector:testConfigSelector];
        NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
        [invocation setTarget: qmNativeClass];
        [invocation setSelector:testConfigSelector];
        [invocation setArgument:&enabled atIndex:2];
        [invocation invoke];
        NSLog(@"invoked real enableTestConfig");
    } else {
        NSLog(@"QMNative did not respond to enableTestConfig selector");
    }
}

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

- (void)sendSessionCookie:(NSString *)sessionCookie andUser:(NSString *)userString {
    NSDictionary *body = @{
        @"sessionCookie": sessionCookie,
        @"userString": userString
    };

    [self sendEventWithName:@"QMSessionCookieCallback" body: body];
}

- (void)sendStartConfigToJS:(NSDictionary*)config {
//  NSString *eventName = notification.userInfo[@"name"];
  [self sendEventWithName:@"QMStartNotification" body:config];
}

@end
