#import "VouchRN.h"
#import <React/RCTBridgeModule.h>
#import <React/RCTConvert.h>
#import <React/RCTLog.h>
#import <VouchSDK/VouchSDK-Swift.h>

#ifdef RCT_NEW_ARCH_ENABLED
#import <RNVouchSpec/RNVouchSpec.h>
#endif

@interface VouchRN ()
@property(nonatomic, strong)
    NSObject *sdk; // Will be SDK* once we can import the header
@property(nonatomic, assign) BOOL initialized;
@property(nonatomic, strong) UIViewController *presentedViewController;
@property(nonatomic, assign) BOOL hasListeners;
@end

@implementation VouchRN

RCT_EXPORT_MODULE(VouchRN)

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

- (void)startObserving {
  self.hasListeners = YES;
}

- (void)stopObserving {
  self.hasListeners = NO;
}

- (instancetype)init {
  self = [super init];
  if (self) {
    _initialized = NO;
    _sdk = nil;
  }
  return self;
}

RCT_EXPORT_METHOD(initialize : (NSString *)customerId apiKey : (NSString *)
                      apiKey languageCodeOverride : (NSString *)
                          languageCodeOverride resolve : (RCTPromiseResolveBlock)
                              resolve reject : (RCTPromiseRejectBlock)reject) {
  // VouchSDK uses WebKit components which must be initialized on the main
  // thread
  dispatch_async(dispatch_get_main_queue(), ^{
    if (self.initialized) {
      RCTLogWarn(@"VouchSDK is already initialized");
      resolve(@(YES));
      return;
    }

    @try {
      VouchSDKBridge *bridge = [[VouchSDKBridge alloc] initWithCustomerId:customerId
                                                                   apiKey:apiKey
                                                      languageCodeOverride:
                                                          languageCodeOverride];
      self.sdk = bridge;

      [bridge initializeWithCompletionHandler:^(NSError *error) {
        dispatch_async(dispatch_get_main_queue(), ^{
          if (self.sdk != bridge) {
            reject(@"initialize_cancelled",
                   @"VouchSDK initialization was cancelled", nil);
            return;
          }

          if (error != nil) {
            self.sdk = nil;
            self.initialized = NO;
            reject(@"initialize_failed", error.localizedDescription, error);
            return;
          }

          self.initialized = YES;
          RCTLogInfo(@"VouchSDK initialized with customerId: %@", customerId);
          resolve(@(YES));
        });
      }];
    } @catch (NSException *exception) {
      self.sdk = nil;
      self.initialized = NO;
      reject(@"initialize_failed", exception.reason, nil);
    }
  });
}

RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSNumber *, isInitialized) {
  return @(self.initialized);
}

RCT_EXPORT_METHOD(destroy : (RCTPromiseResolveBlock)resolve reject
                  : (RCTPromiseRejectBlock)reject) {
  (void)reject;

  dispatch_async(dispatch_get_main_queue(), ^{
    VouchSDKBridge *bridge = (VouchSDKBridge *)self.sdk;
    self.initialized = NO;
    self.sdk = nil;
    self.presentedViewController = nil;

    if (bridge == nil) {
      resolve(@(YES));
      return;
    }

    [bridge invalidateCookiesWithCompletionHandler:^{
      resolve(@(YES));
    }];
  });
}

