
#if OCTAGON

#import <SecurityFoundation/SecurityFoundation.h>
#import <SecurityFoundation/SFIdentity.h>

#import "keychain/TrustedPeersHelper/TrustedPeersHelperProtocol.h"
#import "keychain/ot/OctagonCKKSPeerAdapter.h"
#import "keychain/ot/OTDefines.h"
#import "keychain/ot/OTConstants.h"
#import "keychain/ckks/CKKSPeer.h"
#import "keychain/ckks/CKKSListenerCollection.h"

#import "keychain/ot/ObjCImprovements.h"
#import "utilities/debugging.h"
#import "keychain/categories/NSError+UsefulConstructors.h"

@interface OctagonSelfPeer ()
@property SFIdentity* encryptionIdentity;
@property SFIdentity* signingIdentity;
@end

@implementation OctagonSelfPeer
@synthesize peerID = _peerID;

- (instancetype)initWithPeerID:(NSString*)peerID
               signingIdentity:(SFIdentity*)signingIdentity
            encryptionIdentity:(SFIdentity*)encryptionIdentity
{
    if((self = [super init])) {
        _peerID = peerID;
        _signingIdentity = signingIdentity;
        _encryptionIdentity = encryptionIdentity;
    }
    return self;
}

- (NSString*)description
{
    return [NSString stringWithFormat:@"<OctagonSelfPeer: %@>", self.peerID];
}

- (SFECPublicKey*)publicEncryptionKey
{
    return self.encryptionIdentity.publicKey;
}

- (SFECPublicKey*)publicSigningKey
{
    return self.signingIdentity.publicKey;
}

- (SFECKeyPair*)encryptionKey
{
    return self.encryptionIdentity.keyPair;
}

- (SFECKeyPair*)signingKey
{
    return self.signingIdentity.keyPair;
}

- (bool)matchesPeer:(nonnull id<CKKSPeer>)peer {
    NSString* otherPeerID = peer.peerID;

    if(self.peerID == nil && otherPeerID == nil) {
        return true;
    }

    return [self.peerID isEqualToString:otherPeerID];
}

@end

@interface OctagonCKKSPeerAdapter ()
@property CKKSListenerCollection* peerChangeListeners;
@end

@implementation OctagonCKKSPeerAdapter
@synthesize essential = _essential;
@synthesize providerID = _providerID;


- (instancetype)initWithPeerID:(NSString*)peerID
                 containerName:(NSString*)containerName
                     contextID:(NSString*)contextID
                 cuttlefishXPC:(CuttlefishXPCWrapper*)cuttlefishXPCWrapper
{
    if((self = [super init])) {
        _providerID = [NSString stringWithFormat:@"[OctagonCKKSPeerAdapter:%@]", peerID];
        _peerID = peerID;

        _containerName = containerName;
        _contextID = contextID;
        _cuttlefishXPCWrapper = cuttlefishXPCWrapper;

        _peerChangeListeners = [[CKKSListenerCollection alloc] initWithName:@"ckks-sos"];

        // Octagon is king.
        _essential = YES;
    }
    return self;
}

- (NSString*)description
{
    return [NSString stringWithFormat:@"<OctagonCKKSPeerAdapter: %@ e:%d>", self.peerID, self.essential];
}

- (SFIdentity*)fetchIdentity:(NSString*)identifier error:(NSError *__autoreleasing  _Nullable * _Nullable)error
{
    // Async to sync again! No other option.
    dispatch_semaphore_t sema = dispatch_semaphore_create(0);
    SFKeychainManager* keychainManager = [SFKeychainManager defaultOverCommitManager];

    __block SFIdentity* identity = nil;
    __block NSError* localError = nil;

    [keychainManager identityForIdentifier:identifier resultHandler:^(SFKeychainIdentityFetchResult * _Nonnull result) {
        switch(result.resultType) {
            case SFKeychainFetchResultTypeError:
            case SFKeychainFetchResultTypeNeedsAuthentication:
                secnotice("octagon-ckks", "Unable to fetch identity '%@' from keychain: %@", identifier, result.error);
                localError = result.error;
                break;

            case SFKeychainFetchResultTypeValueAvailable:
                identity = result.value;
                break;
        }
        dispatch_semaphore_signal(sema);
    }];

    dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);

    if(error && localError) {
        *error = localError;
    }
    return identity;
}

