/*
 * Copyright 2019 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 "FirebaseMessaging/Sources/Token/FIRMessagingTokenOperation.h"

#import <GoogleUtilities/GULAppEnvironmentUtil.h>
#import "FirebaseCore/Extension/FIRHeartbeatLogger.h"
#import "FirebaseInstallations/Source/Library/Private/FirebaseInstallationsInternal.h"
#import "FirebaseMessaging/Sources/FIRMessagingLogger.h"
#import "FirebaseMessaging/Sources/FIRMessagingUtilities.h"
#import "FirebaseMessaging/Sources/FIRMessaging_Private.h"
#import "FirebaseMessaging/Sources/NSError+FIRMessaging.h"
#import "FirebaseMessaging/Sources/Token/FIRMessagingCheckinPreferences.h"

static const NSInteger kFIRMessagingPlatformVersionIOS = 2;

// Scope parameter that defines the service using the token
static NSString *const kFIRMessagingParamScope = @"X-scope";
// Defines the SDK version
static NSString *const kFIRMessagingParamFCMLibVersion = @"X-cliv";

@interface FIRMessagingTokenOperation () {
  BOOL _isFinished;
  BOOL _isExecuting;
  NSMutableArray<FIRMessagingTokenOperationCompletion> *_completionHandlers;
  FIRMessagingCheckinPreferences *_checkinPreferences;
}

@property(nonatomic, readwrite, strong) NSString *instanceID;

@property(atomic, strong, nullable) NSString *FISAuthToken;

@end

@implementation FIRMessagingTokenOperation

- (instancetype)initWithAction:(FIRMessagingTokenAction)action
           forAuthorizedEntity:(NSString *)authorizedEntity
                         scope:(NSString *)scope
                       options:(NSDictionary<NSString *, NSString *> *)options
            checkinPreferences:(FIRMessagingCheckinPreferences *)checkinPreferences
                    instanceID:(NSString *)instanceID
               heartbeatLogger:(id<FIRHeartbeatLoggerProtocol>)heartbeatLogger {
  self = [super init];
  if (self) {
    _action = action;
    _authorizedEntity = [authorizedEntity copy];
    _scope = [scope copy];
    _options = [options copy];
    _checkinPreferences = checkinPreferences;
    _instanceID = instanceID;
    _completionHandlers = [[NSMutableArray alloc] init];
    _heartbeatLogger = heartbeatLogger;

    _isExecuting = NO;
    _isFinished = NO;
  }
  return self;
}

- (void)dealloc {
  [_completionHandlers removeAllObjects];
}

- (void)addCompletionHandler:(FIRMessagingTokenOperationCompletion)handler {
  [_completionHandlers addObject:[handler copy]];
}

- (BOOL)isAsynchronous {
  return YES;
}

- (BOOL)isExecuting {
  return _isExecuting;
}

- (void)setExecuting:(BOOL)executing {
  [self willChangeValueForKey:@"isExecuting"];
  _isExecuting = executing;
  [self didChangeValueForKey:@"isExecuting"];
}

- (BOOL)isFinished {
  return _isFinished;
}

- (void)setFinished:(BOOL)finished {
  [self willChangeValueForKey:@"isFinished"];
  _isFinished = finished;
  [self didChangeValueForKey:@"isFinished"];
}

- (void)start {
  if (self.isCancelled) {
    [self finishWithResult:FIRMessagingTokenOperationCancelled token:nil error:nil];
    return;
  }

  // Quickly validate whether or not the operation has all it needs to begin
  BOOL checkinfoAvailable = [self.checkinPreferences hasCheckinInfo];
  if (!checkinfoAvailable) {
    FIRMessagingErrorCode errorCode = kFIRMessagingErrorCodeRegistrarFailedToCheckIn;
    [self finishWithResult:FIRMessagingTokenOperationError
                     token:nil
                     error:[NSError messagingErrorWithCode:errorCode
                                             failureReason:
                                                 @"Failed to checkin before token registration."]];
    return;
  }

  [self setExecuting:YES];

  [[FIRInstallations installations]
      authTokenWithCompletion:^(FIRInstallationsAuthTokenResult *_Nullable tokenResult,
                                NSError *_Nullable error) {
        if (tokenResult.authToken.length > 0) {
          self.FISAuthToken = tokenResult.authToken;
          [self performTokenOperation];
        } else {
          [self finishWithResult:FIRMessagingTokenOperationError token:nil error:error];
        }
      }];
}

- (void)finishWithResult:(FIRMessagingTokenOperationResult)result
                   token:(nullable NSString *)token
                   error:(nullable NSError *)error {
  // Add a check to prevent this finish from being called more than once.
  if (self.isFinished) {
    return;
  }
  self.dataTask = nil;
  _result = result;
  for (FIRMessagingTokenOperationCompletion completionHandler in _completionHandlers) {
    completionHandler(result, token, error);
  }

  [self setExecuting:NO];
  [self setFinished:YES];
}

- (void)cancel {
  [super cancel];
  [self.dataTask cancel];
  [self finishWithResult:FIRMessagingTokenOperationCancelled token:nil error:nil];
}

- (void)performTokenOperation {
}

- (NSMutableURLRequest *)tokenRequest {
  NSString *authHeader =
      [FIRMessagingTokenOperation HTTPAuthHeaderFromCheckin:self.checkinPreferences];
  return [[self class] requestWithAuthHeader:authHeader FISAuthToken:self.FISAuthToken];
}

#pragma mark - Request Construction

+ (NSMutableURLRequest *)requestWithAuthHeader:(NSString *)authHeaderString
                                  FISAuthToken:(NSString *)FISAuthToken {
  NSURL *url = [NSURL URLWithString:FIRMessagingTokenRegisterServer()];
  NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];

  // Add HTTP headers
  [request setValue:authHeaderString forHTTPHeaderField:@"Authorization"];
  [request setValue:FIRMessagingAppIdentifier() forHTTPHeaderField:@"app"];
  if (FISAuthToken) {
    [request setValue:FISAuthToken forHTTPHeaderField:@"x-goog-firebase-installations-auth"];
  }
  request.HTTPMethod = @"POST";
  return request;
}

+ (NSMutableArray<NSURLQueryItem *> *)standardQueryItemsWithDeviceID:(NSString *)deviceID
                                                               scope:(NSString *)scope {
  NSMutableArray<NSURLQueryItem *> *queryItems = [NSMutableArray arrayWithCapacity:8];

  // E.g. X-osv=10.2.1
  NSString *systemVersion = [GULAppEnvironmentUtil systemVersion];
  [queryItems addObject:[NSURLQueryItem queryItemWithName:@"X-osv" value:systemVersion]];
  // E.g. device=
  if (deviceID) {
    [queryItems addObject:[NSURLQueryItem queryItemWithName:@"device" value:deviceID]];
  }
  // E.g. X-scope=fcm
  if (scope) {
    [queryItems addObject:[NSURLQueryItem queryItemWithName:kFIRMessagingParamScope value:scope]];
  }
  // E.g. plat=2
  NSString *platform = [NSString stringWithFormat:@"%ld", (long)kFIRMessagingPlatformVersionIOS];
  [queryItems addObject:[NSURLQueryItem queryItemWithName:@"plat" value:platform]];
  // E.g. app=com.myapp.foo
  NSString *appIdentifier = FIRMessagingAppIdentifier();
  [queryItems addObject:[NSURLQueryItem queryItemWithName:@"app" value:appIdentifier]];
  // E.g. app_ver=1.5
  NSString *appVersion = FIRMessagingCurrentAppVersion();
  [queryItems addObject:[NSURLQueryItem queryItemWithName:@"app_ver" value:appVersion]];
  // E.g. X-cliv=fiid-1.2.3
  NSString *fcmLibraryVersion =
      [NSString stringWithFormat:@"fiid-%@", [FIRMessaging FIRMessagingSDKVersion]];
  if (fcmLibraryVersion.length) {
    NSURLQueryItem *gcmLibVersion =
        [NSURLQueryItem queryItemWithName:kFIRMessagingParamFCMLibVersion value:fcmLibraryVersion];
    [queryItems addObject:gcmLibVersion];
  }

  return queryItems;
}

#pragma mark -  Header

+ (NSString *)HTTPAuthHeaderFromCheckin:(FIRMessagingCheckinPreferences *)checkin {
  NSString *deviceID = checkin.deviceID;
  NSString *secret = checkin.secretToken;
  return [NSString stringWithFormat:@"AidLogin %@:%@", deviceID, secret];
}
@end
