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

/* Octagon Trust Local Context Record Constants  */
static NSString* OTCKRecordContextID = @"contextID";
static NSString* OTCKRecordDSID = @"accountDSID";
static NSString* OTCKRecordContextName = @"contextName";
static NSString* OTCKRecordZoneCreated = @"zoneCreated";
static NSString* OTCKRecordSubscribedToChanges = @"subscribedToChanges";
static NSString* OTCKRecordChangeToken = @"changeToken";
static NSString* OTCKRecordEgoPeerID = @"egoPeerID";
static NSString* OTCKRecordEgoPeerCreationDate = @"egoPeerCreationDate";
static NSString* OTCKRecordRecoverySigningSPKI = @"recoverySigningSPKI";
static NSString* OTCKRecordRecoveryEncryptionSPKI = @"recoveryEncryptionSPKI";
static NSString* OTCKRecordBottledPeerTableEntry = @"bottledPeer";

/* Octagon Trust Local Peer Record  */
static NSString* OTCKRecordPeerID = @"peerID";
static NSString* OTCKRecordPermanentInfo = @"permanentInfo";
static NSString* OTCKRecordStableInfo = @"stableInfo";
static NSString* OTCKRecordDynamicInfo = @"dynamicInfo";
static NSString* OTCKRecordRecoveryVoucher = @"recoveryVoucher";
static NSString* OTCKRecordIsEgoPeer = @"isEgoPeer";

/* Octagon Trust BottledPeerSchema  */
static NSString* OTCKRecordEscrowRecordID = @"escrowRecordID";
static NSString* OTCKRecordRecordID = @"bottledPeerRecordID";
static NSString* OTCKRecordSPID = @"spID";
static NSString* OTCKRecordEscrowSigningSPKI = @"escrowSigningSPKI";
static NSString* OTCKRecordPeerSigningSPKI = @"peerSigningSPKI";
static NSString* OTCKRecordEscrowSigningPubKey = @"escrowSigningPubKey";
static NSString* OTCKRecordPeerSigningPubKey = @"peerSigningPubKey";
static NSString* OTCKRecordSignatureFromEscrow = @"signatureUsingEscrow";
static NSString* OTCKRecordSignatureFromPeerKey = @"signatureUsingPeerKey";
static NSString* OTCKRecordBottle = @"bottle";

static NSString* const testDSID = @"123456789";

@interface UnitTestOTLocalStore : OTTestsBase
@end

@implementation UnitTestOTLocalStore

- (void)setUp
{
    [super setUp];
   
    self.continueAfterFailure = NO;
}

- (void)tearDown
{
    [super tearDown];
}

-(void)testDBConnection
{
    NSError* error = nil;
    
    XCTAssertTrue([self.localStore closeDBWithError:&error], @"failed attempt at closing the db");
    XCTAssertNil(error, @"error should be nil:%@", error);
    
    XCTAssertTrue([self.localStore openDBWithError:&error], @"could not open db");
    XCTAssertNil(error, @"error should be nil:%@", error);
    
    XCTAssertTrue([self.localStore closeDBWithError:&error], @"failed attempt at closing the db");
    XCTAssertNil(error, @"error should be nil:%@", error);
    
    XCTAssertTrue([self.localStore openDBWithError:&error], @"could not open db");
    XCTAssertNil(error, @"error should be nil:%@", error);
}

