#import <Foundation/Foundation.h>
#import <DeviceCheck/DeviceCheck.h>
extern "C" void UnitySendMessage(const char *, const char *, const char *);

static NSString *const KeyName = @"IndieExchange.AppAttest.Key";
static NSString *const EnrolledName = @"IndieExchange.AppAttest.Enrolled";

static void Reply(NSString *receiver, NSString *requestId, NSData *proof, NSString *keyId, BOOL enrollment) {
    NSDictionary *result = @{ @"id": requestId, @"proof": proof ? [proof base64EncodedStringWithOptions:0] : @"",
        @"keyId": keyId ?: @"", @"enrollment": @(enrollment) };
    NSData *json = [NSJSONSerialization dataWithJSONObject:result options:0 error:nil];
    NSString *message = [[NSString alloc] initWithData:json encoding:NSUTF8StringEncoding];
    dispatch_async(dispatch_get_main_queue(), ^{ UnitySendMessage(receiver.UTF8String, "Complete", message.UTF8String); });
}

extern "C" void IndieAttestRequest(const char *receiverValue, const char *idValue, const char *hashValue) {
    NSString *receiver = [NSString stringWithUTF8String:receiverValue];
    NSString *requestId = [NSString stringWithUTF8String:idValue];
    NSString *hash = [[[NSString stringWithUTF8String:hashValue] stringByReplacingOccurrencesOfString:@"-" withString:@"+"] stringByReplacingOccurrencesOfString:@"_" withString:@"/"];
    while (hash.length % 4) hash = [hash stringByAppendingString:@"="];
    NSData *clientHash = [[NSData alloc] initWithBase64EncodedString:hash options:0];
    if (@available(iOS 14.0, *)) {
        DCAppAttestService *service = [DCAppAttestService sharedService];
        if (!service.supported || clientHash.length != 32) { Reply(receiver, requestId, nil, nil, NO); return; }
        NSUserDefaults *storage = [NSUserDefaults standardUserDefaults];
        NSString *existing = [storage stringForKey:KeyName];
        BOOL enrolled = [storage boolForKey:EnrolledName];
        void (^sign)(NSString *, BOOL) = ^(NSString *keyId, BOOL enrollment) {
            if (enrollment) {
                [service attestKey:keyId clientDataHash:clientHash completionHandler:^(NSData *attestation, NSError *error) {
                    if (error) { [storage removeObjectForKey:KeyName]; [storage removeObjectForKey:EnrolledName]; }
                    Reply(receiver, requestId, attestation, keyId, YES);
                }];
            } else {
                [service generateAssertion:keyId clientDataHash:clientHash completionHandler:^(NSData *assertion, NSError *error) {
                    if (error.code == DCErrorInvalidKey) { [storage removeObjectForKey:KeyName]; [storage removeObjectForKey:EnrolledName]; }
                    Reply(receiver, requestId, assertion, keyId, NO);
                }];
            }
        };
        if (existing && enrolled) sign(existing, NO);
        else [service generateKeyWithCompletionHandler:^(NSString *keyId, NSError *error) {
            if (error || !keyId) { Reply(receiver, requestId, nil, nil, NO); return; }
            [storage setObject:keyId forKey:KeyName]; [storage setBool:NO forKey:EnrolledName]; sign(keyId, YES);
        }];
    } else Reply(receiver, requestId, nil, nil, NO);
}

extern "C" void IndieAttestEnrollment(const char *keyValue, bool accepted) {
    NSString *key = [NSString stringWithUTF8String:keyValue];
    NSUserDefaults *storage = [NSUserDefaults standardUserDefaults];
    if (![[storage stringForKey:KeyName] isEqualToString:key]) return;
    if (accepted) [storage setBool:YES forKey:EnrolledName];
    else { [storage removeObjectForKey:KeyName]; [storage removeObjectForKey:EnrolledName]; }
}
