/*
 * 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.
 */

#import <TargetConditionals.h>
#if TARGET_OS_IOS || TARGET_OS_TV

#import "FirebaseCore/Extension/FirebaseCoreInternal.h"

#import "FirebaseInAppMessaging/Sources/FIRCore+InAppMessaging.h"
#import "FirebaseInAppMessaging/Sources/Private/Analytics/FIRIAMClearcutLogger.h"
#import "FirebaseInAppMessaging/Sources/Private/Runtime/FIRIAMFetchFlow.h"
#import "FirebaseInAppMessaging/Sources/Private/Runtime/FIRIAMRuntimeManager.h"

@implementation FIRIAMFetchSetting
@end

// the notification message to say that the fetch flow is done
NSString *const kFIRIAMFetchIsDoneNotification = @"FIRIAMFetchIsDoneNotification";

@interface FIRIAMFetchFlow ()
@property(nonatomic) id<FIRIAMTimeFetcher> timeFetcher;
@property(nonatomic) NSTimeInterval lastFetchTime;
@property(nonatomic, nonnull, readonly) FIRIAMFetchSetting *setting;
@property(nonatomic, nonnull, readonly) FIRIAMMessageClientCache *messageCache;
@property(nonatomic) id<FIRIAMMessageFetcher> messageFetcher;
@property(nonatomic, nonnull, readonly) id<FIRIAMBookKeeper> fetchBookKeeper;
@property(nonatomic, nonnull, readonly) FIRIAMActivityLogger *activityLogger;
@property(nonatomic, nonnull, readonly) id<FIRIAMAnalyticsEventLogger> analyticsEventLogger;
@property(nonatomic, nonnull, readonly) FIRIAMSDKModeManager *sdkModeManager;
@property(nonatomic, nonnull, readonly) FIRIAMDisplayExecutor *displayExecutor;

@end

@implementation FIRIAMFetchFlow
- (instancetype)initWithSetting:(FIRIAMFetchSetting *)setting
                   messageCache:(FIRIAMMessageClientCache *)cache
                 messageFetcher:(id<FIRIAMMessageFetcher>)messageFetcher
                    timeFetcher:(id<FIRIAMTimeFetcher>)timeFetcher
                     bookKeeper:(id<FIRIAMBookKeeper>)fetchBookKeeper
                 activityLogger:(FIRIAMActivityLogger *)activityLogger
           analyticsEventLogger:(id<FIRIAMAnalyticsEventLogger>)analyticsEventLogger
           FIRIAMSDKModeManager:(FIRIAMSDKModeManager *)sdkModeManager
                displayExecutor:(FIRIAMDisplayExecutor *)displayExecutor {
  if (self = [super init]) {
    _timeFetcher = timeFetcher;
    _lastFetchTime = [fetchBookKeeper lastFetchTime];
    _setting = setting;
    _messageCache = cache;
    _messageFetcher = messageFetcher;
    _fetchBookKeeper = fetchBookKeeper;
    _activityLogger = activityLogger;
    _analyticsEventLogger = analyticsEventLogger;
    _sdkModeManager = sdkModeManager;
    _displayExecutor = displayExecutor;
  }
  return self;
}

- (FIRIAMAnalyticsLogEventType)fetchErrorToLogEventType:(NSError *)error {
  if ([error.domain isEqual:NSURLErrorDomain]) {
    if (error.code == NSURLErrorNotConnectedToInternet) {
      return FIRIAMAnalyticsEventFetchAPINetworkError;
    } else {
      // error.code could be a non 2xx status code
      if (error.code > 0) {
        if (error.code >= 400 && error.code < 500) {
          return FIRIAMAnalyticsEventFetchAPIClientError;
        } else {
          if (error.code >= 500 && error.code < 600) {
            return FIRIAMAnalyticsEventFetchAPIServerError;
          }
        }
      }
    }
  }

  return FIRIAMAnalyticsLogEventUnknown;
}

