//!important: Intentionally leave out #import "ZSMNativeModule.h", note that it uses externs and should have no problem with the missing import
#import <React/RCTBridgeModule.h>
#import <ZSM/ZSM.h>
#import "generated.h"

@interface ZSMNativeModule : NSObject <RCTBridgeModule>
@property (nonatomic, strong) FIDO2Client *client;
- (NSString *)generateTraceId;
- (NSString *)formatTraceMessage:(NSString *)traceId message:(NSString *)message;
@end

@implementation ZSMNativeModule

- (NSString *)generateTraceId {
    return [[NSUUID UUID] UUIDString];
}

- (NSString *)formatTraceMessage:(NSString *)traceId message:(NSString *)message {
    return [NSString stringWithFormat:@"[TID:%@] %@", traceId, message];
}

RCT_EXPORT_MODULE(ZSM);

//TODO: use the same return types in the rust(wasm), kotlin, and ios here, rather than handling in the js client

// Method to return the version string
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(getVersionString) {
    return [NSString stringWithUTF8String:ZSM_VERSION];
}

RCT_EXPORT_METHOD(create:(NSDictionary *)config
                resolver:(RCTPromiseResolveBlock)resolve
                rejecter:(RCTPromiseRejectBlock)reject) {
    
    ZSMConfig *zsmConfig = [[ZSMConfig alloc] initWithJSON:config];
    if (!zsmConfig) {
        reject(@"ios::create", @"Invalid config", nil);
        return;
    }
    FIDO2Client *client = [[FIDO2Client alloc] initWithConfig:zsmConfig];
    if (!client) {
        reject(@"ios::create", @"Failed to create client", nil);

    } else {
        self.client = client;
        resolve(@{@"success": @YES});
    }
}

// Method to configure the ZSM instance
RCT_EXPORT_METHOD(configure:(NSDictionary *)config
                   resolver:(RCTPromiseResolveBlock)resolve
                   rejecter:(RCTPromiseRejectBlock)reject) {
    if (!self.client) {
        reject(@"ios::configure", @"Client is not initialized", nil);
        return;
    }

    ZSMConfig *zsmConfig = [[ZSMConfig alloc] initWithJSON:config];
    if (!zsmConfig) {
        reject(@"ios::configure", @"Invalid config", nil);
        return;
    }
    [self.client configure:zsmConfig];
    resolve(@{@"success": @YES});
}

// WebAuthn Create
RCT_EXPORT_METHOD(webauthn_create:(NSDictionary *)options
                          traceId:(nullable NSString *)traceId
                         resolver:(RCTPromiseResolveBlock)resolve
                         rejecter:(RCTPromiseRejectBlock)reject) {
    if (!self.client) {
        reject(@"ios::webauthn_create", @"Client is not initialized", nil);
        return;
    }

    NSString *actualTraceId = traceId ?: [self generateTraceId];
    [ZSMClient logWithLevel:LogLevelTrace message:[self formatTraceMessage:actualTraceId message:@"webauthn_create called"]];

    [self.client webauthnCreateWithOptions:options completion:^(NSDictionary<NSString *, id> * _Nullable credentials, NSDictionary<NSString *, NSString *> * _Nullable metadata, ZSMError * _Nullable error) {
        if (error) {
            [ZSMClient logWithLevel:LogLevelError message:[self formatTraceMessage:actualTraceId message:[NSString stringWithFormat:@"webauthn_create error: %@", error.localizedDescription]]];
            
            // Create rich NSError with all ZSMError properties for JavaScript
            NSError *richError = [NSError errorWithDomain:@"ZSMError" 
                                                     code:error.code 
                                                 userInfo:@{
                                                     @"message": error.message ?: @"Unknown error",
                                                     @"code": @(error.code),
                                                     @"traceId": error.traceId ?: @"",
                                                     @"details": error.description ?: @""
                                                 }];
            
            // Use structured error code and message for consistency with Android
            reject([NSString stringWithFormat:@"ZSM_%ld", (long)error.code], 
                   [NSString stringWithFormat:@"[ZSM %ld] %@ (Trace: %@)", (long)error.code, error.message ?: @"Unknown error", error.traceId ?: @""], 
                   richError);
        } else {
            [ZSMClient logWithLevel:LogLevelTrace message:[self formatTraceMessage:actualTraceId message:@"webauthn_create completed successfully"]];
            resolve(@{
                @"result": credentials ?: [NSNull null],  // Use NSNull for nil credentials
                @"metadata": metadata ?: [NSNull null]   // Use NSNull for nil metadata
            });
        }
    }];
}

