package com.firebase.ui.auth.viewmodel.idp;

import android.app.Activity;
import android.app.Application;
import android.content.Intent;
import android.text.TextUtils;

import com.firebase.ui.auth.ErrorCodes;
import com.firebase.ui.auth.FirebaseUiException;
import com.firebase.ui.auth.IdpResponse;
import com.firebase.ui.auth.data.model.IntentRequiredException;
import com.firebase.ui.auth.data.model.Resource;
import com.firebase.ui.auth.data.model.User;
import com.firebase.ui.auth.data.remote.ProfileMerger;
import com.firebase.ui.auth.ui.email.WelcomeBackEmailLinkPrompt;
import com.firebase.ui.auth.ui.email.WelcomeBackPasswordPrompt;
import com.firebase.ui.auth.ui.idp.WelcomeBackIdpPrompt;
import com.firebase.ui.auth.util.FirebaseAuthError;
import com.firebase.ui.auth.util.data.AuthOperationManager;
import com.firebase.ui.auth.util.data.ProviderUtils;
import com.firebase.ui.auth.viewmodel.RequestCodes;
import com.firebase.ui.auth.viewmodel.SignInViewModelBase;
import com.google.android.gms.tasks.OnFailureListener;
import com.google.android.gms.tasks.OnSuccessListener;
import com.google.firebase.auth.AuthCredential;
import com.google.firebase.auth.AuthResult;
import com.google.firebase.auth.EmailAuthProvider;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseAuthException;
import com.google.firebase.auth.FirebaseAuthInvalidUserException;
import com.google.firebase.auth.FirebaseAuthUserCollisionException;
import com.google.firebase.auth.PhoneAuthProvider;

import java.util.List;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;

