package io.fullstack.firestack;

import android.content.Context;
import android.util.Log;
import java.util.Map;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;

import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.ReadableNativeMap;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.facebook.react.bridge.ReactContext;

import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.OnFailureListener;
import com.google.android.gms.tasks.Task;
import com.google.firebase.FirebaseApp;
import com.google.firebase.FirebaseOptions;

import com.google.firebase.auth.AuthCredential;
import com.google.firebase.auth.AuthResult;
import com.google.firebase.auth.UserProfileChangeRequest;
import com.google.firebase.auth.FacebookAuthProvider;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseUser;
import com.google.firebase.auth.GetTokenResult;
import com.google.firebase.auth.GoogleAuthProvider;
import com.google.firebase.auth.FirebaseAuthException;
import com.google.firebase.auth.TwitterAuthProvider;

class FirestackAuthModule extends ReactContextBaseJavaModule {
  private final int NO_CURRENT_USER = 100;
  private final int ERROR_FETCHING_TOKEN = 101;

  private static final String TAG = "FirestackAuth";

  private Context context;
  private ReactContext mReactContext;
  private FirebaseAuth mAuth;
  private FirebaseApp app;
  private FirebaseUser user;
  private FirebaseAuth.AuthStateListener mAuthListener;

  public FirestackAuthModule(ReactApplicationContext reactContext) {
    super(reactContext);
    this.context = reactContext;
    mReactContext = reactContext;

    Log.d(TAG, "New FirestackAuth instance");
  }

  @Override
  public String getName() {
    return TAG;
  }

  @ReactMethod
  public void listenForAuth() {
    mAuthListener = new FirebaseAuth.AuthStateListener() {

    @Override
    public void onAuthStateChanged(@NonNull FirebaseAuth firebaseAuth) {
        WritableMap msgMap = Arguments.createMap();
        msgMap.putString("eventName", "listenForAuth");

        if (firebaseAuth.getCurrentUser() != null) {
            WritableMap userMap = getUserMap();

            msgMap.putBoolean("authenticated", true);
            msgMap.putMap("user", userMap);

            FirestackUtils.sendEvent(mReactContext, "listenForAuth", msgMap);
        } else {
            msgMap.putBoolean("authenticated", false);
            FirestackUtils.sendEvent(mReactContext, "listenForAuth", msgMap);
        }
        }
      };

      mAuth = FirebaseAuth.getInstance();
      mAuth.addAuthStateListener(mAuthListener);
    }

    @ReactMethod
    public void unlistenForAuth(final Callback callback) {
      if (mAuthListener != null) {
        mAuth.removeAuthStateListener(mAuthListener);

        WritableMap resp = Arguments.createMap();
        resp.putString("status", "complete");

        callback.invoke(null, resp);
      }
    }

    @ReactMethod
    public void createUserWithEmail(final String email, final String password, final Callback callback) {
      mAuth = FirebaseAuth.getInstance();

      mAuth.createUserWithEmailAndPassword(email, password)
              .addOnCompleteListener(new OnCompleteListener<AuthResult>() {
                  @Override
                  public void onComplete(@NonNull Task<AuthResult> task) {
                      if (task.isSuccessful()) {
                        FirestackAuthModule.this.user = task.getResult().getUser();
                        userCallback(FirestackAuthModule.this.user, callback);
                      } else {
                        // userErrorCallback(task, callback);
                      }
                  }
            }).addOnFailureListener(new OnFailureListener() {
              @Override
              public void onFailure(@NonNull Exception ex) {
                userExceptionCallback(ex, callback);
              }
            });
    }

    @ReactMethod
    public void signInWithEmail(final String email, final String password, final Callback callback) {
        mAuth = FirebaseAuth.getInstance();

        mAuth.signInWithEmailAndPassword(email, password)
          .addOnCompleteListener(new OnCompleteListener<AuthResult>() {
              @Override
              public void onComplete(@NonNull Task<AuthResult> task) {
                  if (task.isSuccessful()) {
                    FirestackAuthModule.this.user = task.getResult().getUser();
                    userCallback(FirestackAuthModule.this.user, callback);
                  } else {
                    // userErrorCallback(task, callback);
                  }
                }
            }).addOnFailureListener(new OnFailureListener() {
              @Override
              public void onFailure(@NonNull Exception ex) {
                Log.e(TAG, "An exception occurred: " + ex.getMessage());
                userExceptionCallback(ex, callback);
              }
            });
    }

