#import "RTKRNPermissions.h"
#import "RTKLogger.h"
#import <React/RCTLog.h>
#import <React/RCTConvert.h>
#import "RTKRNPermissionHandlerBluetoothPeripheral.h"
#import "RTKRNPermissionHandlerCamera.h"
#import "RTKRNPermissionHandlerMicrophone.h"
#import "RTKRNPermissionHandlerPhotoLibrary.h"
#import "RTKRNPermissionHandlerNotifications.h"

static NSString* SETTING_KEY = @"@RNPermissions:Requested";

@implementation RCTConvert(RNPermission)

RCT_ENUM_CONVERTER(RNPermission, (@{
    [RTKRNPermissionHandlerBluetoothPeripheral handlerUniqueId]: @(RNPermissionBluetoothPeripheral),
    [RTKRNPermissionHandlerCamera handlerUniqueId]: @(RNPermissionCamera),
    [RTKRNPermissionHandlerMicrophone handlerUniqueId]: @(RNPermissionMicrophone),
    [RTKRNPermissionHandlerPhotoLibrary handlerUniqueId]: @(RNPermissionPhotoLibrary),
}), RNPermissionUnknown, integerValue);

@end

@interface RTKRNPermissions()

@property (nonatomic, strong) NSMutableDictionary<NSString *, id<RTKRNPermissionHandler>> *_Nonnull handlers;

@end

@implementation RTKRNPermissions

RCT_EXPORT_MODULE();

+ (BOOL)requiresMainQueueSetup {
  return YES;
}

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

- (NSDictionary *)constantsToExport {
  NSMutableArray<NSString *> *available = [NSMutableArray new];


    [available addObject:[RTKRNPermissionHandlerBluetoothPeripheral handlerUniqueId]];
    [available addObject:[RTKRNPermissionHandlerCamera handlerUniqueId]];
    [available addObject:[RTKRNPermissionHandlerMicrophone handlerUniqueId]];
    [available addObject:[RTKRNPermissionHandlerPhotoLibrary handlerUniqueId]];

#if RCT_DEV
  if ([available count] == 0) {
    NSMutableString *message = [NSMutableString new];

    [message appendString:@"⚠  No permission handler detected.\n\n"];
    [message appendString:@"• Check that you link at least one permission handler in your Podfile.\n"];
    [message appendString:@"• Uninstall this app, delete your Xcode DerivedData folder and rebuild it.\n"];
    [message appendString:@"• If you use `use_frameworks!`, follow the workaround guide in the project README."];

    RCTLogError(@"%@", message);
  }
#endif

  return @{ @"available": available };
}

- (void)checkUsageDescriptionKeys:(NSArray<NSString *> * _Nonnull)keys {
#if RCT_DEV
  for (NSString *key in keys) {
    if (![[NSBundle mainBundle] objectForInfoDictionaryKey:key]) {
      RCTLogError(@"Cannot check or request permission without the required \"%@\" entry in your app \"Info.plist\" file", key);
      return;
    }
  }
#endif
}

- (id<RTKRNPermissionHandler> _Nullable)handlerForPermission:(RNPermission)permission {
    id<RTKRNPermissionHandler> handler = nil;

  switch (permission) {

    case RNPermissionBluetoothPeripheral:
          handler = [RTKRNPermissionHandlerBluetoothPeripheral new];
          [RTKLogger debug:@"RTKRNPermissions: Created handler for BluetoothPeripheral"];
      break;

    case RNPermissionCamera:
          handler = [RTKRNPermissionHandlerCamera new];
          [RTKLogger debug:@"RTKRNPermissions: Created handler for Camera"];
      break;
    case RNPermissionMicrophone:
          handler = [RTKRNPermissionHandlerMicrophone new];
          [RTKLogger debug:@"RTKRNPermissions: Created handler for Microphone"];
      break;
    case RNPermissionPhotoLibrary:
          handler = [RTKRNPermissionHandlerPhotoLibrary new];
          [RTKLogger debug:@"RTKRNPermissions: Created handler for PhotoLibrary"];
      break;
    case RNPermissionUnknown:
      [RTKLogger warning:@"RTKRNPermissions: Unknown permission type"];
      break; // RCTConvert prevents this case
  }

  [self checkUsageDescriptionKeys:[[handler class] usageDescriptionKeys]];
  return handler;
}

- (NSString *)stringForStatus:(RNPermissionStatus)status {
  switch (status) {
    case RNPermissionStatusNotAvailable:
    case RNPermissionStatusRestricted:
      return @"unavailable";
    case RNPermissionStatusNotDetermined:
      return @"denied";
    case RNPermissionStatusDenied:
      return @"blocked";
    case RNPermissionStatusLimited:
      return @"limited";
    case RNPermissionStatusAuthorized:
      return @"granted";
  }
}

