//
//  FirestackAuth.m
//  Firestack
//
//  Created by Ari Lerner on 8/23/16.
//  Copyright © 2016 Facebook. All rights reserved.
//

#import "FirestackAuth.h"
#import "FirestackErrors.h"
#import "FirestackEvents.h"

@import Firebase;

@implementation FirestackAuth

typedef void (^UserWithTokenResponse)(NSDictionary *, NSError *);

RCT_EXPORT_MODULE(FirestackAuth);

RCT_EXPORT_METHOD(signInWithCustomToken:
                  (NSString *)customToken
                  callback:(RCTResponseSenderBlock) callback)
{
    [[FIRAuth auth]
     signInWithCustomToken:customToken
     completion:^(FIRUser *user, NSError *error) {
         
         if (user != nil) {
             NSDictionary *userProps = [self userPropsFromFIRUser:user];
             callback(@[[NSNull null], userProps]);
         } else {
             NSDictionary *err =
             [FirestackErrors handleFirebaseError:@"signinError"
                                 error:error
                              withUser:user];
             callback(@[err]);
         }
     }];
}

RCT_EXPORT_METHOD(signInWithProvider:
                  (NSString *)provider
                  token:(NSString *)authToken
                  secret:(NSString *)authTokenSecret
                  callback:(RCTResponseSenderBlock)callback)
{
    FIRAuthCredential *credential = [self getCredentialForProvider:provider
                                                             token:authToken
                                                            secret:authTokenSecret];
    if (credential == nil) {
        NSDictionary *err = @{
                              @"error": @"Unhandled provider"
                              };
        return callback(@[err]);
    }
    
    @try {
        [[FIRAuth auth] signInWithCredential:credential
                                  completion:^(FIRUser *user, NSError *error) {
                                      if (user != nil) {
                                          // User is signed in.
                                          NSDictionary *userProps = [self userPropsFromFIRUser:user];
                                          callback(@[[NSNull null], userProps]);
                                      } else {
                                          NSLog(@"An error occurred: %@", [error localizedDescription]);
                                          NSLog(@"Error: %@", error);
                                          // No user is signed in.
                                          NSDictionary *err = @{
                                                                @"error": @"No user signed in",
                                                                @"description": [error localizedDescription]
                                                                };
                                          callback(@[err]);
                                      }
                                  }];
    } @catch (NSException *exception) {
        [FirestackErrors handleException:exception
                            withCallback:callback];
    }
}

RCT_EXPORT_METHOD(signOut:(RCTResponseSenderBlock)callback)
{
    NSError *error;
    [[FIRAuth auth] signOut:&error];
    if (!error) {
        // Sign-out succeeded
        callback(@[[NSNull null], @YES]);
    } else {
        NSDictionary *err = @{
                              @"error": @"Signout error",
                              @"name": @([error code]),
                              @"description": [error description]
                              };
        callback(@[err]);
    }
}

RCT_EXPORT_METHOD(listenForAuth)
{
    self->authListenerHandle =
    [[FIRAuth auth] addAuthStateDidChangeListener:^(FIRAuth *_Nonnull auth,
                                                    FIRUser *_Nullable user) {
        
        if (user != nil) {
            // User is signed in.
            [self userPropsFromFIRUserWithToken:user
                                    andCallback:^(NSDictionary *userProps, NSError * error) {
                                        if (error != nil) {
                                            [self
                                             sendJSEvent:AUTH_CHANGED_EVENT
                                             props: @{
                                                      @"eventName": @"userTokenError",
                                                      @"msg": [error localizedFailureReason]
                                                      }];
                                        } else {
                                            [self
                                             sendJSEvent:AUTH_CHANGED_EVENT
                                             props: @{
                                                      @"eventName": @"user",
                                                      @"authenticated": @(true),
                                                      @"user": userProps
                                                      }];
                                        }
                                    }];
        } else {
            // TODO: Update this with different error states
            NSDictionary *err = @{
                                  @"error": @"No user logged in"
                                  };
            [self sendJSEvent:AUTH_CHANGED_EVENT
                        props:@{
                                @"eventName": @"no_user",
                                @"authenticated": @(false),
                                @"error": err
                                }];
        }
    }];
}

RCT_EXPORT_METHOD(unlistenForAuth:(RCTResponseSenderBlock)callback)
{
    if (self->authListenerHandle != nil) {
        [[FIRAuth auth] removeAuthStateDidChangeListener:self->authListenerHandle];
        callback(@[[NSNull null]]);
    }
}

RCT_EXPORT_METHOD(getCurrentUser:(RCTResponseSenderBlock)callback)
{
    FIRUser *user = [FIRAuth auth].currentUser;
    
    if (user != nil) {
        NSDictionary *userProps = [self userPropsFromFIRUser:user];
        callback(@[[NSNull null], userProps]);
    } else {
        // No user is signed in.
        NSDictionary *err = @{
                              @"user": @"No user logged in"
                              };
        callback(@[err]);
    }
}