    @ReactMethod
    public void signInWithProvider(final String provider, final String authToken, final String authSecret, final Callback callback) {
      if (provider.equals("facebook")) {
           this.facebookLogin(authToken,callback);
      } else if (provider.equals("google")) {
           this.googleLogin(authToken,callback);
      } else if (provider.equals("twitter")) {
           this.twitterLogin(authToken,authSecret,callback);
      } else
      // TODO
      FirestackUtils.todoNote(TAG, "signInWithProvider", callback);
    }

    @ReactMethod
    public void signInAnonymously(final Callback callback) {
        mAuth = FirebaseAuth.getInstance();

        mAuth.signInAnonymously()
                .addOnCompleteListener(new OnCompleteListener<AuthResult>() {
                    @Override
                    public void onComplete(@NonNull Task<AuthResult> task) {
                        Log.d(TAG, "signInAnonymously:onComplete:" + task.isSuccessful());

                        if (task.isSuccessful()) {
                          FirestackAuthModule.this.user = task.getResult().getUser();
                          userCallback(FirestackAuthModule.this.user, callback);
                        } else {
                          // userErrorCallback(task, callback);
                        }
                      }
              }).addOnFailureListener(new OnFailureListener() {
                @Override
                public void onFailure(@NonNull Exception ex) {
                  userExceptionCallback(ex, callback);
                }
              });
    }

    @ReactMethod
    public void signInWithCustomToken(final String customToken, final Callback callback) {
      mAuth = FirebaseAuth.getInstance();

      mAuth.signInWithCustomToken(customToken)
        .addOnCompleteListener(new OnCompleteListener<AuthResult>() {
            @Override
            public void onComplete(@NonNull Task<AuthResult> task) {
                Log.d(TAG, "signInWithCustomToken:onComplete:" + task.isSuccessful());
                  if (task.isSuccessful()) {
                      FirestackAuthModule.this.user = task.getResult().getUser();
                    userCallback(FirestackAuthModule.this.user, callback);
                  } else {
                      // userErrorCallback(task, callback);
                  }
            }
        }).addOnFailureListener(new OnFailureListener() {
              @Override
              public void onFailure(@NonNull Exception ex) {
                userExceptionCallback(ex, callback);
              }
            });
    }

    @ReactMethod
    public void reauthenticateWithCredentialForProvider(final String provider, final String authToken, final String authSecret, final Callback callback) {
        AuthCredential credential;

        if (provider.equals("facebook")) {
            credential = FacebookAuthProvider.getCredential(authToken);
        } else if (provider.equals("google")) {
            credential = GoogleAuthProvider.getCredential(authToken, null);
        } else if (provider.equals("twitter")) {
            credential = TwitterAuthProvider.getCredential(authToken, authSecret);
        } else {
            // TODO:
            FirestackUtils.todoNote(TAG, "reauthenticateWithCredentialForProvider", callback);
            // AuthCredential credential;
            // Log.d(TAG, "reauthenticateWithCredentialForProvider called with: " + provider);
            return;
        }

        FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();
        if (user != null) {
            user.reauthenticate(credential)
                .addOnCompleteListener(new OnCompleteListener<Void>() {
                    @Override
                    public void onComplete(@NonNull Task<Void> task) {
                        if (task.isSuccessful()) {
                            Log.d(TAG, "User re-authenticated with " + provider);
                            FirebaseUser u = FirebaseAuth.getInstance().getCurrentUser();
                            userCallback(u, callback);
                        } else {
                            // userErrorCallback(task, callback);
                        }
                    }
                });
        } else {
            WritableMap err = Arguments.createMap();
            err.putInt("errorCode", NO_CURRENT_USER);
            err.putString("errorMessage", "No current user");
            callback.invoke(err);
        }
    }