- (NSString *)lockHandler:(id<RTKRNPermissionHandler>)handler {
  if (_handlers == nil) {
    _handlers = [NSMutableDictionary new];
  }

  NSString *lockId = [[NSUUID UUID] UUIDString];
  [_handlers setObject:handler forKey:lockId];

  return lockId;
}

- (void)unlockHandler:(NSString * _Nonnull)lockId {
  if (_handlers != nil) {
    [self.handlers removeObjectForKey:lockId];
  }
}

+ (bool)isFlaggedAsRequested:(NSString * _Nonnull)handlerId {
  NSArray<NSString *> *requested = [[NSUserDefaults standardUserDefaults] arrayForKey:SETTING_KEY];
  return requested == nil ? false : [requested containsObject:handlerId];
}

+ (void)flagAsRequested:(NSString * _Nonnull)handlerId {
  NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
  NSMutableArray *requested = [[userDefaults arrayForKey:SETTING_KEY] mutableCopy];

  if (requested == nil) {
    requested = [NSMutableArray new];
  }

  if (![requested containsObject:handlerId]) {
    [requested addObject:handlerId];
    [userDefaults setObject:requested forKey:SETTING_KEY];
    [userDefaults synchronize];
  }
}

RCT_REMAP_METHOD(openSettings,
                 openSettingsWithResolver:(RCTPromiseResolveBlock)resolve
                 rejecter:(RCTPromiseRejectBlock)reject) {
  [RTKLogger debug:@"RTKRNPermissions: openSettings() called"];
  UIApplication *sharedApplication = [UIApplication sharedApplication];
  NSURL *url = [NSURL URLWithString:UIApplicationOpenSettingsURLString];

  [sharedApplication openURL:url options:@{} completionHandler:^(BOOL success) {
    if (success) {
      [RTKLogger debug:@"RTKRNPermissions: Settings opened successfully"];
      resolve(@(true));
    } else {
      [RTKLogger error:@"RTKRNPermissions: Failed to open settings"];
      reject(@"cannot_open_settings", @"Cannot open application settings", nil);
    }
  }];
}

RCT_REMAP_METHOD(check,
                 checkWithPermission:(RNPermission)permission
                 resolver:(RCTPromiseResolveBlock)resolve
                 rejecter:(RCTPromiseRejectBlock)reject) {
    [RTKLogger debug:@"RTKRNPermissions: check() called for permission: %ld", (long)permission];
    id<RTKRNPermissionHandler> handler = [self handlerForPermission:permission];
  NSString *lockId = [self lockHandler:handler];

  [handler checkWithResolver:^(RNPermissionStatus status) {
    [RTKLogger debug:@"RTKRNPermissions: check() resolved with status: %@", [self stringForStatus:status]];
    resolve([self stringForStatus:status]);
    [self unlockHandler:lockId];
  } rejecter:^(NSError *error) {
    [RTKLogger error:@"RTKRNPermissions: check() failed with error: %@", error.localizedDescription];
    reject([NSString stringWithFormat:@"%ld", (long)error.code], error.localizedDescription, error);
    [self unlockHandler:lockId];
  }];
}

RCT_REMAP_METHOD(request,
                 requestWithPermission:(RNPermission)permission
                 resolver:(RCTPromiseResolveBlock)resolve
                 rejecter:(RCTPromiseRejectBlock)reject) {
    [RTKLogger debug:@"RTKRNPermissions: request() called for permission: %ld", (long)permission];
    id<RTKRNPermissionHandler> handler = [self handlerForPermission:permission];
  NSString *lockId = [self lockHandler:handler];  [handler requestWithResolver:^(RNPermissionStatus status) {
    [RTKLogger debug:@"RTKRNPermissions: request() resolved with status: %@", [self stringForStatus:status]];
    resolve([self stringForStatus:status]);
    [self unlockHandler:lockId];
  } rejecter:^(NSError *error) {
    [RTKLogger error:@"RTKRNPermissions: request() failed with error: %@", error.localizedDescription];
    reject([NSString stringWithFormat:@"%ld", (long)error.code], error.localizedDescription, error);
    [self unlockHandler:lockId];
  }];
}

