/**
 * Copyright (c) 2016-present Invertase Limited & Contributors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this library except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

#import "NotifeeCore+UNUserNotificationCenter.h"

#import "NotifeeCore.h"
#import "NotifeeCoreDelegateHolder.h"
#import "NotifeeCoreUtil.h"

typedef void (^NotifeeCorePresentationCompletionHandler)(UNNotificationPresentationOptions options);
typedef void (^NotifeeCoreVoidCompletionHandler)(void);

@interface NotifeeCore (RollingTimestampTopUp)
+ (void)topUpRollingTimestampTriggersWithCompletion:(void (^)(NSError *error))completion;
@end

@interface NotifeeCoreUNUserNotificationCenter ()
- (void)refreshOriginalDelegateSelectorFlags;
- (void)rechainUserNotificationCenterDelegate;
@end

static NotifeeCorePresentationCompletionHandler NotifeeCoreOneShotPresentationCompletionHandler(
    NotifeeCorePresentationCompletionHandler completionHandler) {
  NSObject *completionLock = [NSObject new];
  __block BOOL completionCalled = NO;

  NotifeeCorePresentationCompletionHandler oneShotCompletionHandler =
      ^(UNNotificationPresentationOptions options) {
        BOOL shouldCallCompletion = NO;
        @synchronized(completionLock) {
          if (!completionCalled) {
            completionCalled = YES;
            shouldCallCompletion = YES;
          }
        }

        if (shouldCallCompletion && completionHandler != nil) {
          completionHandler(options);
        }
      };

  return [oneShotCompletionHandler copy];
}

static NotifeeCoreVoidCompletionHandler NotifeeCoreOneShotVoidCompletionHandler(
    NotifeeCoreVoidCompletionHandler completionHandler) {
  NSObject *completionLock = [NSObject new];
  __block BOOL completionCalled = NO;

  NotifeeCoreVoidCompletionHandler oneShotCompletionHandler = ^{
    BOOL shouldCallCompletion = NO;
    @synchronized(completionLock) {
      if (!completionCalled) {
        completionCalled = YES;
        shouldCallCompletion = YES;
      }
    }

    if (shouldCallCompletion && completionHandler != nil) {
      completionHandler();
    }
  };

  return [oneShotCompletionHandler copy];
}

@implementation NotifeeCoreUNUserNotificationCenter

struct {
  unsigned int willPresentNotification : 1;
  unsigned int didReceiveNotificationResponse : 1;
  unsigned int openSettingsForNotification : 1;
} originalUNCDelegateRespondsTo;

+ (instancetype)instance {
  static dispatch_once_t once;
  __strong static NotifeeCoreUNUserNotificationCenter *sharedInstance;
  dispatch_once(&once, ^{
    sharedInstance = [[NotifeeCoreUNUserNotificationCenter alloc] init];
    sharedInstance.initialNotification = nil;
    sharedInstance.initialNotificationGathered = false;
    sharedInstance.initialNotificationBlock = nil;
    sharedInstance.shouldHandleRemoteNotifications = YES;
  });
  return sharedInstance;
}

- (void)observe {
  [self rechainUserNotificationCenterDelegate];
}

- (void)refreshOriginalDelegateSelectorFlags {
  id<UNUserNotificationCenterDelegate> originalDelegate = self.originalDelegate;

  originalUNCDelegateRespondsTo.openSettingsForNotification =
      originalDelegate != nil &&
      [originalDelegate respondsToSelector:@selector(userNotificationCenter:
                                                openSettingsForNotification:)];
  originalUNCDelegateRespondsTo.willPresentNotification =
      originalDelegate != nil &&
      [originalDelegate respondsToSelector:@selector
                        (userNotificationCenter:willPresentNotification:withCompletionHandler:)];
  originalUNCDelegateRespondsTo.didReceiveNotificationResponse =
      originalDelegate != nil &&
      [originalDelegate
          respondsToSelector:@selector(userNotificationCenter:
                                 didReceiveNotificationResponse:withCompletionHandler:)];
}

- (void)rechainUserNotificationCenterDelegate {
  @synchronized(self) {
    UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
    id<UNUserNotificationCenterDelegate> currentDelegate = center.delegate;

    if (currentDelegate == self) {
      [self refreshOriginalDelegateSelectorFlags];
      return;
    }

    self.originalDelegate = currentDelegate;
    [self refreshOriginalDelegateSelectorFlags];
    center.delegate = self;
  }
}

- (void)markInitialNotificationGathered {
  _initialNotificationGathered = YES;
}

- (nullable NSDictionary *)getInitialNotification {
  if (_initialNotificationGathered && _initialNotificationBlock != nil) {
    // copying initial notification
    if (_initialNotification != nil && _notificationOpenedAppID != nil) {
      NSDictionary *initialNotificationCopy = [_initialNotification copy];
      _initialNotification = nil;
      _initialNotificationBlock(nil, initialNotificationCopy);
    } else {
      _initialNotificationBlock(nil, nil);
    }

    _initialNotificationBlock = nil;
  }

  return nil;
}

- (BOOL)isRollingTimestampNotificationRequest:(UNNotificationRequest *)request {
  if ([NotifeeCoreUtil isRollingInternalNotificationId:request.identifier]) {
    return YES;
  }

  NSDictionary *trigger = request.content.userInfo[kNotifeeUserInfoTrigger];
  return [NotifeeCoreUtil isRollingTimestampTrigger:trigger];
}

- (void)topUpRollingTimestampTriggersForLifecycleEvent:(NSString *)eventName {
  [NotifeeCore topUpRollingTimestampTriggersWithCompletion:^(NSError *error) {
    if (error != nil) {
      NSLog(@"NotifeeCore: Failed to top up rolling timestamp triggers after %@: %@", eventName,
            error);
    }
  }];
}

#pragma mark - UNUserNotificationCenter Delegate Methods

// The method will be called on the delegate only if the application is in the
// foreground. If the the handler is not called in a timely manner then the
// notification will not be presented. The application can choose to have the
// notification presented as a sound, badge, alert and/or in the notification
// list. This decision should be based on whether the information in the
// notification is otherwise visible to the user.
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
       willPresentNotification:(UNNotification *)notification
         withCompletionHandler:
             (void (^)(UNNotificationPresentationOptions options))completionHandler {
  NSDictionary *notifeeNotification =
      notification.request.content.userInfo[kNotifeeUserInfoNotification];
  BOOL isRollingTimestampNotification =
      [self isRollingTimestampNotificationRequest:notification.request];

  // we only care about notifications created through notifee
  if (notifeeNotification != nil) {
    UNNotificationPresentationOptions presentationOptions = UNNotificationPresentationOptionNone;
    NSDictionary *foregroundPresentationOptions =
        notifeeNotification[@"ios"][@"foregroundPresentationOptions"];

    BOOL alert = [foregroundPresentationOptions[@"alert"] boolValue];
    BOOL badge = [foregroundPresentationOptions[@"badge"] boolValue];
    BOOL sound = [foregroundPresentationOptions[@"sound"] boolValue];
    BOOL banner = [foregroundPresentationOptions[@"banner"] boolValue];
    BOOL list = [foregroundPresentationOptions[@"list"] boolValue];

    if (badge) {
      presentationOptions |= UNNotificationPresentationOptionBadge;
    }

    if (sound) {
      presentationOptions |= UNNotificationPresentationOptionSound;
    }

    // if list or banner is true, ignore alert property
    if (banner || list) {
      if (banner) {
        presentationOptions |= UNNotificationPresentationOptionBanner;
      }

      if (list) {
        presentationOptions |= UNNotificationPresentationOptionList;
      }
    } else if (alert) {
      // TODO: remove alert once it has been fully removed from the notifee API
      presentationOptions |=
          UNNotificationPresentationOptionBanner | UNNotificationPresentationOptionList;
    }

    // Emit DELIVERED for every Notifee-owned notification presented in foreground.
    // Previously guarded by `notifeeTrigger != nil`, which suppressed the event for
    // displayNotification() calls — only trigger notifications got DELIVERED in foreground.
    // Android emits DELIVERED unconditionally; this aligns iOS behavior.
    [[NotifeeCoreDelegateHolder instance] didReceiveNotifeeCoreEvent:@{
      @"type" : @(NotifeeCoreEventTypeDelivered),
      @"detail" : @{
        @"notification" : notifeeNotification,
      }
    }];

    completionHandler(presentationOptions);
    if (isRollingTimestampNotification) {
      [self topUpRollingTimestampTriggersForLifecycleEvent:@"foreground delivery"];
    }

  } else if (_originalDelegate != nil && originalUNCDelegateRespondsTo.willPresentNotification) {
    NotifeeCorePresentationCompletionHandler oneShotCompletionHandler =
        NotifeeCoreOneShotPresentationCompletionHandler(completionHandler);
    [_originalDelegate userNotificationCenter:center
                      willPresentNotification:notification
                        withCompletionHandler:oneShotCompletionHandler];
  } else {
    // No original delegate captured and the notification is not Notifee-owned.
    // Returning UNNotificationPresentationOptionNone would silently drop the
    // notification (no banner, no sound, no badge, no Notification Center entry),
    // making remote pushes invisible to users in apps that don't install a
    // competing UNUserNotificationCenterDelegate (e.g. apps without
    // @react-native-firebase/messaging). Fall back to the platform default
    // presentation options instead so the system shows the notification as it
    // would if Notifee had not installed a delegate at all.
    completionHandler(UNNotificationPresentationOptionBanner |
                      UNNotificationPresentationOptionSound | UNNotificationPresentationOptionList |
                      UNNotificationPresentationOptionBadge);
  }
}

// The method will be called when the user responded to the notification by
// opening the application, dismissing the notification or choosing a
// UNNotificationAction. The delegate must be set before the application returns
// from application:didFinishLaunchingWithOptions:.
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
    didReceiveNotificationResponse:(UNNotificationResponse *)response
             withCompletionHandler:(void (^)(void))completionHandler {
  NSDictionary *notifeeNotification =
      response.notification.request.content.userInfo[kNotifeeUserInfoNotification];
  BOOL isRollingTimestampNotification =
      [self isRollingTimestampNotificationRequest:response.notification.request];

  _notificationOpenedAppID = notifeeNotification[@"id"];

  // handle notification outside of notifee
  if (notifeeNotification == nil) {
    if (!self.shouldHandleRemoteNotifications) {
      // Flag OFF: always forward to original delegate, never parse as Notifee
      if (_originalDelegate != nil &&
          originalUNCDelegateRespondsTo.didReceiveNotificationResponse) {
        NotifeeCoreVoidCompletionHandler oneShotCompletionHandler =
            NotifeeCoreOneShotVoidCompletionHandler(completionHandler);
        [_originalDelegate userNotificationCenter:center
                   didReceiveNotificationResponse:response
                            withCompletionHandler:oneShotCompletionHandler];
      } else {
        completionHandler();
      }
      return;
    }
    // Flag ON (default): existing behavior
    if (_originalDelegate != nil && originalUNCDelegateRespondsTo.didReceiveNotificationResponse) {
      NotifeeCoreVoidCompletionHandler oneShotCompletionHandler =
          NotifeeCoreOneShotVoidCompletionHandler(completionHandler);
      [_originalDelegate userNotificationCenter:center
                 didReceiveNotificationResponse:response
                          withCompletionHandler:oneShotCompletionHandler];
      return;
    } else {
      notifeeNotification =
          [NotifeeCoreUtil parseUNNotificationRequest:response.notification.request];
    }
  }

  if (notifeeNotification != nil) {
    if ([response.actionIdentifier isEqualToString:UNNotificationDismissActionIdentifier]) {
      // post DISMISSED event, only triggers if notification has a categoryId
      [[NotifeeCoreDelegateHolder instance] didReceiveNotifeeCoreEvent:@{
        @"type" : @(NotifeeCoreEventTypeDismissed),
        @"detail" : @{
          @"notification" : notifeeNotification,
        }
      }];
      completionHandler();
      if (isRollingTimestampNotification) {
        [self topUpRollingTimestampTriggersForLifecycleEvent:@"notification response"];
      }
      return;
    }

    NSNumber *eventType;
    NSMutableDictionary *event = [NSMutableDictionary dictionary];
    NSMutableDictionary *eventDetail = [NSMutableDictionary dictionary];
    NSMutableDictionary *eventDetailPressAction = [NSMutableDictionary dictionary];

    if ([response.actionIdentifier isEqualToString:UNNotificationDefaultActionIdentifier]) {
      eventType = @1;  // PRESS
      // event.detail.pressAction.id
      eventDetailPressAction[@"id"] = @"default";
    } else {
      eventType = @2;  // ACTION_PRESS
      // event.detail.pressAction.id
      eventDetailPressAction[@"id"] = response.actionIdentifier;
    }

    if ([response isKindOfClass:UNTextInputNotificationResponse.class]) {
      // event.detail.input
      eventDetail[@"input"] = [(UNTextInputNotificationResponse *)response userText];
    }

    // event.type
    event[@"type"] = eventType;

    // event.detail.notification
    eventDetail[@"notification"] = notifeeNotification;

    // event.detail.pressAction
    eventDetail[@"pressAction"] = eventDetailPressAction;

    // event.detail
    event[@"detail"] = eventDetail;

    // store notification for getInitialNotification
    _initialNotification = [eventDetail copy];

    [[NotifeeCoreDelegateHolder instance] didReceiveNotifeeCoreEvent:event];

    completionHandler();
    if (isRollingTimestampNotification) {
      [self topUpRollingTimestampTriggersForLifecycleEvent:@"notification response"];
    }
  } else {
    // Defensive: parseUNNotificationRequest: currently never returns nil,
    // but if it did, the completionHandler contract must still be honored.
    completionHandler();
  }
}

- (void)userNotificationCenter:(UNUserNotificationCenter *)center
    openSettingsForNotification:(nullable UNNotification *)notification {
  if (_originalDelegate != nil && originalUNCDelegateRespondsTo.openSettingsForNotification) {
    if (@available(iOS 12.0, macOS 10.14, macCatalyst 13.0, *)) {
      [_originalDelegate userNotificationCenter:center openSettingsForNotification:notification];
    } else {
      // Fallback on earlier versions
    }
  }
}

@end