RCT_EXPORT_METHOD(
    start : (NSString *)dataSourceId webhookUrl : (NSString *)
        webhookUrl inputs : (NSDictionary *)inputs metadata : (NSString *)
            metadata onSuccess : (RCTResponseSenderBlock)
                onSuccess onError : (RCTResponseSenderBlock)onError) {
  dispatch_async(dispatch_get_main_queue(), ^{
    if (!self.initialized || self.sdk == nil) {
      RCTLogError(@"VouchSDK must be initialized before calling start");
      NSDictionary *errorDict = @{
        @"proofId" : @"",
        @"reason" : @(-1),
        @"description" : @"VouchSDK not initialized"
      };
      onError(@[ errorDict ]);
      return;
    }

    VouchSDKBridge *bridge = (VouchSDKBridge *)self.sdk;

    // Convert inputs dictionary
    NSMutableDictionary<NSString *, NSString *> *inputsDict = nil;
    if (inputs && inputs.count > 0) {
      inputsDict = [NSMutableDictionary dictionary];
      for (NSString *key in inputs) {
        inputsDict[key] = [inputs[key] description];
      }
    }

    // Call the Swift start method to get the view controller
    UIViewController *viewController = [bridge
        startWithDataSourceId:dataSourceId
                   webhookUrl:webhookUrl
                       inputs:inputsDict
                     metadata:metadata
                   completion:^(VouchSuccessObjC *success,
                                VouchErrorObjC *error) {
                     dispatch_async(dispatch_get_main_queue(), ^{
                       // Dismiss the view controller
                       if (self.presentedViewController) {
                         [self.presentedViewController
                             dismissViewControllerAnimated:YES
                                                completion:^{
                                                  if (success) {
                                                    NSDictionary *successDict =
                                                        @{
                                                          @"proofId" :
                                                              success.proofId
                                                        };
                                                    onSuccess(@[ successDict ]);
                                                  } else if (error) {
                                                    NSDictionary *errorDict = @{
                                                      @"proofId" :
                                                          error.proofId,
                                                      @"reason" :
                                                          @(error.reason),
                                                      @"description" : error
                                                          .localizedDescription
                                                    };
                                                    onError(@[ errorDict ]);
                                                  }
                                                }];
                       }
                     });
                   }];
    self.presentedViewController = viewController;
    viewController.modalPresentationStyle = UIModalPresentationFullScreen;
    // Present the view controller modally
    UIViewController *rootVC = [self getRootViewController];
    if (rootVC) {
      [rootVC presentViewController:viewController animated:YES completion:nil];
    } else {
      RCTLogError(@"Could not find root view controller to present VouchSDK");
      NSDictionary *errorDict = @{
        @"proofId" : @"",
        @"reason" : @(-1),
        @"description" : @"Could not find root view controller"
      };
      onError(@[ errorDict ]);
    }
  });
}

RCT_EXPORT_METHOD(startHeadless
                  : (NSString *)dataSourceId webhookUrl
                  : (NSString *)webhookUrl inputs
                  : (NSDictionary *)rawInputs metadata
                  : (NSString *)metadata onProgress
                  : (RCTResponseSenderBlock)onProgress resolve
                  : (RCTPromiseResolveBlock)resolve reject
                  : (RCTPromiseRejectBlock)reject) {
  if (!self.initialized) {
    reject(@"not_initialized", @"VouchSDK not initialized", nil);
    return;
  }

  if (![dataSourceId isKindOfClass:[NSString class]]) {
    reject(@"invalid_params", @"dataSourceId is required", nil);
    return;
  }

  dispatch_async(dispatch_get_main_queue(), ^{
    VouchSDKBridge *bridge = (VouchSDKBridge *)self.sdk;
    NSString *requestId = [NSUUID UUID].UUIDString;

    [self emitProgress:@"downloadingConfig" callback:onProgress];
    [bridge downloadConfigWithId:requestId
                    dataSourceId:dataSourceId
                      webhookUrl:webhookUrl
                     redirectUrl:nil
                          inputs:[self stringInputsFromDictionary:rawInputs]
                        metadata:metadata
               completionHandler:^(VouchProofRequestObjC *proofRequest,
                                   NSError *error) {
	                 dispatch_async(dispatch_get_main_queue(), ^{
	                   if (error != nil) {
	                     reject(@"download_config_failed", error.localizedDescription,
	                            error);
	                     return;
	                   }

	                   if (proofRequest == nil) {
	                     reject(@"download_config_failed",
	                            @"Failed to download proof request config", nil);
	                     return;
	                   }

	                   [self startHeadlessSniffingWithBridge:bridge
	                                             proofRequest:proofRequest
	                                             onProgress:onProgress
                                                resolve:resolve
                                                 reject:reject];
                 });
               }];
  });
}

