/*
 * Copyright (c) 2017 Apple Inc. All Rights Reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 *
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 *
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 *
 * @APPLE_LICENSE_HEADER_END@
 */

#if OCTAGON

#import "OTTestsBase.h"

static NSString* const testContextID = @"Foo";
static NSString* const testDSID = @"123456789";
static int _test_num = 0;
static NSString* _path;
static NSString* _dbPath;

static NSString* OTCKZoneName = @"OctagonTrust";

static NSString* const kOTRampZoneName = @"metadata_zone";
static NSString* const kOTRampForEnrollmentRecordName = @"metadata_rampstate_enroll";
static NSString* const kOTRampForRestoreRecordName = @"metadata_rampstate_restore";
static NSString* const kOTRampForCFURecordName = @"metadata_rampstate_cfu";

static NSString* kFeatureAllowedKey =       @"FeatureAllowed";
static NSString* kFeaturePromotedKey =      @"FeaturePromoted";
static NSString* kFeatureVisibleKey =       @"FeatureVisible";
static NSString* kRetryAfterKey =           @"RetryAfter";
static NSString* kRampPriorityKey =         @"RampPriority";

static NSString* OTCKRecordBottledPeerType = @"OTBottledPeer";

@implementation OTTestsBase

// Override our base class
-(NSSet*)managedViewList {
    return [NSSet setWithObject:@"keychain"];
}

+ (void)setUp {
    SecCKKSEnable();
    SecCKKSResetSyncing();
    [super setUp];
}