RCT_EXPORT_METHOD(createUserWithEmail:(NSString *)email
                  pass:(NSString *)password
                  callback:(RCTResponseSenderBlock) callback)
{
    [[FIRAuth auth]
     createUserWithEmail:email
     password:password
     completion:^(FIRUser *_Nullable user,
                  NSError *_Nullable error) {
         if (user != nil) {
             NSDictionary *userProps = [self userPropsFromFIRUser:user];
             callback(@[[NSNull null], userProps]);
         } else {
             NSDictionary *err = @{
                                   @"error": @"createUserWithEmailError",
                                   @"name": @([error code]),
                                   @"description": [error description]
                                   };
             callback(@[err]);
         }
     }];
}

RCT_EXPORT_METHOD(signInWithEmail:(NSString *)email
                  pass:(NSString *)password
                  callback:(RCTResponseSenderBlock) callback)
{
    [[FIRAuth auth] signInWithEmail:email
                           password:password
                         completion:^(FIRUser *user, NSError *error) {
                             if (user != nil) {
                                 NSDictionary *userProps = [self userPropsFromFIRUser:user];
                                 
                                 callback(@[[NSNull null], @{
                                                @"user": userProps
                                                }]);
                             } else {
                                 NSDictionary *err =
                                 [FirestackErrors handleFirebaseError:@"signinError"
                                                     error:error
                                                  withUser:user];
                                 callback(@[err]);
                             }
                         }];
}

RCT_EXPORT_METHOD(updateUserEmail:(NSString *)email
                  callback:(RCTResponseSenderBlock) callback)
{
    FIRUser *user = [FIRAuth auth].currentUser;
    
    [user updateEmail:email completion:^(NSError *_Nullable error) {
        if (error) {
            // An error happened.
            NSDictionary *err =
            [FirestackErrors handleFirebaseError:@"updateEmailError"
                                error:error
                             withUser:user];
            callback(@[err]);
        } else {
            // Email updated.
            NSDictionary *userProps = [self userPropsFromFIRUser:user];
            callback(@[[NSNull null], userProps]);
        }
    }];
}

RCT_EXPORT_METHOD(updateUserPassword:(NSString *)newPassword
                  callback:(RCTResponseSenderBlock) callback)
{
    
    FIRUser *user = [FIRAuth auth].currentUser;
    
    [user updatePassword:newPassword completion:^(NSError *_Nullable error) {
        if (error) {
            // An error happened.
            NSDictionary *err =
            [FirestackErrors handleFirebaseError:@"updateUserPasswordError"
                                error:error
                             withUser:user];
            callback(@[err]);
        } else {
            // Email updated.
            NSDictionary *userProps = [self userPropsFromFIRUser:user];
            callback(@[[NSNull null], userProps]);
        }
    }];
}

RCT_EXPORT_METHOD(sendPasswordResetWithEmail:(NSString *)email
                  callback:(RCTResponseSenderBlock) callback)
{
    
    [[FIRAuth auth] sendPasswordResetWithEmail:email
                                    completion:^(NSError *_Nullable error) {
                                        if (error) {
                                            // An error happened.
                                            NSDictionary *err = @{
                                                                  @"error": @"sendPasswordResetWithEmailError",
                                                                  @"description": error.localizedDescription
                                                                  };
                                            callback(@[err]);
                                        } else {
                                            // Email updated.
                                            callback(@[[NSNull null], @{
                                                           @"result": @(true)
                                                           }]);
                                        }
                                    }];
}

RCT_EXPORT_METHOD(deleteUser:(RCTResponseSenderBlock) callback)
{
    FIRUser *user = [FIRAuth auth].currentUser;
    
    [user deleteWithCompletion:^(NSError *_Nullable error) {
        if (error) {
            NSDictionary *err =
            [FirestackErrors handleFirebaseError:@"deleteUserError"
                                error:error
                             withUser:user];
            callback(@[err]);
        } else {
            callback(@[[NSNull null], @{@"result": @(true)}]);
        }
    }];
}

RCT_EXPORT_METHOD(getToken:(RCTResponseSenderBlock) callback)
{
    FIRUser *user = [FIRAuth auth].currentUser;
    
    [user getTokenWithCompletion:^(NSString *token, NSError *_Nullable error) {
        if (error) {
            NSDictionary *err =
            [FirestackErrors handleFirebaseError:@"getTokenError"
                                error:error
                             withUser:user];
            callback(@[err]);
        } else {
            NSDictionary *userProps = [self userPropsFromFIRUser:user];
            callback(@[[NSNull null], @{@"token": token, @"user": userProps}]);
        }
    }];
}

RCT_EXPORT_METHOD(getTokenWithCompletion:(RCTResponseSenderBlock) callback)
{
    FIRUser *user = [FIRAuth auth].currentUser;
    
    [user getTokenWithCompletion:^(NSString *token , NSError *_Nullable error) {
        if (error) {
            NSDictionary *err =
            [FirestackErrors handleFirebaseError:@"getTokenWithCompletion"
                                error:error
                             withUser:user];
            callback(@[err]);
        } else {
            NSDictionary *userProps = [self userPropsFromFIRUser:user];
            callback(@[[NSNull null], @{@"token": token, @"user": userProps}]);
        }
    }];
}

