/*
 * 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 "FirebaseInstallations/Source/Library/FIRInstallationsItem.h"

#import "FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStoredAuthToken.h"
#import "FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStoredItem.h"

#import "FirebaseInstallations/Source/Library/Errors/FIRInstallationsErrorUtil.h"

@implementation FIRInstallationsItem

- (instancetype)initWithAppID:(NSString *)appID firebaseAppName:(NSString *)firebaseAppName {
  self = [super init];
  if (self) {
    _appID = [appID copy];
    _firebaseAppName = [firebaseAppName copy];
  }
  return self;
}

- (nonnull id)copyWithZone:(nullable NSZone *)zone {
  FIRInstallationsItem *clone = [[FIRInstallationsItem alloc] initWithAppID:self.appID
                                                            firebaseAppName:self.firebaseAppName];
  clone.firebaseInstallationID = [self.firebaseInstallationID copy];
  clone.refreshToken = [self.refreshToken copy];
  clone.authToken = [self.authToken copy];
  clone.registrationStatus = self.registrationStatus;
  clone.IIDDefaultToken = [self.IIDDefaultToken copy];

  return clone;
}

- (void)updateWithStoredItem:(FIRInstallationsStoredItem *)item {
  self.firebaseInstallationID = item.firebaseInstallationID;
  self.refreshToken = item.refreshToken;
  self.authToken = item.authToken;
  self.registrationStatus = item.registrationStatus;
  self.IIDDefaultToken = item.IIDDefaultToken;
}

- (FIRInstallationsStoredItem *)storedItem {
  FIRInstallationsStoredItem *storedItem = [[FIRInstallationsStoredItem alloc] init];
  storedItem.firebaseInstallationID = self.firebaseInstallationID;
  storedItem.refreshToken = self.refreshToken;
  storedItem.authToken = self.authToken;
  storedItem.registrationStatus = self.registrationStatus;
  storedItem.IIDDefaultToken = self.IIDDefaultToken;
  return storedItem;
}

- (nonnull NSString *)identifier {
  return [[self class] identifierWithAppID:self.appID appName:self.firebaseAppName];
}

- (BOOL)isValid:(NSError *_Nullable *)outError {
  NSMutableArray<NSString *> *validationIssues = [NSMutableArray array];

  if (self.appID.length == 0) {
    [validationIssues addObject:@"`appID` must not be empty"];
  }

  if (self.firebaseAppName.length == 0) {
    [validationIssues addObject:@"`firebaseAppName` must not be empty"];
  }

  if (self.firebaseInstallationID.length == 0) {
    [validationIssues addObject:@"`firebaseInstallationID` must not be empty"];
  }

  switch (self.registrationStatus) {
    case FIRInstallationStatusUnknown:
      [validationIssues addObject:@"invalid `registrationStatus`"];
      break;

    case FIRInstallationStatusRegistered:
      if (self.refreshToken == 0) {
        [validationIssues addObject:@"registered installation must have non-empty `refreshToken`"];
      }

      if (self.authToken.token == 0) {
        [validationIssues
            addObject:@"registered installation must have non-empty `authToken.token`"];
      }

      if (self.authToken.expirationDate == nil) {
        [validationIssues
            addObject:@"registered installation must have non-empty `authToken.expirationDate`"];
      }

    case FIRInstallationStatusUnregistered:
      break;
  }

  BOOL isValid = validationIssues.count == 0;

  if (!isValid && outError) {
    NSString *failureReason =
        [NSString stringWithFormat:@"FIRInstallationsItem validation errors: %@",
                                   [validationIssues componentsJoinedByString:@", "]];
    *outError =
        [FIRInstallationsErrorUtil installationsErrorWithCode:FIRInstallationsErrorCodeUnknown
                                                failureReason:failureReason
                                              underlyingError:nil];
  }

  return isValid;
}

+ (NSString *)identifierWithAppID:(NSString *)appID appName:(NSString *)appName {
  return [appID stringByAppendingString:appName];
}

+ (NSString *)generateFID {
  NSUUID *UUID = [NSUUID UUID];
  uuid_t UUIDBytes;
  [UUID getUUIDBytes:UUIDBytes];

  NSUInteger UUIDLength = sizeof(uuid_t);
  NSData *UUIDData = [NSData dataWithBytes:UUIDBytes length:UUIDLength];

  uint8_t UUIDLast4Bits = UUIDBytes[UUIDLength - 1] & 0b00001111;

  // FID first 4 bits must be `0111`. The last 4 UUID bits will be cut later to form a proper FID.
  // To keep 16 random bytes we copy these last 4 UUID to the FID 1st byte after `0111` prefix.
  uint8_t FIDPrefix = 0b01110000 | UUIDLast4Bits;
  NSMutableData *FIDData = [NSMutableData dataWithBytes:&FIDPrefix length:1];

  [FIDData appendData:UUIDData];
  NSString *FIDString = [self base64URLEncodedStringWithData:FIDData];

  // A valid FID has exactly 22 base64 characters, which is 132 bits, or 16.5 bytes.
  // Our generated ID has 16 bytes UUID + 1 byte prefix which after encoding with base64 will become
  // 23 characters plus 1 character for "=" padding.

  // Remove the 23rd character that was added because of the extra 4 bits at the
  // end of our 17 byte data and the '=' padding.
  return [FIDString substringWithRange:NSMakeRange(0, 22)];
}

+ (NSString *)base64URLEncodedStringWithData:(NSData *)data {
  NSString *string = [data base64EncodedStringWithOptions:0];
  string = [string stringByReplacingOccurrencesOfString:@"/" withString:@"_"];
  string = [string stringByReplacingOccurrencesOfString:@"+" withString:@"-"];
  return string;
}

@end