    @ReactMethod
    public void updateUserEmail(final String email, final Callback callback) {
      FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();

      if (user != null) {
        user.updateEmail(email)
          .addOnCompleteListener(new OnCompleteListener<Void>() {
            @Override
            public void onComplete(@NonNull Task<Void> task) {
              if (task.isSuccessful()) {
                Log.d(TAG, "User email address updated");
                FirebaseUser u = FirebaseAuth.getInstance().getCurrentUser();
                userCallback(u, callback);
              } else {
                // userErrorCallback(task, callback);
              }
            }
          }).addOnFailureListener(new OnFailureListener() {
              @Override
              public void onFailure(@NonNull Exception ex) {
                userExceptionCallback(ex, callback);
              }
            });
      } else {
        WritableMap err = Arguments.createMap();
        err.putInt("errorCode", NO_CURRENT_USER);
        err.putString("errorMessage", "No current user");
        callback.invoke(err);
      }
    }

    @ReactMethod
    public void updateUserPassword(final String newPassword, final Callback callback) {
      FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();

      if (user != null) {
        user.updatePassword(newPassword)
          .addOnCompleteListener(new OnCompleteListener<Void>() {
            @Override
            public void onComplete(@NonNull Task<Void> task) {
              if (task.isSuccessful()) {
                Log.d(TAG, "User password updated");

                FirebaseUser u = FirebaseAuth.getInstance().getCurrentUser();
                userCallback(u, callback);
              } else {
                // userErrorCallback(task, callback);
              }
            }
          }).addOnFailureListener(new OnFailureListener() {
              @Override
              public void onFailure(@NonNull Exception ex) {
                userExceptionCallback(ex, callback);
              }
            });
      } else {
        WritableMap err = Arguments.createMap();
        err.putInt("errorCode", NO_CURRENT_USER);
        err.putString("errorMessage", "No current user");
        callback.invoke(err);
      }
    }

    @ReactMethod
    public void sendPasswordResetWithEmail(final String email, final Callback callback) {
        mAuth = FirebaseAuth.getInstance();

        mAuth.sendPasswordResetEmail(email)
                .addOnCompleteListener(new OnCompleteListener<Void>() {
                    @Override
                    public void onComplete(@NonNull Task<Void> task) {
                        if(task.isSuccessful()){
                            WritableMap resp = Arguments.createMap();
                            resp.putString("status", "complete");
                            callback.invoke(null, resp);
                        } else {
                            callback.invoke(task.getException().toString());
                        }
                    }
              }).addOnFailureListener(new OnFailureListener() {
              @Override
              public void onFailure(@NonNull Exception ex) {
                userExceptionCallback(ex, callback);
              }
            });
    }

    @ReactMethod
    public void deleteUser(final Callback callback) {
      FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();

      if (user != null) {
        user.delete()
          .addOnCompleteListener(new OnCompleteListener<Void>() {
            @Override
            public void onComplete(@NonNull Task<Void> task) {
              if (task.isSuccessful()) {
                Log.d(TAG, "User account deleted");
                WritableMap resp = Arguments.createMap();
                resp.putString("status", "complete");
                resp.putString("msg", "User account deleted");
                callback.invoke(null, resp);
              } else {
                // userErrorCallback(task, callback);
              }
            }
          }).addOnFailureListener(new OnFailureListener() {
              @Override
              public void onFailure(@NonNull Exception ex) {
                userExceptionCallback(ex, callback);
              }
            });
      } else {
        WritableMap err = Arguments.createMap();
        err.putInt("errorCode", NO_CURRENT_USER);
        err.putString("errorMessage", "No current user");
        callback.invoke(err);
      }
    }