- (void)sendFetchIsDoneNotification {
  [[NSNotificationCenter defaultCenter] postNotificationName:kFIRIAMFetchIsDoneNotification
                                                      object:self];
}

- (void)handleSuccessullyFetchedMessages:(NSArray<FIRIAMMessageDefinition *> *)messagesInResponse
                       withFetchWaitTime:(NSNumber *_Nullable)fetchWaitTime
                      requestImpressions:(NSArray<FIRIAMImpressionRecord *> *)requestImpressions {
  FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM700004", @"%lu messages were fetched successfully.",
              (unsigned long)messagesInResponse.count);

  for (FIRIAMMessageDefinition *next in messagesInResponse) {
    if (next.isTestMessage && self.sdkModeManager.currentMode != FIRIAMSDKModeTesting) {
      FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM700006",
                  @"Seeing test message in fetch response. Turn "
                   "the current instance into a testing instance.");
      [self.sdkModeManager becomeTestingInstance];
    }
  }

  NSArray<NSString *> *responseMessageIDs =
      [messagesInResponse valueForKeyPath:@"renderData.messageID"];
  NSArray<NSString *> *impressionMessageIDs = [requestImpressions valueForKey:@"messageID"];

  // We are going to clear impression records for those IDs that are in both impressionMessageIDs
  // and responseMessageIDs. This is to avoid incorrectly clearing impressions records that come
  // in between the sending the request and receiving the response for the fetch operation.
  // So we are computing intersection between responseMessageIDs and impressionMessageIDs and use
  // that for impression log clearing.
  NSMutableSet *idIntersection = [NSMutableSet setWithArray:responseMessageIDs];
  [idIntersection intersectSet:[NSSet setWithArray:impressionMessageIDs]];

  [self.fetchBookKeeper clearImpressionsWithMessageList:[idIntersection allObjects]];
  [self.messageCache setMessageData:messagesInResponse];

  [self.sdkModeManager registerOneMoreFetch];
  [self.fetchBookKeeper recordNewFetchWithFetchCount:messagesInResponse.count
                              withTimestampInSeconds:[self.timeFetcher currentTimestampInSeconds]
                                   nextFetchWaitTime:fetchWaitTime];
}