- (void)startHeadlessSniffingWithBridge:(VouchSDKBridge *)bridge
                           proofRequest:(VouchProofRequestObjC *)proofRequest
                             onProgress:(RCTResponseSenderBlock)onProgress
                                resolve:(RCTPromiseResolveBlock)resolve
                                 reject:(RCTPromiseRejectBlock)reject {
  [self emitProgress:@"sniffingRequests" callback:onProgress];

  UIViewController *rootVC = [self getRootViewController];
  if (!rootVC) {
    reject(@"root_view_controller_missing",
           @"Could not find root view controller", nil);
    return;
  }

  UIViewController *viewController =
      [bridge sniffRequestsWithProofRequest:proofRequest
                                 completion:^(VouchSniffRequestsSuccessObjC
                                                  *success,
                                              VouchErrorObjC *error) {
                                   dispatch_async(dispatch_get_main_queue(), ^{
                                     void (^handleResult)(void) = ^{
                                       [self
                                           handleHeadlessSniffSuccess:success
                                                                error:error
                                                               bridge:bridge
                                                           onProgress:onProgress
                                                              resolve:resolve
                                                               reject:reject];
                                     };

                                     if (self.presentedViewController) {
                                       UIViewController *presented =
                                           self.presentedViewController;
                                       self.presentedViewController = nil;
                                       [presented
                                           dismissViewControllerAnimated:YES
                                                              completion:
                                                                  handleResult];
                                     } else {
                                       handleResult();
                                     }
                                   });
                                 }];

  self.presentedViewController = viewController;
  viewController.modalPresentationStyle = UIModalPresentationFullScreen;
  [rootVC presentViewController:viewController animated:YES completion:nil];
}

- (void)handleHeadlessSniffSuccess:(VouchSniffRequestsSuccessObjC *)success
                              error:(VouchErrorObjC *)error
                             bridge:(VouchSDKBridge *)bridge
                         onProgress:(RCTResponseSenderBlock)onProgress
                            resolve:(RCTPromiseResolveBlock)resolve
                             reject:(RCTPromiseRejectBlock)reject {
  if (error != nil) {
    reject(@"sniff_requests_failed", error.localizedDescription, nil);
    return;
  }

  if (success == nil) {
    reject(@"sniff_requests_failed", @"Failed to sniff requests", nil);
    return;
  }

  [self emitProgress:@"proving" callback:onProgress];
  [bridge proveWithProofRequest:success.proofRequest
                         matches:success.matches
               completionHandler:^(VouchSuccessObjC *proofSuccess,
                                   NSError *proveError) {
                 dispatch_async(dispatch_get_main_queue(), ^{
                   if (proveError != nil) {
                     reject(@"prove_failed", proveError.localizedDescription,
                            proveError);
                     return;
                   }

                   if (proofSuccess == nil) {
                     reject(@"prove_failed", @"Failed to prove request", nil);
                     return;
                   }

                   NSString *proofId = proofSuccess.proofId;
                   [self emitProgress:@"finished" callback:onProgress];
                   resolve(@{@"proofId" : proofId});
                 });
               }];
}

- (UIViewController *)getRootViewController {
  UIWindow *window = [UIApplication sharedApplication].delegate.window;
  return window.rootViewController;
}

- (NSMutableDictionary<NSString *, NSString *> *)stringInputsFromDictionary:
    (NSDictionary *)rawInputs {
  if (![rawInputs isKindOfClass:[NSDictionary class]] || rawInputs.count == 0) {
    return nil;
  }

  NSMutableDictionary<NSString *, NSString *> *inputs =
      [NSMutableDictionary dictionary];
  for (NSString *key in rawInputs) {
    inputs[key] = [rawInputs[key] description];
  }
  return inputs;
}

- (void)emitProgress:(NSString *)progress
            callback:(RCTResponseSenderBlock)callback {
  (void)callback;
  if (self.hasListeners) {
    [self sendEventWithName:@"VouchHeadlessProgress" body:progress];
  }
}

#ifdef RCT_NEW_ARCH_ENABLED
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
    (const facebook::react::ObjCTurboModule::InitParams &)params {
  return std::make_shared<facebook::react::NativeVouchSpecJSI>(params);
}
#endif

@end
