/*
 * Copyright 2017 Google
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file 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.
 */
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 ||                                          \
    __MAC_OS_X_VERSION_MAX_ALLOWED >= __MAC_10_14 || __TV_OS_VERSION_MAX_ALLOWED >= __TV_10_0 || \
    __WATCH_OS_VERSION_MAX_ALLOWED >= __WATCHOS_3_0 || TARGET_OS_MACCATALYST
#import <UserNotifications/UserNotifications.h>
#endif
#import <OCMock/OCMock.h>
#import <XCTest/XCTest.h>

#import "FirebaseMessaging/Sources/FIRMessagingContextManagerService.h"

static NSString *const kBody = @"Save 20% off!";
static NSString *const kTitle = @"Sparky WFH";
static NSString *const kSoundName = @"default";
static NSString *const kAction = @"open";
static NSString *const kUserInfoKey1 = @"level";
static NSString *const kUserInfoKey2 = @"isPayUser";
static NSString *const kUserInfoValue1 = @"5";
static NSString *const kUserInfoValue2 = @"Yes";
static NSString *const kMessageIdentifierKey = @"gcm.message_id";
static NSString *const kMessageIdentifierValue = @"1584748495200141";

@interface FIRMessagingContextManagerService (ExposedForTest)
+ (void)scheduleiOS10LocalNotificationForMessage:(NSDictionary *)message atDate:(NSDate *)date;
+ (UNMutableNotificationContent *)contentFromContextualMessage:(NSDictionary *)message;
@end

API_AVAILABLE(macos(10.14))
@interface FIRMessagingContextManagerServiceTest : XCTestCase

@property(nonatomic, readwrite, strong) NSDateFormatter *dateFormatter;
@property(nonatomic, readwrite, strong) NSMutableArray *scheduledLocalNotifications;
@property(nonatomic, readwrite, strong)
    NSMutableArray<UNNotificationRequest *> *requests API_AVAILABLE(ios(10.0), macos(10.4));

@end

@implementation FIRMessagingContextManagerServiceTest