    @ReactMethod
    public void getToken(final Callback callback) {
      FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();

      user.getToken(true)
        .addOnCompleteListener(new OnCompleteListener<GetTokenResult>() {
          @Override
          public void onComplete(@NonNull Task<GetTokenResult> task) {
            if (task.isSuccessful()) {
              String token = task.getResult().getToken();
              WritableMap resp = Arguments.createMap();
              resp.putString("status", "complete");
              resp.putString("token", token);
              callback.invoke(null, resp);
            } else {
              WritableMap err = Arguments.createMap();
              err.putInt("errorCode", ERROR_FETCHING_TOKEN);
              err.putString("errorMessage", task.getException().getMessage());
              callback.invoke(err);
            }
          }
        }).addOnFailureListener(new OnFailureListener() {
              @Override
              public void onFailure(@NonNull Exception ex) {
                userExceptionCallback(ex, callback);
              }
            });
    }

    @ReactMethod
    public void updateUserProfile(ReadableMap props, final Callback callback) {
      FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();

      UserProfileChangeRequest.Builder profileBuilder = new UserProfileChangeRequest.Builder();

      Map<String, Object> m = FirestackUtils.recursivelyDeconstructReadableMap(props);

      if (m.containsKey("displayName")) {
        String displayName = (String) m.get("displayName");
        profileBuilder.setDisplayName(displayName);
      }

      if (m.containsKey("photoUri")) {
        String photoUriStr = (String) m.get("photoUri");
        Uri uri = Uri.parse(photoUriStr);
        profileBuilder.setPhotoUri(uri);
      }

      UserProfileChangeRequest profileUpdates = profileBuilder.build();

      user.updateProfile(profileUpdates)
        .addOnCompleteListener(new OnCompleteListener<Void>() {
          @Override
          public void onComplete(@NonNull Task<Void> task) {
            if (task.isSuccessful()) {
              Log.d(TAG, "User profile updated");
              FirebaseUser u = FirebaseAuth.getInstance().getCurrentUser();
              userCallback(u, callback);
            } else {
              // userErrorCallback(task, callback);
            }
          }
        }).addOnFailureListener(new OnFailureListener() {
              @Override
              public void onFailure(@NonNull Exception ex) {
                userExceptionCallback(ex, callback);
              }
            });
    }

    @ReactMethod
    public void signOut(final Callback callback) {
      FirebaseAuth.getInstance().signOut();
      this.user = null;

      WritableMap resp = Arguments.createMap();
      resp.putString("status", "complete");
      resp.putString("msg", "User signed out");
      callback.invoke(null, resp);
    }

    @ReactMethod
    public void getCurrentUser(final Callback callback) {
        mAuth = FirebaseAuth.getInstance();

        this.user = mAuth.getCurrentUser();
        if(this.user == null){
            noUserCallback(callback);
        }else{
            userCallback(this.user, callback);
        }
    }

    // TODO: Check these things
    @ReactMethod
    public void googleLogin(String IdToken, final Callback callback) {
        mAuth = FirebaseAuth.getInstance();

        AuthCredential credential = GoogleAuthProvider.getCredential(IdToken, null);
        mAuth.signInWithCredential(credential)
                .addOnCompleteListener(new OnCompleteListener<AuthResult>() {
                    @Override
                    public void onComplete(@NonNull Task<AuthResult> task) {
                      if (task.isSuccessful()) {
                          FirestackAuthModule.this.user = task.getResult().getUser();
                          userCallback(FirestackAuthModule.this.user, callback);
                      }else{
                          // userErrorCallback(task, callback);
                      }
                    }
                }).addOnFailureListener(new OnFailureListener() {
              @Override
              public void onFailure(@NonNull Exception ex) {
                userExceptionCallback(ex, callback);
              }
            });
    }

    @ReactMethod
    public void facebookLogin(String Token, final Callback callback) {
        mAuth = FirebaseAuth.getInstance();

        AuthCredential credential = FacebookAuthProvider.getCredential(Token);
        mAuth.signInWithCredential(credential)
                .addOnCompleteListener(new OnCompleteListener<AuthResult>() {
                    @Override
                    public void onComplete(@NonNull Task<AuthResult> task) {
                      if (task.isSuccessful()) {
                          FirestackAuthModule.this.user = task.getResult().getUser();
                          userCallback(FirestackAuthModule.this.user, callback);
                      }else{
                          // userErrorCallback(task, callback);
                      }
                    }
                }).addOnFailureListener(new OnFailureListener() {
              @Override
              public void onFailure(@NonNull Exception ex) {
                userExceptionCallback(ex, callback);
              }
            });
    }