// WebAuthn Get
RCT_EXPORT_METHOD(webauthn_get:(NSDictionary *)options
                       traceId:(nullable NSString *)traceId
                      resolver:(RCTPromiseResolveBlock)resolve
                      rejecter:(RCTPromiseRejectBlock)reject) {
    if (!self.client) {
        reject(@"ios::webauthn_get", @"Client is not initialized", nil);
        return;
    }

    NSString *actualTraceId = traceId ?: [self generateTraceId];
    [ZSMClient logWithLevel:LogLevelTrace message:[self formatTraceMessage:actualTraceId message:@"webauthn_get called"]];

    [self.client webauthnGetWithOptions:options completion:^(NSDictionary<NSString *, id> * _Nullable credentials, NSDictionary<NSString *, NSString *> * _Nullable metadata, ZSMError * _Nullable error) {
        if (error) {
            [ZSMClient logWithLevel:LogLevelError message:[self formatTraceMessage:actualTraceId message:[NSString stringWithFormat:@"webauthn_get error: %@", error.localizedDescription]]];
            
            // Create rich NSError with all ZSMError properties for JavaScript
            NSError *richError = [NSError errorWithDomain:@"ZSMError" 
                                                     code:error.code 
                                                 userInfo:@{
                                                     @"message": error.message ?: @"Unknown error",
                                                     @"code": @(error.code),
                                                     @"traceId": error.traceId ?: @"",
                                                     @"details": error.description ?: @""
                                                 }];
            
            // Use structured error code and message for consistency with Android
            reject([NSString stringWithFormat:@"ZSM_%ld", (long)error.code], 
                   [NSString stringWithFormat:@"[ZSM %ld] %@ (Trace: %@)", (long)error.code, error.message ?: @"Unknown error", error.traceId ?: @""], 
                   richError);
        } else {
            [ZSMClient logWithLevel:LogLevelTrace message:[self formatTraceMessage:actualTraceId message:@"webauthn_get completed successfully"]];
            resolve(@{
                @"result": credentials ?: [NSNull null],  // Use NSNull for nil credentials
                @"metadata": metadata ?: [NSNull null]   // Use NSNull for nil metadata
            });
        }
    }];
}