-(void) testDBLocalContextRetrieval
{
    NSString* contextAndDSID = [NSString stringWithFormat:@"testContextRetreival-%@", testDSID];
    _SFECKeyPair *recoverySigningPublicKey = [[SFECKeyPair alloc] initRandomKeyPairWithSpecifier:[[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384]];
    _SFECKeyPair *recoveryEncryptionPublicKey = [[SFECKeyPair alloc] initRandomKeyPairWithSpecifier:[[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384]];

    NSError* error = nil;
    NSDictionary *attributes = @{
                                 OTCKRecordContextID : @"testContextRetreival",
                                 OTCKRecordDSID : testDSID,
                                 OTCKRecordContextName : @"newFoo",
                                 OTCKRecordZoneCreated : @(NO),
                                 OTCKRecordSubscribedToChanges : @(NO),
                                 OTCKRecordChangeToken : [NSData data],
                                 OTCKRecordEgoPeerID : @"OctagonPeerID",
                                 OTCKRecordEgoPeerCreationDate : [NSDate date],
                                 OTCKRecordRecoverySigningSPKI : [[recoverySigningPublicKey publicKey] keyData],
                                 OTCKRecordRecoveryEncryptionSPKI :[[recoveryEncryptionPublicKey publicKey] keyData]};

    XCTAssertTrue([self.localStore insertLocalContextRecord:attributes error:&error], @"inserting new context failed");
    XCTAssertNil(error, @"error should be nil:%@", error);

    OTContextRecord* record = [self.localStore readLocalContextRecordForContextIDAndDSID:contextAndDSID error:&error];
    XCTAssertNotNil(record, @"fetching attributes returned nil");
    XCTAssertNotNil(record.contextID, @"fetching attributes returned nil");
    XCTAssertNotNil(record.contextName, @"fetching attributes returned nil");
    XCTAssertNotNil(record.dsid, @"fetching attributes returned nil");
    XCTAssertNotNil(record.egoPeerCreationDate, @"fetching attributes returned nil");
    XCTAssertNotNil(record.egoPeerID, @"fetching attributes returned nil");
    XCTAssertNotNil(record.recoveryEncryptionSPKI, @"fetching attributes returned nil");
    XCTAssertNotNil(record.recoverySigningSPKI, @"fetching attributes returned nil");

    XCTAssertNil(error, @"failed to read local context for test local store");
    
    OTContextRecord* recordToTestEquality = [[OTContextRecord alloc]init];
    recordToTestEquality.contextName = @"newFoo";
    recordToTestEquality.contextID = @"testContextRetreival";
    recordToTestEquality.dsid = testDSID;
    recordToTestEquality.contextName = @"newFoo";
    recordToTestEquality.egoPeerID = @"OctagonPeerID";
    recordToTestEquality.recoveryEncryptionSPKI = [[recoveryEncryptionPublicKey publicKey] keyData];
    recordToTestEquality.recoverySigningSPKI = [[recoverySigningPublicKey publicKey] keyData];
    
    OTContextRecord* recordFromDB = [self.localStore readLocalContextRecordForContextIDAndDSID:contextAndDSID error:&error];
    XCTAssertTrue([recordFromDB isEqual:recordToTestEquality], @"OTContext should be equal");
}

-(void) testDBMultipleContexts
{
    NSError* error = nil;
    NSString* newFooContextAndDSID = [NSString stringWithFormat:@"newFoo-%@", testDSID];

    _SFECKeyPair *recoverySigningPublicKey = [[SFECKeyPair alloc] initRandomKeyPairWithSpecifier:[[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384]];
    _SFECKeyPair *recoveryEncryptionPublicKey = [[SFECKeyPair alloc] initRandomKeyPairWithSpecifier:[[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384]];
    NSDictionary *attributes = @{
                                 OTCKRecordContextID : @"newFoo",
                                 OTCKRecordContextName : @"newFoo",
                                 OTCKRecordDSID : testDSID,
                                 OTCKRecordZoneCreated : @(NO),
                                 OTCKRecordSubscribedToChanges : @(NO),
                                 OTCKRecordChangeToken : [NSData data],
                                 OTCKRecordEgoPeerID : @"OctagonPeerID",
                                 OTCKRecordEgoPeerCreationDate : [NSDate date],
                                 OTCKRecordRecoverySigningSPKI : [[recoverySigningPublicKey publicKey] keyData],    // FIXME not SPKI
                                 OTCKRecordRecoveryEncryptionSPKI : [[recoveryEncryptionPublicKey publicKey] keyData]};
    
    
    XCTAssertTrue([self.localStore insertLocalContextRecord:attributes error:&error], @"inserting new context failed");
    XCTAssertNil(error, @"error should be nil:%@", error);
    
    NSString* foo2ContextAndDSID = [NSString stringWithFormat:@"Foo2-%@", testDSID];
    attributes = @{
                   OTCKRecordContextID : @"Foo2",
                   OTCKRecordContextName : @"Foo2",
                   OTCKRecordDSID : testDSID,
                   OTCKRecordZoneCreated : @(NO),
                   OTCKRecordSubscribedToChanges : @(NO),
                   OTCKRecordChangeToken : [NSData data],
                   OTCKRecordEgoPeerID : @"OctagonPeerID2",
                   OTCKRecordEgoPeerCreationDate : [NSDate date],
                   OTCKRecordRecoverySigningSPKI : [[recoverySigningPublicKey publicKey] keyData],  // FIXME not SPKI
                   OTCKRecordRecoveryEncryptionSPKI :[[recoveryEncryptionPublicKey publicKey] keyData]};
    
    XCTAssertTrue([self.localStore insertLocalContextRecord:attributes error:&error], @"inserting new context failed");
    XCTAssertNil(error, @"error should be nil:%@", error);

    OTContextRecord* recordNewFoo = [self.localStore readLocalContextRecordForContextIDAndDSID:newFooContextAndDSID error:&error];

    XCTAssertNotNil(recordNewFoo, @"fetching attributes returned nil");
    XCTAssertNotNil(recordNewFoo.contextID, @"fetching attributes returned nil");
    XCTAssertNotNil(recordNewFoo.contextName, @"fetching attributes returned nil");
    XCTAssertNotNil(recordNewFoo.dsid, @"fetching attributes returned nil");
    XCTAssertNotNil(recordNewFoo.egoPeerCreationDate, @"fetching attributes returned nil");
    XCTAssertNotNil(recordNewFoo.egoPeerID, @"fetching attributes returned nil");
    XCTAssertNotNil(recordNewFoo.recoveryEncryptionSPKI, @"fetching attributes returned nil");
    XCTAssertNotNil(recordNewFoo.recoverySigningSPKI, @"fetching attributes returned nil");

    XCTAssertNil(error, @"failed to read local context for test local store");

    OTContextRecord* recordFoo2 = [self.localStore readLocalContextRecordForContextIDAndDSID:foo2ContextAndDSID error:&error];

    XCTAssertNotNil(recordFoo2, @"fetching attributes returned nil");
    XCTAssertNotNil(recordFoo2.contextID, @"fetching attributes returned nil");
    XCTAssertNotNil(recordFoo2.contextName, @"fetching attributes returned nil");
    XCTAssertNotNil(recordFoo2.dsid, @"fetching attributes returned nil");
    XCTAssertNotNil(recordFoo2.egoPeerCreationDate, @"fetching attributes returned nil");
    XCTAssertNotNil(recordFoo2.egoPeerID, @"fetching attributes returned nil");
    XCTAssertNotNil(recordFoo2.recoveryEncryptionSPKI, @"fetching attributes returned nil");
    XCTAssertNotNil(recordFoo2.recoverySigningSPKI, @"fetching attributes returned nil");
    XCTAssertNil(error, @"failed to read local context for test local store");

}

-(void) testRowUpdates
{
    NSError* error = nil;
    NSString* escrowRecordID = @"escrow record 1";
    NSString* escrowRecordID2 = @"escrow record 2";
    NSString* escrowRecordID3 = @"escrow record 3";

    OTBottledPeerRecord* record = [[OTBottledPeerRecord alloc]init];
    OTBottledPeerRecord* record2 = [[OTBottledPeerRecord alloc]init];
    OTBottledPeerRecord* record3 = [[OTBottledPeerRecord alloc]init];

    record.escrowRecordID = escrowRecordID;
    record2.escrowRecordID = escrowRecordID2;
    record3.escrowRecordID = escrowRecordID3;
    
    record.escrowedSigningSPKI = [@"escrowedSigingSPKI" dataUsingEncoding:kCFStringEncodingUTF8];
    record2.escrowedSigningSPKI = [@"escrowedSigingSPI" dataUsingEncoding:kCFStringEncodingUTF8];
    record3.escrowedSigningSPKI = [@"escrowedSigingSPKI" dataUsingEncoding:kCFStringEncodingUTF8];

    XCTAssertTrue([self.localStore insertBottledPeerRecord:record escrowRecordID:escrowRecordID error:&error]);
    XCTAssertNil(error, @"error should be nil:%@", error);

    XCTAssertTrue([self.localStore insertBottledPeerRecord:record2 escrowRecordID:escrowRecordID2 error:&error]);
    XCTAssertNil(error, @"error should be nil:%@", error);

    XCTAssertTrue([self.localStore insertBottledPeerRecord:record3 escrowRecordID:escrowRecordID3 error:&error]);
    XCTAssertNil(error, @"error should be nil:%@", error);


    OTBottledPeerRecord *bp = [self.localStore readLocalBottledPeerRecordWithRecordID:record.recordName error:&error];
    XCTAssertNotNil(bp);
    XCTAssertNil(error, @"error should be nil:%@", error);
    
    OTBottledPeerRecord *bp2 = [self.localStore readLocalBottledPeerRecordWithRecordID:record2.recordName error:&error];
    XCTAssertNotNil(bp2);
    XCTAssertNil(error, @"error should be nil:%@", error);
  
    OTBottledPeerRecord *bp3 = [self.localStore readLocalBottledPeerRecordWithRecordID:record3.recordName error:&error];
    XCTAssertNotNil(bp3);
    XCTAssertNil(error, @"error should be nil:%@", error);
    
    XCTAssertTrue([self.localStore updateLocalContextRecordRowWithContextID:self.localStore.contextID columnName:OTCKRecordContextName newValue:(void*)@"SuperSuperFoo" error:&error], @"could not update column:%@ with value:%@", OTCKRecordContextName, @"SuperSuperFoo");
    XCTAssertNil(error, @"error should be nil:%@", error);
    
    XCTAssertTrue([self.localStore updateLocalContextRecordRowWithContextID:self.localStore.contextID columnName:OTCKRecordEgoPeerID newValue:(void*)@"NewPeerID" error:&error], @"could not update column:%@ with value:%@", OTCKRecordEgoPeerID, @"NewPeerID");
    XCTAssertNil(error, @"error should be nil:%@", error);
    
    XCTAssertTrue([self.localStore updateLocalContextRecordRowWithContextID:self.localStore.contextID columnName:OTCKRecordRecoverySigningSPKI newValue:(void*)[[NSData alloc]initWithBase64EncodedString:@"I'm a string" options:NSDataBase64DecodingIgnoreUnknownCharacters] error:&error], @"could not update column:%@ with value:%@", OTCKRecordContextName, @"NewPeerID");
    XCTAssertNil(error, @"error should be nil:%@", error);
    
    XCTAssertFalse([self.localStore updateLocalContextRecordRowWithContextID:self.localStore.contextID columnName:@"ColumnName" newValue:(void*)@"value" error:&error], @"could not update column:%@ with value:%@", @"ColumnName", @"value");
    XCTAssertNotNil(error, @"error should not be nil: %@", error);
}

@end

#endif /* OCTAGON */