RCT_REMAP_METHOD(checkNotifications,
                 checkNotificationsWithResolver:(RCTPromiseResolveBlock)resolve
                 rejecter:(RCTPromiseRejectBlock)reject) {
  [RTKLogger debug:@"RTKRNPermissions: checkNotifications() called"];
#if __has_include("RTKRNPermissionHandlerNotifications.h")
  RTKRNPermissionHandlerNotifications *handler = [RTKRNPermissionHandlerNotifications new];
  NSString *lockId = [self lockHandler:(id<RTKRNPermissionHandler>)handler];

  [handler checkWithResolver:^(RNPermissionStatus status, NSDictionary * _Nonnull settings) {
    [RTKLogger debug:@"RTKRNPermissions: checkNotifications() resolved with status: %@", [self stringForStatus:status]];
    resolve(@{ @"status": [self stringForStatus:status], @"settings": settings });
    [self unlockHandler:lockId];
  } rejecter:^(NSError * _Nonnull error) {
    [RTKLogger error:@"RTKRNPermissions: checkNotifications() failed with error: %@", error.localizedDescription];
    reject([NSString stringWithFormat:@"%ld", (long)error.code], error.localizedDescription, error);
    [self unlockHandler:lockId];
  }];
#else
  [RTKLogger error:@"RTKRNPermissions: Notifications permission pod is missing"];
  reject(@"notifications_pod_missing", @"Notifications permission pod is missing", nil);
#endif
}

RCT_REMAP_METHOD(requestNotifications,
                 requestNotificationsWithOptions:(NSArray<NSString *> * _Nonnull)options
                 resolver:(RCTPromiseResolveBlock)resolve
                 rejecter:(RCTPromiseRejectBlock)reject) {
  [RTKLogger debug:@"RTKRNPermissions: requestNotifications() called with options: %@", options];
#if __has_include("RTKRNPermissionHandlerNotifications.h")
  RTKRNPermissionHandlerNotifications *handler = [RTKRNPermissionHandlerNotifications new];
  NSString *lockId = [self lockHandler:(id<RTKRNPermissionHandler>)handler];

  [handler requestWithResolver:^(RNPermissionStatus status, NSDictionary * _Nonnull settings) {
    [RTKLogger debug:@"RTKRNPermissions: requestNotifications() resolved with status: %@", [self stringForStatus:status]];
    resolve(@{ @"status": [self stringForStatus:status], @"settings": settings });
    [self unlockHandler:lockId];
  } rejecter:^(NSError * _Nonnull error) {
    [RTKLogger error:@"RTKRNPermissions: requestNotifications() failed with error: %@", error.localizedDescription];
    reject([NSString stringWithFormat:@"%ld", (long)error.code], error.localizedDescription, error);
    [self unlockHandler:lockId];
  } options:options];
#else
  [RTKLogger error:@"RTKRNPermissions: Notifications permission pod is missing"];
  reject(@"notifications_pod_missing", @"Notifications permission pod is missing", nil);
#endif
}

RCT_REMAP_METHOD(openLimitedPhotoLibraryPicker,
                 openLimitedPhotoLibraryPickerWithResolver:(RCTPromiseResolveBlock)resolve
                 rejecter:(RCTPromiseRejectBlock)reject) {
  [RTKLogger debug:@"RTKRNPermissions: openLimitedPhotoLibraryPicker() called"];
    RTKRNPermissionHandlerPhotoLibrary *handler = [RTKRNPermissionHandlerPhotoLibrary new];
  [handler openLimitedPhotoLibraryPickerWithResolver:resolve rejecter:reject];
}

RCT_REMAP_METHOD(checkLocationAccuracy,
                 checkLocationAccuracyWithResolver:(RCTPromiseResolveBlock)resolve
                 rejecter:(RCTPromiseRejectBlock)reject) {
#if __has_include("RNPermissionHandlerLocationAccuracy.h")
  [self checkUsageDescriptionKeys:[RNPermissionHandlerLocationAccuracy usageDescriptionKeys]];

  RNPermissionHandlerLocationAccuracy *handler = [RNPermissionHandlerLocationAccuracy new];
  [handler checkWithResolver:resolve rejecter:reject];
#else
  reject(@"location_accuracy_pod_missing", @"LocationAccuracy permission pod is missing", nil);
#endif
}

RCT_REMAP_METHOD(requestLocationAccuracy,
                 requestLocationAccuracyWithPurposeKey:(NSString * _Nonnull)purposeKey
                 resolver:(RCTPromiseResolveBlock)resolve
                 rejecter:(RCTPromiseRejectBlock)reject) {
#if __has_include("RNPermissionHandlerLocationAccuracy.h")
  [self checkUsageDescriptionKeys:[RNPermissionHandlerLocationAccuracy usageDescriptionKeys]];

  RNPermissionHandlerLocationAccuracy *handler = [RNPermissionHandlerLocationAccuracy new];
  [handler requestWithPurposeKey:purposeKey resolver:resolve rejecter:reject];
#else
  reject(@"location_accuracy_pod_missing", @"LocationAccuracy permission pod is missing", nil);
#endif
}

@end