// WebAuthn Retrieve
RCT_EXPORT_METHOD(webauthn_retrieve:(NSString *)identityId
                           traceId:(nullable NSString *)traceId
                           resolver:(RCTPromiseResolveBlock)resolve
                           rejecter:(RCTPromiseRejectBlock)reject) {

    if (!self.client) {
        reject(@"ios::webauthn_retrieve", @"Client is not initialized", nil);
        return;
    }

    NSString *actualTraceId = traceId ?: [self generateTraceId];
    [ZSMClient logWithLevel:LogLevelTrace message:[self formatTraceMessage:actualTraceId message:[NSString stringWithFormat:@"webauthn_retrieve called with identityId: %@", identityId]]];

    // Store the identity mapping for future use (implements the missing lookup functionality)
    NSString *currentConsumerId = self.client.config.consumerId;
    [ZSMClient logWithLevel:LogLevelTrace message:[self formatTraceMessage:actualTraceId message:[NSString stringWithFormat:@"[IDENTITY-MAPPING] Current consumerId: %@", currentConsumerId]]];
    [ZSMClient logWithLevel:LogLevelTrace message:[self formatTraceMessage:actualTraceId message:[NSString stringWithFormat:@"[IDENTITY-MAPPING] identityId parameter: %@", identityId]]];
    
    if (identityId && ![identityId isEqualToString:currentConsumerId]) {
        [ZSMClient logWithLevel:LogLevelTrace message:[self formatTraceMessage:actualTraceId message:[NSString stringWithFormat:@"[IDENTITY-MAPPING] About to store mapping: userId='%@' -> identityId='%@'", currentConsumerId, identityId]]];
        [self storeIdentityMapping:identityId identityId:currentConsumerId];
        [ZSMClient logWithLevel:LogLevelTrace message:[self formatTraceMessage:actualTraceId message:[NSString stringWithFormat:@"[IDENTITY-MAPPING] ✅ Stored identity mapping: userId='%@' -> identityId='%@'", currentConsumerId, identityId]]];
        
        // Verify the storage immediately
        NSString *verifyLookup = [self lookupIdentityId:currentConsumerId];
        [ZSMClient logWithLevel:LogLevelTrace message:[self formatTraceMessage:actualTraceId message:[NSString stringWithFormat:@"[IDENTITY-MAPPING] ✓ Verification lookup for userId='%@' returned: '%@'", currentConsumerId, verifyLookup]]];
    } else {
        [ZSMClient logWithLevel:LogLevelTrace message:[self formatTraceMessage:actualTraceId message:[NSString stringWithFormat:@"[IDENTITY-MAPPING] ⚠️ NOT storing mapping - identityId:'%@' same as consumerId:'%@' or identityId is nil", identityId, currentConsumerId]]];
    }

    // Try to retrieve using the current consumerId first (original userId for storage access)
    [ZSMClient logWithLevel:LogLevelTrace message:[self formatTraceMessage:actualTraceId message:@"Trying webauthnRetrieve with original consumerId for storage access"]];
    [ZSMClient logWithLevel:LogLevelTrace message:[self formatTraceMessage:actualTraceId message:[NSString stringWithFormat:@"Current consumerId (for storage): %@", currentConsumerId]]];
    [ZSMClient logWithLevel:LogLevelTrace message:[self formatTraceMessage:actualTraceId message:[NSString stringWithFormat:@"identityId (for future MPC): %@", identityId]]];
    
    [self.client webauthnRetrieveWithCompletion:^(NSDictionary<NSString *, id> * _Nullable credentials, NSDictionary<NSString *, NSString *> * _Nullable metadata, ZSMError * _Nullable error) {
        [ZSMClient logWithLevel:LogLevelTrace message:[self formatTraceMessage:actualTraceId message:[NSString stringWithFormat:@"webauthnRetrieve callback - result: %@, error: %@", credentials, error]]];
        
        if (credentials) {
            [ZSMClient logWithLevel:LogLevelTrace message:[self formatTraceMessage:actualTraceId message:[NSString stringWithFormat:@"Found credential in storage for consumerId: %@", currentConsumerId]]];
        } else if (!error) {
            // No credentials found with current consumerId, try looking up the identityId as a userId
            NSString *lookupIdentityId = [self lookupIdentityId:identityId];
            if (lookupIdentityId && ![lookupIdentityId isEqualToString:identityId] && ![lookupIdentityId isEqualToString:currentConsumerId]) {
                [ZSMClient logWithLevel:LogLevelTrace message:[self formatTraceMessage:actualTraceId message:[NSString stringWithFormat:@"No credential found for consumerId: %@, trying with mapped identityId: %@", currentConsumerId, lookupIdentityId]]];
                
                // Try with the mapped identity ID
                [self.client webauthnRetrieveForUser:lookupIdentityId withCompletion:^(NSDictionary<NSString *, id> * _Nullable altCredentials, NSDictionary<NSString *, NSString *> * _Nullable altMetadata, ZSMError * _Nullable altError) {
                    if (altCredentials) {
                        [ZSMClient logWithLevel:LogLevelTrace message:[self formatTraceMessage:actualTraceId message:[NSString stringWithFormat:@"Found credential using mapped identityId: %@", lookupIdentityId]]];
                        resolve(@{
                            @"result": altCredentials ?: [NSNull null],
                            @"metadata": altMetadata ?: [NSNull null]
                        });
                    } else {
                        [ZSMClient logWithLevel:LogLevelTrace message:[self formatTraceMessage:actualTraceId message:[NSString stringWithFormat:@"No credential found for mapped identityId: %@", lookupIdentityId]]];
                        resolve(@{
                            @"result": [NSNull null],
                            @"metadata": metadata ?: [NSNull null]
                        });
                    }
                }];
                return;
            } else {
                [ZSMClient logWithLevel:LogLevelTrace message:[self formatTraceMessage:actualTraceId message:[NSString stringWithFormat:@"No credential found in storage for consumerId: %@", currentConsumerId]]];
            }
        }
        
        if (error) {
            [ZSMClient logWithLevel:LogLevelError message:[self formatTraceMessage:actualTraceId message:[NSString stringWithFormat:@"webauthn_retrieve error: %@", error.localizedDescription]]];
            
            // Create rich NSError with all ZSMError properties for JavaScript
            NSError *richError = [NSError errorWithDomain:@"ZSMError" 
                                                     code:error.code 
                                                 userInfo:@{
                                                     @"message": error.message ?: @"Unknown error",
                                                     @"code": @(error.code),
                                                     @"traceId": error.traceId ?: @"",
                                                     @"details": error.description ?: @""
                                                 }];
            
            // Use structured error code and message for consistency with Android
            reject([NSString stringWithFormat:@"ZSM_%ld", (long)error.code], 
                   [NSString stringWithFormat:@"[ZSM %ld] %@ (Trace: %@)", (long)error.code, error.message ?: @"Unknown error", error.traceId ?: @""], 
                   richError);
        } else {
            [ZSMClient logWithLevel:LogLevelTrace message:[self formatTraceMessage:actualTraceId message:@"webauthn_retrieve completed successfully"]];
            resolve(@{
                @"result": credentials ?: [NSNull null],  // Use NSNull for nil credentials
                @"metadata": metadata ?: [NSNull null]   // Use NSNull for nil metadata
            });
        }
    }];
}