- (CKKSSelves * _Nullable)fetchSelfPeers:(NSError *__autoreleasing  _Nullable * _Nullable)error
{
    if(self.peerID) {
        // Shameless duplication of swift code in TPH. Thanks, perf team!
        NSError* keychainError = nil;

        SFIdentity* signingIdentity = [self fetchIdentity:[NSString stringWithFormat:@"signing-key %@", self.peerID] error:&keychainError];
        if(!signingIdentity || keychainError) {
            if(error) {
                // TODO: ensure error exists
                *error = keychainError;
            }
            return nil;
        }

        SFIdentity* encryptionIdentity = [self fetchIdentity:[NSString stringWithFormat:@"encryption-key %@", self.peerID] error:&keychainError];
        if(!encryptionIdentity || keychainError) {
            if(error) {
                // TODO: ensure error exists
                *error = keychainError;
            }
            return nil;
        }

        OctagonSelfPeer* selfPeer = [[OctagonSelfPeer alloc] initWithPeerID:self.peerID
                                                            signingIdentity:signingIdentity
                                                         encryptionIdentity:encryptionIdentity];

        CKKSSelves* selves = [[CKKSSelves alloc] initWithCurrent:selfPeer allSelves:[NSSet set]];
        return selves;
    }

    secnotice("octagon-ckks", "No peer ID; therefore no identity");
    if(error) {
        *error = [NSError errorWithDomain:OctagonErrorDomain
                                     code:OTErrorNoIdentity
                              description:@"no peer ID present"];
    }
    return nil;
}

- (NSSet<id<CKKSRemotePeerProtocol>> * _Nullable)fetchTrustedPeers:(NSError *__autoreleasing  _Nullable * _Nullable)error
{
    // TODO: make this memoized somehow, somewhere

    __block NSError* localerror;
    __block NSMutableSet<id<CKKSRemotePeerProtocol>> * peers = nil;

    WEAKIFY(self);
    [self.cuttlefishXPCWrapper fetchTrustStateWithContainer:self.containerName
                                                    context:self.contextID
                                                      reply:^(TrustedPeersHelperPeerState * _Nullable selfPeerState,
                                                              NSArray<TrustedPeersHelperPeer *> * _Nullable trustedPeers,
                                                              NSError * _Nullable operror) {
            STRONGIFY(self);
            if(operror) {
                secnotice("octagon", "Unable to fetch trusted peers for (%@,%@): %@", self.containerName, self.contextID, operror);
                localerror = operror;

            } else {
                peers = [NSMutableSet set];

                // Turn these peers into CKKSPeers
                for(TrustedPeersHelperPeer* peer in trustedPeers) {
                    SFECPublicKey* signingKey = [SFECPublicKey keyWithSubjectPublicKeyInfo:peer.signingSPKI];
                    SFECPublicKey* encryptionKey = [SFECPublicKey keyWithSubjectPublicKeyInfo:peer.encryptionSPKI];

                    CKKSActualPeer* ckkspeer = [[CKKSActualPeer alloc] initWithPeerID:peer.peerID
                                                                  encryptionPublicKey:encryptionKey
                                                                     signingPublicKey:signingKey
                                                                             viewList:peer.viewList];
                    [peers addObject:ckkspeer];
                }
            }
        }];

    if(error && localerror) {
        *error = localerror;
    }

    return peers;
}

- (void)registerForPeerChangeUpdates:(nonnull id<CKKSPeerUpdateListener>)listener {
    [self.peerChangeListeners registerListener:listener];
}

- (void)sendSelfPeerChangedUpdate {
    [self.peerChangeListeners iterateListeners: ^(id<CKKSPeerUpdateListener> listener) {
        [listener selfPeerChanged: self];
    }];
}

- (void)sendTrustedPeerSetChangedUpdate {
    [self.peerChangeListeners iterateListeners: ^(id<CKKSPeerUpdateListener> listener) {
        [listener trustedPeerSetChanged: self];
    }];
}

- (nonnull CKKSPeerProviderState *)currentState {
    return [CKKSPeerProviderState createFromProvider:self];
}


@end

#endif // OCTAGON