- (void)setUp
{
    [super setUp];
    
    self.continueAfterFailure = NO;
    NSError* error = nil;

    _path = @"/tmp/ottrusttests";
    _dbPath = [_path stringByAppendingFormat:@"/ottest.db.%d",_test_num++];
    
    XCTAssertTrue([[NSFileManager defaultManager] createDirectoryAtPath:_path withIntermediateDirectories:YES attributes:nil error:nil], @"directory created!");
    self.localStore = [[OTLocalStore alloc]initWithContextID:testContextID dsid:testDSID path:_dbPath error:&error];
    XCTAssertNil(error, "error should be nil");

    self.cloudStore = [[OTCloudStore alloc] initWithContainer:self.mockContainer
                                                     zoneName:OTCKZoneName
                                               accountTracker:self.mockAccountStateTracker
                                          reachabilityTracker:self.mockReachabilityTracker
                                                   localStore:self.localStore
                                                    contextID:testContextID
                                                         dsid:testDSID
                         fetchRecordZoneChangesOperationClass:self.mockFakeCKFetchRecordZoneChangesOperation
                                   fetchRecordsOperationClass:self.mockFakeCKFetchRecordZoneChangesOperation
                                          queryOperationClass:self.mockFakeCKQueryOperation
                            modifySubscriptionsOperationClass:self.mockFakeCKModifySubscriptionsOperation
                              modifyRecordZonesOperationClass:self.mockFakeCKFetchRecordsOperation
                                           apsConnectionClass:self.mockFakeCKModifySubscriptionsOperation
                                               operationQueue:nil];
  
    NSString* secretString = @"I'm a secretI'm a secretI'm a secretI'm a secretI'm a secretI'm a secret";
    self.secret = [[NSData alloc]initWithBytes:[secretString UTF8String] length:[secretString length]];
    
    self.context = [[OTContext alloc]initWithContextID:testContextID dsid:testDSID localStore:self.localStore cloudStore:self.cloudStore identityProvider:self error:&error];
    XCTAssertNil(error, "error should be nil");

    self.sosPeerID = @"spID";
    self.egoPeerID = @"egoPeerID";
    self.peerSigningKey = [[SFECKeyPair alloc] initRandomKeyPairWithSpecifier:[[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384]];
    self.peerEncryptionKey = [[SFECKeyPair alloc] initRandomKeyPairWithSpecifier:[[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384]];
    self.escrowKeys = [[OTEscrowKeys alloc]initWithSecret:self.secret dsid:testDSID error:&error];

    XCTAssertNotNil(self.context, @"context not initialized");
    
    self.otZoneID = [[CKRecordZoneID alloc] initWithZoneName:OTCKZoneName ownerName:CKCurrentUserDefaultName];
    
    XCTAssertNotNil(self.otZoneID, @"cloudkit record zone id is not initialized");
    
    self.otFakeZone = [[FakeCKZone alloc] initZone: self.otZoneID];
    XCTAssertNotNil(self.otFakeZone, @"fake ot zone is not initialized");
    
    self.zones[self.otZoneID] = self.otFakeZone;
    XCTAssertNotNil(self.zones, @"ot zones set is not initialized");

    self.rampZoneID = [[CKRecordZoneID alloc] initWithZoneName:kOTRampZoneName ownerName:CKCurrentUserDefaultName];
    self.rampZone = [[FakeCKZone alloc]initZone:self.rampZoneID];
    self.zones[self.rampZoneID] = self.rampZone;

    self.cfu = [self fakeRamp:kOTRampForCFURecordName featureName:@"FAKE-cfu"];
    self.enroll = [self fakeRamp:kOTRampForEnrollmentRecordName featureName:@"FAKE-enroll"];
    self.restore = [self fakeRamp:kOTRampForRestoreRecordName featureName:@"FAKE-restore"];

    self.scheduler = [[CKKSNearFutureScheduler alloc] initWithName: @"test" delay:50*NSEC_PER_MSEC keepProcessAlive:true
                                         dependencyDescriptionCode:CKKSResultDescriptionNone
                                                             block:^{
                                                                 [self.expectation fulfill];
                                                             }];
    self.manager = [[OTManager alloc] initWithContext:self.context
                                           localStore:self.localStore
                                               enroll:self.enroll
                                              restore:self.restore
                                                  cfu:self.cfu
                                         cfuScheduler:self.scheduler];
    
    id mockConnection = OCMPartialMock([[NSXPCConnection alloc] init]);
    OCMStub([mockConnection remoteObjectProxyWithErrorHandler:[OCMArg any]]).andCall(self, @selector(manager));
    self.otControl = [[OTControl alloc] initWithConnection:mockConnection];
    XCTAssertNotNil(self.otControl, "Should have received control object");

    [self startCKKSSubsystem];
    
    self.accountStatus = CKAccountStatusAvailable;
    self.circleStatus = [[SOSAccountStatus alloc] init:kSOSCCInCircle error:nil];
    [self.cfu.accountTracker notifyCKAccountStatusChangeAndWaitForSignal];

    [self.context.accountTracker notifyCKAccountStatusChangeAndWaitForSignal];
    [self.enroll.accountTracker notifyCKAccountStatusChangeAndWaitForSignal];
    [self.restore.accountTracker notifyCKAccountStatusChangeAndWaitForSignal];

    [self.context.accountTracker notifyCircleStatusChangeAndWaitForSignal];
    [self.cfu.accountTracker notifyCircleStatusChangeAndWaitForSignal];
    [self.enroll.accountTracker notifyCircleStatusChangeAndWaitForSignal];
    [self.restore.accountTracker notifyCircleStatusChangeAndWaitForSignal];

    self.reachabilityFlags = kSCNetworkReachabilityFlagsReachable;
    [self.context.reachabilityTracker recheck];
    [self.cfu.reachabilityTracker recheck];
    [self.enroll.reachabilityTracker recheck];
    [self.restore.reachabilityTracker recheck];

}


- (void)tearDown
{
    NSError *error = nil;

    [_localStore removeAllBottledPeerRecords:&error];
    [_localStore deleteAllContexts:&error];
    
    _context = nil;
    _cloudStore = nil;
    _localStore = nil;
    _escrowKeys = nil;
    _peerSigningKey = nil;
    _peerEncryptionKey = nil;
    _otFakeZone = nil;
    _otZoneID = nil;

    _rampZone = nil;
    _rampZoneID = nil;
    _cfuRampRecord = nil;
    _enrollRampRecord = nil;
    _restoreRampRecord = nil;
    _scheduler = nil;

    [super tearDown];
}

-(OTRamp*) fakeRamp:(NSString*)recordName featureName:(NSString*)featureName
{

    OTRamp* ramp = [[OTRamp alloc]initWithRecordName:recordName
                                         featureName:featureName
                                           container:self.mockContainer
                                            database:self.mockDatabase
                                              zoneID:self.rampZoneID
                                      accountTracker:[CKKSViewManager manager].accountTracker
                                    lockStateTracker:[CKKSViewManager manager].lockStateTracker
                                 reachabilityTracker:[CKKSViewManager manager].reachabilityTracker
                    fetchRecordRecordsOperationClass:self.mockFakeCKFetchRecordsOperation];

    return ramp;
}

-(void) setUpRampRecordsInCloudKitWithFeatureOff
{
    CKRecordID* enrollRecordID = [[CKRecordID alloc] initWithRecordName:kOTRampForEnrollmentRecordName zoneID:self.rampZoneID];
    self.enrollRampRecord = [[CKRecord alloc] initWithRecordType:kOTRampForEnrollmentRecordName recordID:enrollRecordID];
    self.enrollRampRecord[kFeatureAllowedKey] = @NO;
    self.enrollRampRecord[kFeaturePromotedKey] = @NO;   //always false right now
    self.enrollRampRecord[kFeatureVisibleKey] = @NO;
    self.enrollRampRecord[kRetryAfterKey] = [[NSNumber alloc]initWithInt:3600];

    CKRecordID* restoreRecordID = [[CKRecordID alloc] initWithRecordName:kOTRampForRestoreRecordName zoneID:self.rampZoneID];
    self.restoreRampRecord = [[CKRecord alloc] initWithRecordType:kOTRampForEnrollmentRecordName recordID:restoreRecordID];
    self.restoreRampRecord[kFeatureAllowedKey] = @NO;
    self.restoreRampRecord[kFeaturePromotedKey] = @NO;  //always false right now
    self.restoreRampRecord[kFeatureVisibleKey] = @NO;
    self.restoreRampRecord[kRetryAfterKey] = [[NSNumber alloc]initWithInt:3600];

    CKRecordID* cfuRecordID = [[CKRecordID alloc] initWithRecordName:kOTRampForCFURecordName zoneID:self.rampZoneID];
    self.cfuRampRecord = [[CKRecord alloc] initWithRecordType:kOTRampForCFURecordName recordID:cfuRecordID];
    self.cfuRampRecord[kFeatureAllowedKey] = @NO;
    self.cfuRampRecord[kFeaturePromotedKey] = @NO;  //always false right now
    self.cfuRampRecord[kFeatureVisibleKey] = @NO;
    self.cfuRampRecord[kRetryAfterKey] = [[NSNumber alloc]initWithInt:3600];

    [self.rampZone addToZone:self.enrollRampRecord];
    [self.rampZone addToZone:self.restoreRampRecord];
    [self.rampZone addToZone:self.cfuRampRecord];
}

-(void) setUpRampRecordsInCloudKitWithFeatureOn
{
    CKRecordID* enrollRecordID = [[CKRecordID alloc] initWithRecordName:kOTRampForEnrollmentRecordName zoneID:self.rampZoneID];
    self.enrollRampRecord = [[CKRecord alloc] initWithRecordType:kOTRampForEnrollmentRecordName recordID:enrollRecordID];
    self.enrollRampRecord[kFeatureAllowedKey] = @YES;
    self.enrollRampRecord[kFeaturePromotedKey] = @NO;   //always false right now
    self.enrollRampRecord[kFeatureVisibleKey] = @YES;
    self.enrollRampRecord[kRetryAfterKey] = [[NSNumber alloc]initWithInt:3600];

    CKRecordID* restoreRecordID = [[CKRecordID alloc] initWithRecordName:kOTRampForRestoreRecordName zoneID:self.rampZoneID];
    self.restoreRampRecord = [[CKRecord alloc] initWithRecordType:kOTRampForEnrollmentRecordName recordID:restoreRecordID];
    self.restoreRampRecord[kFeatureAllowedKey] = @YES;
    self.restoreRampRecord[kFeaturePromotedKey] = @NO;  //always false right now
    self.restoreRampRecord[kFeatureVisibleKey] = @YES;
    self.restoreRampRecord[kRetryAfterKey] = [[NSNumber alloc]initWithInt:3600];

    CKRecordID* cfuRecordID = [[CKRecordID alloc] initWithRecordName:kOTRampForCFURecordName zoneID:self.rampZoneID];
    self.cfuRampRecord = [[CKRecord alloc] initWithRecordType:kOTRampForCFURecordName recordID:cfuRecordID];
    self.cfuRampRecord[kFeatureAllowedKey] = @YES;
    self.cfuRampRecord[kFeaturePromotedKey] = @NO;  //always false right now
    self.cfuRampRecord[kFeatureVisibleKey] = @YES;
    self.cfuRampRecord[kRetryAfterKey] = [[NSNumber alloc]initWithInt:3600];

    [self.rampZone addToZone:self.enrollRampRecord];
    [self.rampZone addToZone:self.restoreRampRecord];
    [self.rampZone addToZone:self.cfuRampRecord];
}


-(void)expectAddedCKModifyRecords:(NSDictionary<NSString*, NSNumber*>*)records holdFetch:(BOOL)shouldHoldTheFetch
{
    __weak __typeof(self) weakSelf = self;
    
    [self expectCKModifyRecords:records
        deletedRecordTypeCounts:nil
                         zoneID:self.otZoneID
            checkModifiedRecord:^BOOL (CKRecord* record){
                if([record.recordType isEqualToString: OTCKRecordBottledPeerType]) {
                    return YES;
                } else { //not a Bottled Peer Record Type
                    return NO;
                }
            }
           runAfterModification:^{
               __strong __typeof(self) strongSelf = weakSelf;
               [strongSelf holdCloudKitFetches];
           }
     ];
}

-(void)expectDeletedCKModifyRecords:(NSDictionary<NSString*, NSNumber*>*)records holdFetch:(BOOL)shouldHoldTheFetch
{
    __weak __typeof(self) weakSelf = self;
    
    [self expectCKModifyRecords:[NSMutableDictionary dictionary]
        deletedRecordTypeCounts:records
                         zoneID:self.otZoneID
            checkModifiedRecord:^BOOL (CKRecord* record){
                if([record.recordType isEqualToString: OTCKRecordBottledPeerType]) {
                    return YES;
                } else { //not a Bottled Peer Record Type
                    return NO;
                }
            }
           runAfterModification:^{
               __strong __typeof(self) strongSelf = weakSelf;
               [strongSelf holdCloudKitFetches];
           }
     ];
}

- (nullable OTIdentity *)currentIdentity:(NSError * _Nullable __autoreleasing * _Nullable)error {
    return [[OTIdentity alloc]initWithPeerID:self.egoPeerID spID:self.sosPeerID peerSigningKey:self.peerSigningKey peerEncryptionkey:self.peerEncryptionKey error:error];
}


@end
#endif