- (void)checkAndFetchForInitialAppLaunch:(BOOL)forInitialAppLaunch {
  NSTimeInterval intervalFromLastFetchInSeconds =
      [self.timeFetcher currentTimestampInSeconds] - self.fetchBookKeeper.lastFetchTime;

  FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM700005",
              @"Interval from last time fetch is %lf seconds", intervalFromLastFetchInSeconds);

  BOOL fetchIsAllowedNow = NO;

  if (intervalFromLastFetchInSeconds >= self.fetchBookKeeper.nextFetchWaitTime) {
    // it's enough wait time interval from last fetch.
    fetchIsAllowedNow = YES;
  } else {
    FIRIAMSDKMode sdkMode = [self.sdkModeManager currentMode];
    if (sdkMode == FIRIAMSDKModeNewlyInstalled || sdkMode == FIRIAMSDKModeTesting) {
      FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM700007",
                  @"OK to fetch due to current SDK mode being %@",
                  FIRIAMDescriptonStringForSDKMode(sdkMode));
      fetchIsAllowedNow = YES;
    } else {
      FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM700008",
                  @"Interval from last time fetch is %lf seconds, smaller than fetch wait time %lf",
                  intervalFromLastFetchInSeconds, self.fetchBookKeeper.nextFetchWaitTime);
    }
  }

  if (fetchIsAllowedNow) {
    // we are allowed to fetch in-app message from time interval wise

    FIRIAMActivityRecord *record =
        [[FIRIAMActivityRecord alloc] initWithActivityType:FIRIAMActivityTypeCheckForFetch
                                              isSuccessful:YES
                                                withDetail:@"OK to do a fetch"
                                                 timestamp:nil];
    [self.activityLogger addLogRecord:record];

    NSArray<FIRIAMImpressionRecord *> *impressions = [self.fetchBookKeeper getImpressions];
    FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM700001", @"Go ahead to fetch messages");

    NSTimeInterval fetchStartTime = [[NSDate date] timeIntervalSince1970];

    [self.messageFetcher
        fetchMessagesWithImpressionList:impressions
                         withCompletion:^(NSArray<FIRIAMMessageDefinition *> *_Nullable messages,
                                          NSNumber *_Nullable nextFetchWaitTime,
                                          NSInteger discardedMessageCount,
                                          NSError *_Nullable error) {
                           if (error) {
                             FIRLogWarning(kFIRLoggerInAppMessaging, @"I-IAM700002",
                                           @"Error happened during message fetching %@", error);

                             FIRIAMAnalyticsLogEventType eventType =
                                 [self fetchErrorToLogEventType:error];

                             [self.analyticsEventLogger logAnalyticsEventForType:eventType
                                                                   forCampaignID:@"all"
                                                                withCampaignName:@"all"
                                                                   eventTimeInMs:nil
                                                                      completion:^(BOOL success){
                                                                          // nothing to do
                                                                      }];

                             FIRIAMActivityRecord *record = [[FIRIAMActivityRecord alloc]
                                 initWithActivityType:FIRIAMActivityTypeFetchMessage
                                         isSuccessful:NO
                                           withDetail:error.description
                                            timestamp:nil];
                             [self.activityLogger addLogRecord:record];
                           } else {
                             double fetchOperationLatencyInMills =
                                 ([[NSDate date] timeIntervalSince1970] - fetchStartTime) * 1000;
                             NSString *impressionListString =
                                 [impressions componentsJoinedByString:@","];
                             NSString *activityLogDetail = @"";

                             if (discardedMessageCount > 0) {
                               activityLogDetail = [NSString
                                   stringWithFormat:
                                       @"%lu messages fetched with impression list as [%@]"
                                        " and %lu messages are discarded due to data being "
                                        "invalid. It took"
                                        " %lf milliseconds",
                                       (unsigned long)messages.count, impressionListString,
                                       (unsigned long)discardedMessageCount,
                                       fetchOperationLatencyInMills];
                             } else {
                               activityLogDetail = [NSString
                                   stringWithFormat:
                                       @"%lu messages fetched with impression list as [%@]. It took"
                                        " %lf milliseconds",
                                       (unsigned long)messages.count, impressionListString,
                                       fetchOperationLatencyInMills];
                             }

                             FIRIAMActivityRecord *record = [[FIRIAMActivityRecord alloc]
                                 initWithActivityType:FIRIAMActivityTypeFetchMessage
                                         isSuccessful:YES
                                           withDetail:activityLogDetail
                                            timestamp:nil];
                             [self.activityLogger addLogRecord:record];

                             // Now handle the fetched messages.
                             [self handleSuccessullyFetchedMessages:messages
                                                  withFetchWaitTime:nextFetchWaitTime
                                                 requestImpressions:impressions];

                             if (forInitialAppLaunch) {
                               [self checkForAppLaunchMessage];
                             }
                           }
                           // Send this regardless whether fetch is successful or not.
                           [self sendFetchIsDoneNotification];
                         }];

  } else {
    FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM700003",
                @"Only %lf seconds from last fetch time. No action.",
                intervalFromLastFetchInSeconds);
    // for no fetch case, we still send out the notification so that and display flow can continue
    // from here.
    [self sendFetchIsDoneNotification];
    FIRIAMActivityRecord *record =
        [[FIRIAMActivityRecord alloc] initWithActivityType:FIRIAMActivityTypeCheckForFetch
                                              isSuccessful:NO
                                                withDetail:@"Abort due to check time interval "
                                                            "not reached yet"
                                                 timestamp:nil];
    [self.activityLogger addLogRecord:record];
  }
}

- (void)checkForAppLaunchMessage {
  [self.displayExecutor checkAndDisplayNextAppLaunchMessage];
}
@end

#endif  // TARGET_OS_IOS || TARGET_OS_TV