RCT_EXPORT_METHOD(reauthenticateWithCredentialForProvider:
                  (NSString *)provider
                  token:(NSString *)authToken
                  secret:(NSString *)authTokenSecret
                  callback:(RCTResponseSenderBlock)callback)
{
    FIRAuthCredential *credential = [self getCredentialForProvider:provider
                                                             token:authToken
                                                            secret:authTokenSecret];
    if (credential == nil) {
        NSDictionary *err = @{
                              @"error": @"Unhandled provider"
                              };
        return callback(@[err]);
    }
    
    FIRUser *user = [FIRAuth auth].currentUser;
    
    [user reauthenticateWithCredential:credential completion:^(NSError *_Nullable error) {
        if (error) {
            NSDictionary *err =
            [FirestackErrors handleFirebaseError:@"reauthenticateWithCredentialForProviderError"
                                error:error
                             withUser:user];
            callback(@[err]);
        } else {
            callback(@[[NSNull null], @{@"result": @(true)}]);
        }
    }];
}


RCT_EXPORT_METHOD(updateUserProfile:(NSDictionary *)userProps
                  callback:(RCTResponseSenderBlock) callback)
{
    FIRUser *user = [FIRAuth auth].currentUser;
    FIRUserProfileChangeRequest *changeRequest = [user profileChangeRequest];
    
    NSMutableArray *allKeys = [[userProps allKeys] mutableCopy];
    for (NSString *key in allKeys) {
        // i.e. changeRequest.displayName = userProps[displayName];
        @try {
            if ([key isEqualToString:@"photoURL"]) {
                NSURL *url = [NSURL URLWithString:[userProps valueForKey:key]];
                [changeRequest setValue:url forKey:key];
            } else {
                [changeRequest setValue:[userProps objectForKey:key] forKey:key];
            }
        }
        @catch (NSException *exception) {
            NSLog(@"Exception occurred while configuring: %@", exception);
        }
        @finally {
            [changeRequest commitChangesWithCompletion:^(NSError *_Nullable error) {
                if (error) {
                    // An error happened.
                    NSDictionary *err =
                    [FirestackErrors handleFirebaseError:@"updateEmailError"
                                        error:error
                                     withUser:user];
                    callback(@[err]);
                } else {
                    // Profile updated.
                    NSDictionary *userProps = [self userPropsFromFIRUser:user];
                    callback(@[[NSNull null], userProps]);
                }
            }];
        }
    }
}

- (NSDictionary *) userPropsFromFIRUser:(FIRUser *) user
{
    NSMutableDictionary *userProps = [@{
                                        @"uid": user.uid,
                                        @"email": user.email ? user.email : @"",
                                        @"emailVerified": @(user.emailVerified),
                                        @"anonymous": @(user.anonymous),
                                        @"displayName": user.displayName ? user.displayName : @"",
                                        @"refreshToken": user.refreshToken,
                                        @"providerID": user.providerID
                                        } mutableCopy];
    
    if ([user valueForKey:@"photoURL"] != nil) {
        [userProps setValue: [NSString stringWithFormat:@"%@", user.photoURL]
                     forKey:@"photoURL"];
    }
    
    return userProps;
}

- (void) userPropsFromFIRUserWithToken:(FIRUser *) user
                           andCallback:(UserWithTokenResponse) callback
{
    NSMutableDictionary *userProps = [[self userPropsFromFIRUser:user] mutableCopy];
    [user getTokenWithCompletion:^(NSString * _Nullable token, NSError * _Nullable error) {
        if (error != nil) {
            return callback(nil, error);
        }
        
        [userProps setValue:token forKey:@"idToken"];
        callback(userProps, nil);
    }];
}

- (FIRAuthCredential *)getCredentialForProvider:(NSString *)provider
                                          token:(NSString *)authToken
                                         secret:(NSString *)authTokenSecret
{
    FIRAuthCredential *credential;
    if ([provider isEqualToString: @"twitter"]) {
        credential = [FIRTwitterAuthProvider credentialWithToken:authToken
                                                          secret:authTokenSecret];
    } if ([provider isEqualToString: @"facebook"]) {
        credential = [FIRFacebookAuthProvider credentialWithAccessToken:authToken];
    } if ([provider isEqualToString: @"google"]) {
        credential = [FIRGoogleAuthProvider credentialWithIDToken:authToken
                                                      accessToken:authTokenSecret];
    } else {
        NSLog(@"Provider not yet handled");
    }
    return credential;
}

// Not sure how to get away from this... yet
- (NSArray<NSString *> *)supportedEvents {
    return @[AUTH_CHANGED_EVENT];
}

- (void) sendJSEvent:(NSString *)title
               props:(NSDictionary *)props
{
    @try {
        [self sendEventWithName:title
                           body:props];
    }
    @catch (NSException *err) {
        NSLog(@"An error occurred in sendJSEvent: %@", [err debugDescription]);
    }
}


@end