import static com.firebase.ui.auth.AuthUI.EMAIL_LINK_PROVIDER;

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public class SocialProviderResponseHandler extends SignInViewModelBase {
    public SocialProviderResponseHandler(Application application) {
        super(application);
    }

    public void startSignIn(@NonNull final IdpResponse response) {
        if (!response.isSuccessful() && !response.isRecoverableErrorResponse()) {
            setResult(Resource.<IdpResponse>forFailure(response.getError()));
            return;
        }

        if (isEmailOrPhoneProvider(response.getProviderType())) {
            throw new IllegalStateException(
                    "This handler cannot be used with email or phone providers");
        }

        setResult(Resource.<IdpResponse>forLoading());

        // Recoverable error flows (linking) for Generic OAuth providers are handled here.
        // For Generic OAuth providers, the credential is set on the IdpResponse, as
        // a credential made from the id token/access token cannot be used to sign-in.
        if (response.hasCredentialForLinking()) {
            handleGenericIdpLinkingFlow(response);
            return;
        }

        final AuthCredential credential = ProviderUtils.getAuthCredential(response);
        AuthOperationManager.getInstance().signInAndLinkWithCredential(
                getAuth(),
                getArguments(),
                credential)
                .continueWithTask(new ProfileMerger(response))
                .addOnSuccessListener(new OnSuccessListener<AuthResult>() {
                    @Override
                    public void onSuccess(AuthResult result) {
                        handleSuccess(response, result);
                    }
                })
                .addOnFailureListener(new OnFailureListener() {
                    @Override
                    public void onFailure(@NonNull Exception e) {
                        // For some reason disabled users can hit FirebaseAuthUserCollisionException
                        // so we have to handle this special case.
                        boolean isDisabledUser = (e instanceof FirebaseAuthInvalidUserException);
                        if (e instanceof FirebaseAuthException) {
                            FirebaseAuthException authEx = (FirebaseAuthException) e;
                            FirebaseAuthError fae = FirebaseAuthError.fromException(authEx);
                            if (fae == FirebaseAuthError.ERROR_USER_DISABLED) {
                               isDisabledUser = true;
                            }
                        }

                        if (isDisabledUser) {
                            setResult(Resource.<IdpResponse>forFailure(
                                    new FirebaseUiException(ErrorCodes.ERROR_USER_DISABLED)
                            ));
                        } else if (e instanceof FirebaseAuthUserCollisionException) {
                            final String email = response.getEmail();
                            if (email == null) {
                                setResult(Resource.<IdpResponse>forFailure(e));
                                return;
                            }

                            // There can be a collision due to:
                            // CASE 1: Anon user signing in with a credential that belongs to an
                            // existing user.
                            // CASE 2: non - anon user signing in with a credential that does not
                            // belong to an existing user, but the email matches an existing user
                            // that has another social IDP. We need to link this new IDP to this
                            // existing user.
                            // CASE 3: CASE 2 with an anonymous user. We link the new IDP to the
                            // same account before handling invoking a merge failure.
                            ProviderUtils.fetchSortedProviders(getAuth(), getArguments(), email)
                                    .addOnSuccessListener(new OnSuccessListener<List<String>>() {
                                        @Override
                                        public void onSuccess(List<String> providers) {
                                            if (providers.contains(response.getProviderType())) {
                                                // Case 1
                                                handleMergeFailure(credential);
                                            } else if (providers.isEmpty()) {
                                                setResult(Resource.<IdpResponse>forFailure(
                                                        new FirebaseUiException(
                                                                ErrorCodes.DEVELOPER_ERROR,
                                                                "No supported providers.")));
                                            } else {
                                                // Case 2 & 3 - we need to link
                                                startWelcomeBackFlowForLinking(
                                                        providers.get(0), response);
                                            }
                                        }
                                    })
                                    .addOnFailureListener(new OnFailureListener() {
                                        @Override
                                        public void onFailure(@NonNull Exception e) {
                                            setResult(Resource.<IdpResponse>forFailure(
                                                    e));
                                        }
                                    });
                        }
                    }
                });
    }

    public void startWelcomeBackFlowForLinking(String provider, IdpResponse response) {
        if (provider == null) {
            throw new IllegalStateException(
                    "No provider even though we received a FirebaseAuthUserCollisionException");
        }

        if (provider.equals(EmailAuthProvider.PROVIDER_ID)) {
            // Start email welcome back flow
            setResult(Resource.<IdpResponse>forFailure(new IntentRequiredException(
                    WelcomeBackPasswordPrompt.createIntent(
                            getApplication(),
                            getArguments(),
                            response),
                    RequestCodes.ACCOUNT_LINK_FLOW
            )));
        } else if (provider.equals(EMAIL_LINK_PROVIDER)) {
            // Start email link welcome back flow
            setResult(Resource.<IdpResponse>forFailure(new IntentRequiredException(
                    WelcomeBackEmailLinkPrompt.createIntent(
                            getApplication(),
                            getArguments(),
                            response),
                    RequestCodes.WELCOME_BACK_EMAIL_LINK_FLOW
            )));
        } else {
            // Start Idp welcome back flow
            setResult(Resource.<IdpResponse>forFailure(new IntentRequiredException(
                    WelcomeBackIdpPrompt.createIntent(
                            getApplication(),
                            getArguments(),
                            new User.Builder(provider, response.getEmail()).build(),
                            response),
                    RequestCodes.ACCOUNT_LINK_FLOW
            )));
        }
    }

    public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        if (requestCode == RequestCodes.ACCOUNT_LINK_FLOW) {
            IdpResponse response = IdpResponse.fromResultIntent(data);
            if (resultCode == Activity.RESULT_OK) {
                setResult(Resource.forSuccess(response));
            } else {
                Exception e;
                if (response == null) {
                    e = new FirebaseUiException(
                            ErrorCodes.UNKNOWN_ERROR, "Link canceled by user.");
                } else {
                    e = response.getError();
                }
                setResult(Resource.<IdpResponse>forFailure(e));
            }
        }
    }

    private void handleGenericIdpLinkingFlow(@NonNull final IdpResponse idpResponse) {
        ProviderUtils.fetchSortedProviders(getAuth(), getArguments(), idpResponse.getEmail())
                .addOnSuccessListener(new OnSuccessListener<List<String>>() {
                    @Override
                    public void onSuccess(@NonNull List<String> providers) {
                        if (providers.isEmpty()) {
                            setResult(Resource.<IdpResponse>forFailure(
                                    new FirebaseUiException(ErrorCodes.DEVELOPER_ERROR,
                                            "No supported providers.")));
                            return;
                        }
                        startWelcomeBackFlowForLinking(providers.get(0), idpResponse);
                    }
                })
                .addOnFailureListener(new OnFailureListener() {
                    @Override
                    public void onFailure(@NonNull Exception e) {
                        setResult(Resource.<IdpResponse>forFailure(e));
                    }
                });
    }

    private boolean isEmailOrPhoneProvider(@NonNull String provider) {
        return TextUtils.equals(provider, EmailAuthProvider.PROVIDER_ID)
                || TextUtils.equals(provider, PhoneAuthProvider.PROVIDER_ID);
    }
}