- (void)storeIdentityMapping:(NSString *)userId identityId:(NSString *)identityId {
    [ZSMClient logWithLevel:LogLevelTrace message:[NSString stringWithFormat:@"[STORE] storeIdentityMapping called with userId='%@', identityId='%@'", userId, identityId]];
    
    if (!userId || !identityId) {
        [ZSMClient logWithLevel:LogLevelError message:[NSString stringWithFormat:@"[STORE] ❌ Cannot store identity mapping with nil values: userId=%@, identityId=%@", userId, identityId]];
        return;
    }
    
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    NSString *key = [NSString stringWithFormat:@"zsm_identity_mapping_%@", userId];
    [ZSMClient logWithLevel:LogLevelTrace message:[NSString stringWithFormat:@"[STORE] Using storage key: '%@'", key]];
    
    [defaults setObject:identityId forKey:key];
    [defaults synchronize];
    
    // Verify storage worked immediately
    NSString *storedValue = [defaults stringForKey:key];
    [ZSMClient logWithLevel:LogLevelTrace message:[NSString stringWithFormat:@"[STORE] ✅ Stored and verified identity mapping: userId='%@' -> identityId='%@' (verified: '%@')", userId, identityId, storedValue]];
}

- (NSString *)lookupIdentityId:(NSString *)userId {
    [ZSMClient logWithLevel:LogLevelTrace message:[NSString stringWithFormat:@"[LOOKUP] lookupIdentityId called with userId='%@'", userId]];
    
    if (!userId) {
        [ZSMClient logWithLevel:LogLevelError message:@"[LOOKUP] ❌ Cannot lookup identity mapping with nil userId"];
        return userId;
    }
    
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    NSString *key = [NSString stringWithFormat:@"zsm_identity_mapping_%@", userId];
    [ZSMClient logWithLevel:LogLevelTrace message:[NSString stringWithFormat:@"[LOOKUP] Looking up storage key: '%@'", key]];
    
    NSString *identityId = [defaults stringForKey:key];
    
    if (identityId) {
        [ZSMClient logWithLevel:LogLevelTrace message:[NSString stringWithFormat:@"[LOOKUP] ✅ Found identity mapping: userId='%@' -> identityId='%@'", userId, identityId]];
        return identityId;
    } else {
        [ZSMClient logWithLevel:LogLevelTrace message:[NSString stringWithFormat:@"[LOOKUP] ⚠️ No identity mapping found for userId='%@', returning original userId", userId]];
        return userId;
    }
}

@end