- (void)setUp {
  [super setUp];
  self.dateFormatter = [[NSDateFormatter alloc] init];
  self.dateFormatter.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"];
  [self.dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
  self.scheduledLocalNotifications = [[NSMutableArray alloc] init];
  if (@available(macOS 10.14, iOS 10.0, watchOS 3.0, tvOS 10.0, *)) {
    self.requests = [[NSMutableArray alloc] init];
  }

  [self mockSchedulingLocalNotifications];
}

- (void)tearDown {
  [super tearDown];
}

/**
 *  Test invalid context manager message, missing lt_start string.
 */
- (void)testInvalidContextManagerMessage_missingStartTime {
  NSDictionary *message = @{
    @"hello" : @"world",
  };
  XCTAssertFalse([FIRMessagingContextManagerService isContextManagerMessage:message]);
}

/**
 *  Test valid context manager message.
 */
- (void)testValidContextManagerMessage {
  NSDictionary *message = @{
    kFIRMessagingContextManagerLocalTimeStart : @"2015-12-12 00:00:00",
    @"hello" : @"world",
  };
  XCTAssertTrue([FIRMessagingContextManagerService isContextManagerMessage:message]);
}

/**
 *  Context Manager message with future start date should be successfully scheduled.
 */
- (void)testMessageWithFutureStartTime {
  // way into the future
  NSString *startTimeString = [self.dateFormatter stringFromDate:[NSDate distantFuture]];
  NSDictionary *message = @{
    kFIRMessagingContextManagerLocalTimeStart : startTimeString,
    kFIRMessagingContextManagerBodyKey : kBody,
    kMessageIdentifierKey : kMessageIdentifierValue,
    kUserInfoKey1 : kUserInfoValue1,
    kUserInfoKey2 : kUserInfoValue2
  };
  XCTAssertTrue([FIRMessagingContextManagerService handleContextManagerMessage:message]);

  if (@available(macOS 10.14, iOS 10.0, watchOS 3.0, tvOS 10.0, *)) {
    XCTAssertEqual(self.requests.count, 1);
    UNNotificationRequest *request = self.requests.firstObject;
    XCTAssertEqualObjects(request.identifier, kMessageIdentifierValue);
#if TARGET_OS_IOS || TARGET_OS_WATCH || TARGET_OS_OSX
    XCTAssertEqualObjects(request.content.body, kBody);
    XCTAssertEqualObjects(request.content.userInfo[kUserInfoKey1], kUserInfoValue1);
    XCTAssertEqualObjects(request.content.userInfo[kUserInfoKey2], kUserInfoValue2);
#endif
    return;
  }

#if TARGET_OS_IOS
  XCTAssertEqual(self.scheduledLocalNotifications.count, 1);
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
  UILocalNotification *notification = self.scheduledLocalNotifications.firstObject;
#pragma clang diagnostic pop
  NSDate *date = [self.dateFormatter dateFromString:startTimeString];
  XCTAssertEqual([notification.fireDate compare:date], NSOrderedSame);
  XCTAssertEqualObjects(notification.alertBody, kBody);
  XCTAssertEqualObjects(notification.userInfo[kUserInfoKey1], kUserInfoValue1);
  XCTAssertEqualObjects(notification.userInfo[kUserInfoKey2], kUserInfoValue2);
#endif
}

/**
 *  Context Manager message with past end date should not be scheduled.
 */
- (void)testMessageWithPastEndTime {
#if TARGET_OS_IOS
  NSString *startTimeString = @"2010-01-12 12:00:00";  // way into the past
  NSString *endTimeString = @"2011-01-12 12:00:00";    // way into the past
  NSDictionary *message = @{
    kFIRMessagingContextManagerLocalTimeStart : startTimeString,
    kFIRMessagingContextManagerLocalTimeEnd : endTimeString,
    kFIRMessagingContextManagerBodyKey : kBody,
    kMessageIdentifierKey : kMessageIdentifierValue,
    @"hello" : @"world"
  };

  XCTAssertTrue([FIRMessagingContextManagerService handleContextManagerMessage:message]);
  if (@available(macOS 10.14, iOS 10.0, watchOS 3.0, tvOS 10.0, *)) {
    XCTAssertEqual(self.requests.count, 0);
    return;
  }
  XCTAssertEqual(self.scheduledLocalNotifications.count, 0);
#endif
}

/**
 *  Context Manager message with past start and future end date should be successfully
 *  scheduled.
 */
- (void)testMessageWithPastStartAndFutureEndTime {
#if TARGET_OS_IOS
  NSDate *startDate = [NSDate dateWithTimeIntervalSinceNow:-1000];  // past
  NSDate *endDate = [NSDate dateWithTimeIntervalSinceNow:1000];     // future
  NSString *startTimeString = [self.dateFormatter stringFromDate:startDate];
  NSString *endTimeString = [self.dateFormatter stringFromDate:endDate];

  NSDictionary *message = @{
    kFIRMessagingContextManagerLocalTimeStart : startTimeString,
    kFIRMessagingContextManagerLocalTimeEnd : endTimeString,
    kFIRMessagingContextManagerBodyKey : kBody,
    kMessageIdentifierKey : kMessageIdentifierValue,
    kUserInfoKey1 : kUserInfoValue1,
    kUserInfoKey2 : kUserInfoValue2
  };

  XCTAssertTrue([FIRMessagingContextManagerService handleContextManagerMessage:message]);

  if (@available(macOS 10.14, iOS 10.0, watchOS 3.0, tvOS 10.0, *)) {
    XCTAssertEqual(self.requests.count, 1);
    UNNotificationRequest *request = self.requests.firstObject;
    XCTAssertEqualObjects(request.identifier, kMessageIdentifierValue);
    XCTAssertEqualObjects(request.content.body, kBody);
    XCTAssertEqualObjects(request.content.userInfo[kUserInfoKey1], kUserInfoValue1);
    XCTAssertEqualObjects(request.content.userInfo[kUserInfoKey2], kUserInfoValue2);
    return;
  }
  XCTAssertEqual(self.scheduledLocalNotifications.count, 1);
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
  UILocalNotification *notification = [self.scheduledLocalNotifications firstObject];
#pragma clang diagnostic pop
  // schedule notification after start date
  XCTAssertEqual([notification.fireDate compare:startDate], NSOrderedDescending);
  // schedule notification after end date
  XCTAssertEqual([notification.fireDate compare:endDate], NSOrderedAscending);
  XCTAssertEqualObjects(notification.userInfo[kUserInfoKey1], kUserInfoValue1);
  XCTAssertEqualObjects(notification.userInfo[kUserInfoKey2], kUserInfoValue2);
#endif
}

/**
 *  Test correctly parsing user data in local notifications.
 */
- (void)testTimedNotificationsUserInfo {
#if TARGET_OS_IOS
  // way into the future
  NSString *startTimeString = [self.dateFormatter stringFromDate:[NSDate distantFuture]];

  NSDictionary *message = @{
    kFIRMessagingContextManagerLocalTimeStart : startTimeString,
    kFIRMessagingContextManagerBodyKey : kBody,
    kMessageIdentifierKey : kMessageIdentifierValue,
    kUserInfoKey1 : kUserInfoValue1,
    kUserInfoKey2 : kUserInfoValue2
  };

  XCTAssertTrue([FIRMessagingContextManagerService handleContextManagerMessage:message]);
  if (@available(macOS 10.14, iOS 10.0, watchOS 3.0, tvOS 10.0, *)) {
    XCTAssertEqual(self.requests.count, 1);
    UNNotificationRequest *request = self.requests.firstObject;
    XCTAssertEqualObjects(request.identifier, kMessageIdentifierValue);
    XCTAssertEqualObjects(request.content.body, kBody);
    XCTAssertEqualObjects(request.content.userInfo[kUserInfoKey1], kUserInfoValue1);
    XCTAssertEqualObjects(request.content.userInfo[kUserInfoKey2], kUserInfoValue2);
    return;
  }
  XCTAssertEqual(self.scheduledLocalNotifications.count, 1);
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
  UILocalNotification *notification = [self.scheduledLocalNotifications firstObject];
#pragma clang diagnostic pop
  XCTAssertEqualObjects(notification.userInfo[kUserInfoKey1], kUserInfoValue1);
  XCTAssertEqualObjects(notification.userInfo[kUserInfoKey2], kUserInfoValue2);
#endif
}

#pragma mark - Private Helpers

- (void)mockSchedulingLocalNotifications {
  if (@available(macOS 10.14, iOS 10.0, watchOS 3.0, tvOS 10.0, *)) {
    id mockNotificationCenter =
        OCMPartialMock([UNUserNotificationCenter currentNotificationCenter]);
    __block UNNotificationRequest *request;
    [[[mockNotificationCenter stub] andDo:^(NSInvocation *invocation) {
      [self.requests addObject:request];
    }] addNotificationRequest:[OCMArg checkWithBlock:^BOOL(id obj) {
         if ([obj isKindOfClass:[UNNotificationRequest class]]) {
           request = obj;
           [self.requests addObject:request];
           return YES;
         }
         return NO;
       }]
        withCompletionHandler:^(NSError *_Nullable error){
        }];
    return;
  }
#if TARGET_OS_IOS
  id mockApplication = OCMPartialMock([UIApplication sharedApplication]);
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
  __block UILocalNotification *notificationToSchedule;
  [[[mockApplication stub] andDo:^(NSInvocation *invocation) {
    // Mock scheduling a notification
    if (notificationToSchedule) {
      [self.scheduledLocalNotifications addObject:notificationToSchedule];
    }
  }] scheduleLocalNotification:[OCMArg checkWithBlock:^BOOL(id obj) {
       if ([obj isKindOfClass:[UILocalNotification class]]) {
         notificationToSchedule = obj;
         return YES;
       }
       return NO;
     }]];
#pragma clang diagnostic pop
#endif
}

- (void)testScheduleiOS10LocalNotification {
  if (@available(macOS 10.14, iOS 10.0, watchOS 3.0, tvOS 10.0, *)) {
    id mockContextManagerService = OCMClassMock([FIRMessagingContextManagerService class]);
    NSDictionary *message = @{};

    [FIRMessagingContextManagerService scheduleiOS10LocalNotificationForMessage:message
                                                                         atDate:[NSDate date]];
    OCMVerify([mockContextManagerService contentFromContextualMessage:message]);
    [mockContextManagerService stopMocking];
  }
}

- (void)testContentFromConetxtualMessage {
  if (@available(macOS 10.14, iOS 10.0, watchOS 3.0, tvOS 10.0, *)) {
    NSDictionary *message = @{
      @"aps" : @{@"content-available" : @1},
      @"gcm.message_id" : @1623702615599207,
      @"gcm.n.e" : @1,
      @"gcm.notification.badge" : @1,
      @"gcm.notification.body" : kBody,
      @"gcm.notification.image" :
          @"https://firebasestorage.googleapis.com/v0/b/fir-ios-app-extensions.appspot.com/o/"
          @"sparkyWFH.png?alt=media&token=f4dc1533-4d80-4ed6-9870-8df528593157",
      @"gcm.notification.mutable_content" : @1,
      @"gcm.notification.sound" : kSoundName,
      @"gcm.notification.sound2" : kSoundName,
      @"gcm.notification.title" : kTitle,
      // This field is not popped out from console
      // Manual add here to test unit test
      @"gcm.notification.click_action" : kAction,
      @"gcms" : @"gcm.gmsproc.cm",
      @"google.c.a.c_id" : @2159728303499680621,
      @"google.c.a.c_l" : @"test local send with sound",
      @"google.c.a.e" : @1,
      @"google.c.a.ts" : @1623753000,
      @"google.c.a.udt" : @1,
      @"google.c.cm.lt_end" : @"2021-07-13 10:30:00",
      @"google.c.cm.lt_start" : @"2021-06-15 10:30:00",
      @"google.c.sender.id" : @449451107265,
    };
    UNMutableNotificationContent *content =
        [FIRMessagingContextManagerService contentFromContextualMessage:message];
    XCTAssertEqualObjects(content.badge, @1);

#if TARGET_OS_IOS || TARGET_OS_OSX || TARGET_OS_WATCH
    XCTAssertEqualObjects(content.body, kBody);
    XCTAssertEqualObjects(content.title, kTitle);
#if !TARGET_OS_WATCH
    XCTAssertEqualObjects(content.sound, [UNNotificationSound soundNamed:kSoundName]);
#else   // !TARGET_OS_WATCH
    XCTAssertEqualObjects(content.sound, [UNNotificationSound defaultSound]);
#endif  // !TARGET_OS_WATCH
    XCTAssertEqualObjects(content.categoryIdentifier, kAction);
    NSDictionary *userInfo = @{
      @"gcm.message_id" : @1623702615599207,
      @"gcm.n.e" : @1,
      @"gcm.notification.badge" : @1,
      @"gcm.notification.body" : kBody,
      @"gcm.notification.image" :
          @"https://firebasestorage.googleapis.com/v0/b/fir-ios-app-extensions.appspot.com/o/"
          @"sparkyWFH.png?alt=media&token=f4dc1533-4d80-4ed6-9870-8df528593157",
      @"gcm.notification.mutable_content" : @1,
      @"gcm.notification.sound" : kSoundName,
      @"gcm.notification.sound2" : kSoundName,
      @"gcm.notification.title" : kTitle,
      // This field is not popped out from console
      // Manual add here to test unit test
      @"gcm.notification.click_action" : kAction,
      @"gcms" : @"gcm.gmsproc.cm",
      @"google.c.a.c_id" : @2159728303499680621,
      @"google.c.a.c_l" : @"test local send with sound",
      @"google.c.a.e" : @1,
      @"google.c.a.ts" : @1623753000,
      @"google.c.a.udt" : @1,
      @"google.c.sender.id" : @449451107265
    };
    XCTAssertEqualObjects(content.userInfo, userInfo);
#endif  // TARGET_OS_IOS || TARGET_OS_OSX || TARGET_OS_WATCH
  }
}

@end
