package com.takejame.cordova.plugin.googlesignin;

import android.util.Log;
import android.content.Intent;

import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaInterface;
import org.apache.cordova.CordovaWebView;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import com.google.android.gms.common.GoogleApiAvailability;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApi;
import com.google.android.gms.common.api.ApiException;
import com.google.android.gms.common.api.CommonStatusCodes;

//import com.google.android.gms.common.Scopes;
//import com.google.android.gms.common.api.Scope;
import com.google.android.gms.auth.api.signin.GoogleSignInClient;
import com.google.android.gms.auth.api.signin.GoogleSignInOptions;
import com.google.android.gms.auth.api.signin.GoogleSignInAccount;
import com.google.android.gms.auth.api.signin.GoogleSignIn;
import com.google.android.gms.auth.api.signin.GoogleSignInStatusCodes;
import com.google.android.gms.games.Games;
import com.google.android.gms.games.GamesClient;
import com.google.android.gms.games.PlayersClient;
import com.google.android.gms.games.Player;
import com.google.android.gms.tasks.Task;
import com.google.android.gms.tasks.OnCompleteListener;
import androidx.annotation.NonNull;
import com.google.gson.Gson;


import java.util.Arrays;
import android.os.AsyncTask;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.Method;
/**
 * This class echoes a string called from JavaScript.
 */
public class CordovaGoogleSignIn extends CordovaPlugin {
    public static final String ACTION_GET_LAST_SIGNED_IN_ACCOUNT = "getLastSignedInAccount";
    public static final String ACTION_SIGN_IN = "signIn";
    public static final String ACTION_SILENT_SIGN_IN = "silentSignIn";
    public static final String ACTION_SIGN_OUT = "signOut";
    public static final String ACTION_REVOKE_ACCESS = "revokeAccess";
    public static final String ACTION_IS_GOOGLE_PLAY_SERVICES_AVAILABLE = "isGooglePlayServicesAvailable";
    
    public static final String TAG = "GoogleSignIn";
    public static final int RC_GOOGLESIGNIN = 1553; // Request Code to identify our plugin's activities
    
    private GoogleSignInClient googleSignInClient; 
    private CallbackContext savedCallbackContext;
    private String signInMode;

    @Override
    public void initialize(CordovaInterface cordova, CordovaWebView webView) {
        super.initialize(cordova, webView);
        
        /*Activity cordovaActivity = cordova.getActivity();

        googlePlayServicesReturnCode = GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(cordovaActivity);

        if (googlePlayServicesReturnCode == ConnectionResult.SUCCESS) {
            gameHelper = new GameHelper(cordovaActivity, GameHelper.CLIENT_GAMES);
            gameHelper.setup(this);
        } else {
            Log.w(LOGTAG, String.format("GooglePlayServices not available. Error: '" +
                    GoogleApiAvailability.getInstance().getErrorString(googlePlayServicesReturnCode) +
                    "'. Error Code: " + googlePlayServicesReturnCode));
        }*/
        
        
    }

