/*
 * 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 "FIRMessagingTopicOperation.h"

#import "FIRMessagingCheckinService.h"
#import "FIRMessagingDefines.h"
#import "FIRMessagingLogger.h"
#import "FIRMessagingUtilities.h"
#import "NSError+FIRMessaging.h"

#define DEBUG_LOG_SUBSCRIPTION_OPERATION_DURATIONS 0

static NSString *const kFIRMessagingSubscribeServerHost =
    @"https://iid.googleapis.com/iid/register";

NSString *FIRMessagingSubscriptionsServer() {
  static NSString *serverHost = nil;
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    NSDictionary *environment = [[NSProcessInfo processInfo] environment];
    NSString *customServerHost = environment[@"FCM_SERVER_ENDPOINT"];
    if (customServerHost.length) {
      serverHost = customServerHost;
    } else {
      serverHost = kFIRMessagingSubscribeServerHost;
    }
  });
  return serverHost;
}

@interface FIRMessagingTopicOperation () {
  BOOL _isFinished;
  BOOL _isExecuting;
}

@property(nonatomic, readwrite, copy) NSString *topic;
@property(nonatomic, readwrite, assign) FIRMessagingTopicAction action;
@property(nonatomic, readwrite, copy) NSString *token;
@property(nonatomic, readwrite, copy) NSDictionary *options;
@property(nonatomic, readwrite, strong) FIRMessagingCheckinService *checkinService;
@property(nonatomic, readwrite, copy) FIRMessagingTopicOperationCompletion completion;

@property(atomic, strong) NSURLSessionDataTask *dataTask;

@end

@implementation FIRMessagingTopicOperation

+ (NSURLSession *)sharedSession {
  static NSURLSession *subscriptionOperationSharedSession;
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
    config.timeoutIntervalForResource = 60.0f;  // 1 minute
    subscriptionOperationSharedSession = [NSURLSession sessionWithConfiguration:config];
    subscriptionOperationSharedSession.sessionDescription = @"com.google.fcm.topics.session";
  });
  return subscriptionOperationSharedSession;
}

- (instancetype)initWithTopic:(NSString *)topic
                       action:(FIRMessagingTopicAction)action
                        token:(NSString *)token
                      options:(NSDictionary *)options
               checkinService:(FIRMessagingCheckinService *)checkinService
                   completion:(FIRMessagingTopicOperationCompletion)completion {
  if (self = [super init]) {
    _topic = topic;
    _action = action;
    _token = token;
    _options = options;
    _checkinService = checkinService;
    _completion = completion;

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

- (void)dealloc {
  _topic = nil;
  _token = nil;
  _checkinService = nil;
  _completion = nil;
}

- (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) {
    NSError *error =
        [NSError errorWithFCMErrorCode:kFIRMessagingErrorCodePubSubOperationIsCancelled];
    [self finishWithError:error];
    return;
  }

  [self setExecuting:YES];

  [self performSubscriptionChange];
}

- (void)finishWithError:(NSError *)error {
  // Add a check to prevent this finish from being called more than once.
  if (self.isFinished) {
    return;
  }
  self.dataTask = nil;
  if (self.completion) {
    self.completion(error);
  }

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

- (void)cancel {
  [super cancel];
  [self.dataTask cancel];
  NSError *error = [NSError errorWithFCMErrorCode:kFIRMessagingErrorCodePubSubOperationIsCancelled];
  [self finishWithError:error];
}

- (void)performSubscriptionChange {

  NSURL *url = [NSURL URLWithString:FIRMessagingSubscriptionsServer()];
  NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
  NSString *appIdentifier = FIRMessagingAppIdentifier();
  NSString *deviceAuthID = self.checkinService.deviceAuthID;
  NSString *secretToken = self.checkinService.secretToken;
  NSString *authString = [NSString stringWithFormat:@"AidLogin %@:%@", deviceAuthID, secretToken];
  [request setValue:authString forHTTPHeaderField:@"Authorization"];
  [request setValue:appIdentifier forHTTPHeaderField:@"app"];
  [request setValue:self.checkinService.versionInfo forHTTPHeaderField:@"info"];

  // Topic can contain special characters (like `%`) so encode the value.
  NSCharacterSet *characterSet = [NSCharacterSet URLQueryAllowedCharacterSet];
  NSString *encodedTopic =
      [self.topic stringByAddingPercentEncodingWithAllowedCharacters:characterSet];
  if (encodedTopic == nil) {
    // The transformation was somehow not possible, so use the original topic.
    FIRMessagingLoggerWarn(kFIRMessagingMessageCodeTopicOptionTopicEncodingFailed,
                           @"Unable to encode the topic '%@' during topic subscription change. "
                           @"Please ensure that the topic name contains only valid characters.",
                           self.topic);
    encodedTopic = self.topic;
  }

  NSMutableString *content = [NSMutableString stringWithFormat:
                              @"sender=%@&app=%@&device=%@&"
                              @"app_ver=%@&X-gcm.topic=%@&X-scope=%@",
                              self.token,
                              appIdentifier,
                              deviceAuthID,
                              FIRMessagingCurrentAppVersion(),
                              encodedTopic,
                              encodedTopic];

  if (self.action == FIRMessagingTopicActionUnsubscribe) {
    [content appendString:@"&delete=true"];
  }

  FIRMessagingLoggerInfo(kFIRMessagingMessageCodeTopicOption000, @"Topic subscription request: %@",
                         content);

  request.HTTPBody = [content dataUsingEncoding:NSUTF8StringEncoding];
  [request setHTTPMethod:@"POST"];

#if DEBUG_LOG_SUBSCRIPTION_OPERATION_DURATIONS
  NSDate *start = [NSDate date];
#endif

  FIRMessaging_WEAKIFY(self)
  void(^requestHandler)(NSData *, NSURLResponse *, NSError *) =
      ^(NSData *data, NSURLResponse *URLResponse, NSError *error) {
        FIRMessaging_STRONGIFY(self)
    if (error) {
      // Our operation could have been cancelled, which would result in our data task's error being
      // NSURLErrorCancelled
      if (error.code == NSURLErrorCancelled) {
        // We would only have been cancelled in the -cancel method, which will call finish for us
        // so just return and do nothing.
        return;
      }
      FIRMessagingLoggerDebug(kFIRMessagingMessageCodeTopicOption001,
                              @"Device registration HTTP fetch error. Error Code: %ld",
                              _FIRMessaging_L(error.code));
      [self finishWithError:error];
      return;
    }
    NSString *response = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    if (response.length == 0) {
      FIRMessagingLoggerDebug(kFIRMessagingMessageCodeTopicOperationEmptyResponse,
                              @"Invalid registration response - zero length.");
      [self finishWithError:[NSError errorWithFCMErrorCode:kFIRMessagingErrorCodeUnknown]];
      return;
    }
    NSArray *parts = [response componentsSeparatedByString:@"="];
    _FIRMessagingDevAssert(parts.count, @"Invalid registration response");
    if (![parts[0] isEqualToString:@"token"] || parts.count <= 1) {
      FIRMessagingLoggerDebug(kFIRMessagingMessageCodeTopicOption002,
                              @"Invalid registration response %@", response);
      [self finishWithError:[NSError errorWithFCMErrorCode:kFIRMessagingErrorCodeUnknown]];
      return;
    }
#if DEBUG_LOG_SUBSCRIPTION_OPERATION_DURATIONS
    NSTimeInterval duration = -[start timeIntervalSinceNow];
    FIRMessagingLoggerDebug(@"%@ change took %.2fs", self.topic, duration);
#endif
    [self finishWithError:nil];

  };

  NSURLSession *urlSession = [FIRMessagingTopicOperation sharedSession];

  self.dataTask = [urlSession dataTaskWithRequest:request completionHandler:requestHandler];
  NSString *description;
  if (_action == FIRMessagingTopicActionSubscribe) {
    description = [NSString stringWithFormat:@"com.google.fcm.topics.subscribe: %@", _topic];
  } else {
    description = [NSString stringWithFormat:@"com.google.fcm.topics.unsubscribe: %@", _topic];
  }
  self.dataTask.taskDescription = description;
  [self.dataTask resume];
}

@end