    @ReactMethod
    public void twitterLogin(String Token, String Secret, final Callback callback) {
        mAuth = FirebaseAuth.getInstance();

        AuthCredential credential = TwitterAuthProvider.getCredential(Token, Secret);
        mAuth.signInWithCredential(credential)
                .addOnCompleteListener(new OnCompleteListener<AuthResult>() {
                    @Override
                    public void onComplete(@NonNull Task<AuthResult> task) {
                      if (task.isSuccessful()) {
                          FirestackAuthModule.this.user = task.getResult().getUser();
                          userCallback(FirestackAuthModule.this.user, callback);
                      }else{
                          // userErrorCallback(task, callback);
                      }
                    }
                }).addOnFailureListener(new OnFailureListener() {
              @Override
              public void onFailure(@NonNull Exception ex) {
                userExceptionCallback(ex, callback);
              }
            });
    }

    // Internal helpers
    public void userCallback(FirebaseUser passedUser, final Callback callback) {

        if (passedUser == null) {
          mAuth = FirebaseAuth.getInstance();
          this.user = mAuth.getCurrentUser();
        } else {
          this.user = passedUser;
        }

        this.user.getToken(false).addOnCompleteListener(new OnCompleteListener<GetTokenResult>() {
            @Override
            public void onComplete(@NonNull Task<GetTokenResult> task) {
                WritableMap msgMap = Arguments.createMap();
                WritableMap userMap = getUserMap();
                boolean authenticated = false;
                if (FirestackAuthModule.this.user != null) {
                    final String token = task.getResult().getToken();
                    userMap.putString("token", token);
                    authenticated = true;
                }

                msgMap.putMap("user", userMap);
                msgMap.putBoolean("authenticated", authenticated);
                callback.invoke(null, msgMap);
            }
        }).addOnFailureListener(new OnFailureListener() {
              @Override
              public void onFailure(@NonNull Exception ex) {
                userExceptionCallback(ex, callback);
              }
            });
    }

    public void noUserCallback(final Callback callback) {
        WritableMap message = Arguments.createMap();

        message.putString("errorMessage", "no_user");
        message.putString("eventName", "no_user");
        message.putBoolean("authenticated", false);

        callback.invoke(null, message);
    }

    public void userErrorCallback(Task task, final Callback onFail) {
      userExceptionCallback(task.getException(), onFail);
    }

    public void userExceptionCallback(Exception exp, final Callback onFail) {
      WritableMap error = Arguments.createMap();
      error.putString("errorMessage",     exp.getMessage());
      error.putString("allErrorMessage",  exp.toString());

      try {
        throw exp;
      } catch (FirebaseAuthException ex) {
        error.putString("errorCode", ex.getErrorCode());
      } catch (Exception ex) {
        Log.e(TAG, ex.getMessage());
      }

      onFail.invoke(error);
    }

    private WritableMap getUserMap() {
        WritableMap userMap = Arguments.createMap();

        FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();

        if (user != null) {
          final String email = user.getEmail();
          final String uid   = user.getUid();
          final String provider = user.getProviderId();
          final String name = user.getDisplayName();
          final Uri photoUrl = user.getPhotoUrl();

          userMap.putString("email", email);
          userMap.putString("uid", uid);
          userMap.putString("providerId", provider);
          userMap.putBoolean("emailVerified", user.isEmailVerified());
          userMap.putBoolean("anonymous", user.isAnonymous());
          if (name != null) {
            userMap.putString("displayName", name);
          }

          if (photoUrl != null) {
            userMap.putString("photoUrl", photoUrl.toString());
          }
        } else {
          userMap.putString("msg", "no user");
        }

        return userMap;
    }
}