    @Override
    public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
        savedCallbackContext=callbackContext;
        if (action.equals(ACTION_GET_LAST_SIGNED_IN_ACCOUNT)) {
            getLastSignedInAccount(callbackContext);
            return true;
        } else if(action.equals(ACTION_SIGN_IN)){
            buildGoogleApiClient(args);//.optJSONObject(0));
            signIn();
            return true;
        } else if (action.equals(ACTION_SILENT_SIGN_IN)) {
            buildGoogleApiClient(args);//.optJSONObject(0));
            silentSignIn();
            return true;
        } else if (action.equals(ACTION_SIGN_OUT)) {
            signOut();
            return true;
        } else if (action.equals(ACTION_REVOKE_ACCESS)) {
            revokeAccess();
            return true;
        } else if (action.equals(ACTION_IS_GOOGLE_PLAY_SERVICES_AVAILABLE)) {
            isGooglePlayServicesAvailable();
            return true;
        }
        return false;
    }
    
    /**
     * Set options for login and Build the GoogleApiClient if it has not already been built.
     * @param clientOptions - the options object passed in the login function
     */
    private synchronized void buildGoogleApiClient(JSONArray clientOptions) throws JSONException {
        Log.i(TAG,"build options:");
        /*Log.i(TAG,toJsonString(clientOptions));
        Log.i(TAG,toJsonString(clientOptions.optJSONObject(0)));
        Log.i(TAG,toJsonString(clientOptions.optJSONObject(0).getString("signInMode")));
        Log.i(TAG,toJsonString(clientOptions.optJSONObject(0).get("scopes")));
        Log.i(TAG,toJsonString(clientOptions.optJSONObject(0).get("scopes").getClass().getName()));
        Log.i(TAG,Arrays.toString(clientOptions.optJSONObject(0).get("scopes").getClass().getDeclaredMethods()));
        //JSONArray scopes = clientOptions.optJSONObject(0).getJSONArray("scopes");
        Log.i(TAG,toJsonString(clientOptions.optJSONObject(0).getString("webClientId")));*/ 
        
        JSONObject buildOptions=clientOptions.optJSONObject(0);
        this.signInMode=buildOptions.getString("signInMode");
        JSONArray scopes=buildOptions.getJSONArray("scopes");
        String webClientId=buildOptions.getString("webClientId");
        Log.i(TAG,this.signInMode);
        Log.i(TAG,toJsonString(scopes));
        Log.i(TAG,""+isSet(scopes,"email"));
        if(scopes.length()>0){
            Log.i(TAG,toJsonString(scopes.get(0)));
        }
        Log.i(TAG,webClientId);
        /*
        if (clientOptions == null) {
            return;
        }
        if (this.googleSignInClient != null) this.googleSignInClient.signOut();
        this.googleSignInClient = null;

        Log.i(TAG, "Building Google options");

        // Make our SignIn Options builder.
        GoogleSignInOptions.Builder gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_GAMES_SIGN_IN);
        
        String webClientId = clientOptions.optString(ARGUMENT_WEB_CLIENT_ID, null);
        
        gso.requestProfile().requestServerAuthCode(webClientId) ;
        

        //Now that we have our options, let's build our Client
        Log.i(TAG, "Building GoogleApiClient");

        this.googleSignInClient = GoogleSignIn.getClient(cordova.getActivity(),gso.build());
*/
        GoogleSignInOptions.Builder options;
        if(this.signInMode.toLowerCase().equals("games")){
            Log.i(TAG,"performing games signin");
            options = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_GAMES_SIGN_IN);
        } else {
            Log.i(TAG,"performing default signin");
            options = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN);
        }
        if(isSet(scopes,"email")){
            options.requestEmail();
        }
        if(isSet(scopes,"id")){
            options.requestId();
        }
        if(isSet(scopes,"profile")){
            options.requestProfile();
        }
        if(isSet(scopes,"idtoken")){
            options.requestIdToken(webClientId);
        }
        if(isSet(scopes,"serverauthcode")){
            options.requestServerAuthCode(webClientId);
        }
        this.googleSignInClient = GoogleSignIn.getClient(cordova.getActivity(), options.build());
        Log.i(TAG, "GoogleSignInClient built");
    }
    private void getLastSignedInAccount(CallbackContext callbackContext){
        //try {
        Log.i(TAG, "getLastSignedInAccount");
        GoogleSignInAccount account = GoogleSignIn.getLastSignedInAccount(cordova.getActivity());
        if(account!=null){
            callbackContext.success(toJson(account));
            Log.i(TAG, "getLastSignedInAccount successfull");
        //} catch (Exception e){
        } else {
            Log.e(TAG, "getLastSignedInAccount failed");
            //Log.e(TAG, e.getMessage());
            //e.printStackTrace();
            //callbackContext.error(e.getMessage());
            callbackContext.error("No previous account found.");
        }
        //savedCallbackContext.success(new Gson().toJsonTree(account).toString());
        //savedCallbackContext.success(account.toString());
    }
    private void signIn() {
        //Intent signInIntent = Auth.GoogleSignInApi.getSignInIntent(this.mGoogleApiClient);
        Log.i(TAG, "signIn:");
        cordova.setActivityResultCallback(this);
        Intent signInIntent = this.googleSignInClient.getSignInIntent();
        cordova.getActivity().startActivityForResult(signInIntent, RC_GOOGLESIGNIN);
    }
    private void silentSignIn() {
        Log.i(TAG, "silentSignIn:");
        Task<GoogleSignInAccount> task = this.googleSignInClient.silentSignIn();
        task.addOnCompleteListener(new OnCompleteListener<GoogleSignInAccount>() {
            @Override
            public void onComplete(@NonNull Task<GoogleSignInAccount> task) {
                handleSignInResult(task);
            }
        });
    }
    private void signOut() {
        Log.i(TAG, "signOut:");
        checkGoogleSignInClient();
        Task<Void> task = this.googleSignInClient.signOut();
        task.addOnCompleteListener(new OnCompleteListener<Void>() {
            @Override
            public void onComplete(@NonNull Task<Void> task) {
                if (task.isSuccessful()) {
                    savedCallbackContext.success("Sign out successfull");
                    Log.i(TAG, "signOut successfull");
                } else {
                    handleSignInError(task.getException());
                    //savedCallbackContext.error(task.getException().toString());
                    Log.e(TAG, "signOut failed");
                }
            }
        });
    }
    private void revokeAccess() {
        Log.i(TAG, "revokeAccess");
        checkGoogleSignInClient();
        Task<Void> task = this.googleSignInClient.revokeAccess();
        task.addOnCompleteListener(new OnCompleteListener<Void>() {
            @Override
            public void onComplete(@NonNull Task<Void> task) {
                if (task.isSuccessful()) {
                    savedCallbackContext.success("Revoke access successfull");
                    Log.i(TAG, "revokeAccess successfull");
                } else {
                    handleSignInError(task.getException());
                    //savedCallbackContext.error(task.getException().toString());
                    Log.e(TAG, "revokeAccess failed");
                }
            }
        });
    }
    private void isGooglePlayServicesAvailable(){
        
        /*
        checkGoogleSignInClient();
        GoogleApiAvailability googleApiAvailability=GoogleApiAvailability.getInstance();
        Task<Void> task=googleApiAvailability.checkApiAvailability(com.google.android.gms.games.GamesClient);
        task.addOnCompleteListener(new OnCompleteListener<Void>() {
            @Override
            public void onComplete(@NonNull Task<Void> task) {
                if (task.isSuccessful()) {
                    savedCallbackContext.success("checkApiAvailability successfull");
                    Log.i(TAG, "checkApiAvailability successfull");
                } else {
                    savedCallbackContext.error(task.getException().toString());
                    Log.e(TAG, "checkApiAvailability failed");
                }
            }
        });
        return;
        */

        GoogleApiAvailability googleApiAvailability=GoogleApiAvailability.getInstance();
        int isGoogleAvailable=googleApiAvailability.isGooglePlayServicesAvailable(cordova.getActivity());
        JSONObject result = new JSONObject();
        try {
            result.put("isGoogleAvailable",String.valueOf(isGoogleAvailable));
            result.put("SUCCESS", ConnectionResult.SUCCESS);
            result.put("SERVICE_MISSING", ConnectionResult.SERVICE_MISSING);
            result.put("SERVICE_UPDATING", ConnectionResult.SERVICE_UPDATING);
            result.put("SERVICE_VERSION_UPDATE_REQUIRED", ConnectionResult.SERVICE_VERSION_UPDATE_REQUIRED);
            result.put("SERVICE_DISABLED", ConnectionResult.SERVICE_DISABLED);
            result.put("SERVICE_INVALID", ConnectionResult.SERVICE_INVALID);
            savedCallbackContext.success(result);
        } catch (Exception e) {
            //handleSignInError(e); Exception cannot be converted to ApiException
            savedCallbackContext.error("Trouble obtaining result, error: " + e.getMessage());
        }
    }
    /*private void checkGoogleSignInClient(){
        if(this.googleSignInClient==null){
            this.googleSignInClient = GoogleSignIn.getClient(
                cordova.getActivity(),
                new GoogleSignInOptions.Builder().build()
            );
        }
        return;
    }*/ 
    private String toJsonString(Object object){
        Gson gson=new Gson();
        return gson.toJsonTree(object).toString();
    }
    private JSONObject toJson(Object object){
        Log.i(TAG, "toJson:");
        //Gson gson=new Gson();
        //String objectString=gson.toJsonTree(object).toString();
        String objectString=toJsonString(object);
        JSONObject json;
        try {
            json = new JSONObject(objectString);
            Log.i(TAG, "toJson successfull");
        } catch(JSONException ex) {
            Log.e(TAG, "Could not convert data.", ex);
            json = new JSONObject();
            Log.e(TAG, "toJson failed");
        }
        return json;
    }
    private Boolean isSet(JSONArray array, String target){
        for(int i=0;i<array.length();i++){
            try {
                String source=array.getString(i).toLowerCase();
                if(source.equals(target)) return true;
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
        return false;
    }
    private void handleSignInResult(Task<GoogleSignInAccount> task){
        if (task.isSuccessful()) {
            Log.i(TAG, "silentSignIn successfull");
            // Sign in succeeded, proceed with account
            GoogleSignInAccount account = task.getResult();
            /*if(this.signInMode=="games"){
                PlayersClient playersClient = Games.getPlayersClient(cordova.getActivity(),account);
                Task<Player> playerTask=playersClient.getCurrentPlayer();
                playerTask.addOnCompleteListener(new OnCompleteListener<Void>() {
                    @Override
                    public void onComplete(@NonNull Task<Player> playerTask) {
                        if (task.isSuccessful()) {
                            Player player = playerTask.getResult();
                            JSONObject result = new JSONObject();
                            result.put("playerName", player.getDisplayName());
                            result.put("lastPlayed", player.getLastPlayedWithTimestamp());
                            result.put("playerId", player.getPlayerId());
                            result.put("playerLevel", player.getLevelInfo().getCurrentLevel().getLevelNumber());
                            result.put("retrievedTimestamp", player.getRetrievedTimestamp());
                            result.put("playerTitle", player.getTitle());
                            
                            result.put("email", account.getEmail());
                            result.put("idToken", account.getIdToken());
                            result.put("serverAuthCode", account.getServerAuthCode());
                            result.put("userId", account.getId());
                            result.put("familyName", account.getFamilyName());
                            result.put("givenName", account.getGivenName());
                            result.put("imageUrl", account.getPhotoUrl());
                            result.put("grantedScopes", account.getGrantedScopes());
                            savedCallbackContext.success(result);
                        } else {
                            //Log.e(TAG, "silentSignIn failed");
                            savedCallbackContext.error("Trouble obtaining result, error: " + task.getException().toString());
                            // Task failed with an exception
                            //savedCallbackContext.error(task.getException().toString());
                        }
                    }
                });
            } else {*/
                new AsyncTask<Void, Void, Void>() {
                    @Override
                    protected Void doInBackground(Void... params) {
                        JSONObject result = new JSONObject();
                        try {
                            /*JSONObject accessTokenBundle = getAuthToken(
                                cordova.getActivity(), acct.getAccount(), true
                            );
                            result.put(FIELD_ACCESS_TOKEN, accessTokenBundle.get(FIELD_ACCESS_TOKEN));
                            result.put(FIELD_TOKEN_EXPIRES, accessTokenBundle.get(FIELD_TOKEN_EXPIRES));
                            result.put(FIELD_TOKEN_EXPIRES_IN, accessTokenBundle.get(FIELD_TOKEN_EXPIRES_IN));*/
                            result.put("statusCode",CommonStatusCodes.SUCCESS);
                            result.put("SUCCESS",CommonStatusCodes.SUCCESS);

                            result.put("email", account.getEmail());
                            result.put("idToken", account.getIdToken());
                            result.put("serverAuthCode", account.getServerAuthCode());
                            result.put("userId", account.getId());
                            result.put("displayName", account.getDisplayName());
                            result.put("familyName", account.getFamilyName());
                            result.put("givenName", account.getGivenName());
                            result.put("imageUrl", account.getPhotoUrl());
                            result.put("grantedScopes", account.getGrantedScopes());
                            savedCallbackContext.success(result);
                        } catch (Exception e) {
                            savedCallbackContext.error("Trouble obtaining result, error: " + e.getMessage());
                        }
                        return null;
                    }
                }.execute();
            //}
            
            /*
            //no scope
            Log.i(TAG,toJsonString(account));
            //scope requestEmail()
            Log.i(TAG,toJsonString(account.getAccount()));
            //no scope or default signin
            Log.i(TAG,toJsonString(account.getId()));
            Log.i(TAG,toJsonString(account.getDisplayName()));
            Log.i(TAG,toJsonString(account.getFamilyName()));
            Log.i(TAG,toJsonString(account.getGivenName()));
            //scope.requestEmail()
            Log.i(TAG,toJsonString(account.getEmail()));
            //scope.requestProfile() or default signin
            Log.i(TAG,toJsonString(account.getPhotoUrl()));
            Log.i(TAG,toJsonString(account.getGrantedScopes()));




            savedCallbackContext.success(toJson(account));
            //savedCallbackContext.success(new Gson().toJsonTree(acct).toString());*/
        } else {
            Log.e(TAG, "silentSignIn failed");
            // Task failed with an exception


            ApiException exception=(ApiException)task.getException();
            handleSignInError(exception);
            

            //savedCallbackContext.error(task.getException().toString());
        }
    }
    private void handleSignInError(ApiException exception){
        JSONObject error = new JSONObject();
        try {
            Class exceptionClass = exception.getClass();
            error.put("className", exceptionClass.getName());
            error.put("statusCode", exception.getStatusCode());
            
            error=getClassConstants(CommonStatusCodes.class,error);
            error=getClassConstants(GoogleSignInStatusCodes.class,error);
            savedCallbackContext.success(error);
        } catch (Exception e) {
            savedCallbackContext.error("Error obtaining result: " + e.getMessage());
        }
    }
    private JSONObject getClassConstants(Class targetClass, JSONObject originalJson) throws IllegalAccessException, JSONException{
        
        Field fieldlist[] = targetClass.getFields();
        for (Field field : fieldlist) {
            if(field.getModifiers()==Modifier.PUBLIC+Modifier.STATIC+Modifier.FINAL){
                originalJson.put(field.getName(),field.get(field));
            }
        }
        return originalJson;
    }
    /**
     * Listens for and responds to an activity result. If the activity result request code matches our own,
     * we know that the sign in Intent that we started has completed.
     *
     * The result is retrieved and send to the handleSignInResult function.
     *
     * @param requestCode The request code originally supplied to startActivityForResult(),
     * @param resultCode The integer result code returned by the child activity through its setResult().
     * @param intent Information returned by the child activity
     */
    @Override
    public void onActivityResult(int requestCode, final int resultCode, final Intent intent) {
        super.onActivityResult(requestCode, resultCode, intent);

        Log.i(TAG, "In onActivityResult");

        if (requestCode == RC_GOOGLESIGNIN) {
            Log.i(TAG, "signIn result:");
            Task<GoogleSignInAccount> task = GoogleSignIn.getSignedInAccountFromIntent(intent);
            handleSignInResult(task);
        } else {
            Log.i(TAG, "This wasn't one of our activities");
        }
    }

    /**
     * Function for handling the sign in result
     * Handles the result of the authentication workflow.
     *
     * If the sign in was successful, we build and return an object containing the users email, id, displayname,
     * id token, and (optionally) the server authcode.
     *
     * If sign in was not successful, for some reason, we return the status code to web app to be handled.
     * Some important Status Codes:
     *      SIGN_IN_CANCELLED = 12501 -> cancelled by the user, flow exited, oauth consent denied
     *      SIGN_IN_FAILED = 12500 -> sign in attempt didn't succeed with the current account
     *      SIGN_IN_REQUIRED = 4 -> Sign in is needed to access API but the user is not signed in
     *      INTERNAL_ERROR = 8
     *      NETWORK_ERROR = 7
     *
     * @param signInResult - the GoogleSignInResult object retrieved in the onActivityResult method.
     */
    /*private void handleSignInResult(final GoogleSignInResult signInResult) {
        //if (this.mGoogleApiClient == null) {
        if (this.mGoogleSignInClient == null) {
            savedCallbackContext.error("GoogleApiClient was never initialized");
            return;
        }

        if (signInResult == null) {
          savedCallbackContext.error("SignInResult is null");
          return;
        }

        Log.i(TAG, "Handling SignIn Result");

        if (!signInResult.isSuccess()) {
            Log.i(TAG, "Wasn't signed in");
            //Return the status code to be handled client side
            savedCallbackContext.error(signInResult.getStatus().getStatusCode());
        } else {
            new AsyncTask<Void, Void, Void>() {
                @Override
                protected void doInBackground(Void... params) {
                    GoogleSignInAccount acct = signInResult.getSignInAccount();
                    savedCallbackContext.success(acct);
                    Log.i(TAG, "UPDATE 1");
                    return null;
                    
                    /*JSONObject result = new JSONObject();
                    try {
                        /*JSONObject accessTokenBundle = getAuthToken(
                            cordova.getActivity(), acct.getAccount(), true
                        );
                        result.put(FIELD_ACCESS_TOKEN, accessTokenBundle.get(FIELD_ACCESS_TOKEN));
                        result.put(FIELD_TOKEN_EXPIRES, accessTokenBundle.get(FIELD_TOKEN_EXPIRES));
                        result.put(FIELD_TOKEN_EXPIRES_IN, accessTokenBundle.get(FIELD_TOKEN_EXPIRES_IN));*/
                        //result.put("email", acct.getEmail());
                    /*    result.put("idToken", acct.getIdToken());
                        //result.put("serverAuthCode", acct.getServerAuthCode());
                        /*result.put("userId", acct.getId());
                        result.put("displayName", acct.getDisplayName());
                        result.put("familyName", acct.getFamilyName());
                        result.put("givenName", acct.getGivenName());
                        result.put("imageUrl", acct.getPhotoUrl());
                        result.put("scopes", acct.getGrantedScopes());*/ 
                    /*    savedCallbackContext.success(result);
                    } catch (Exception e) {
                        savedCallbackContext.error("Trouble obtaining result, error: " + e.getMessage());
                    }*/
    /*                return null;
                }
            }.execute();
        }
    }*/
}
