package com.iterable.iterableapi;

import android.util.Base64;

import androidx.annotation.VisibleForTesting;

import com.iterable.iterableapi.util.Future;

import org.json.JSONObject;

import java.io.UnsupportedEncodingException;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.Callable;

public class IterableAuthManager {
    private static final String TAG = "IterableAuth";
    private static final String expirationString = "exp";

    private final IterableApi api;
    private final IterableAuthHandler authHandler;
    private final long expiringAuthTokenRefreshPeriod;

    @VisibleForTesting
    Timer timer;
    private boolean hasFailedPriorAuth;
    private boolean pendingAuth;
    private boolean requiresAuthRefresh;

    IterableAuthManager(IterableApi api, IterableAuthHandler authHandler, long expiringAuthTokenRefreshPeriod) {
        this.api = api;
        this.authHandler = authHandler;
        this.expiringAuthTokenRefreshPeriod = expiringAuthTokenRefreshPeriod;
    }

    public synchronized void requestNewAuthToken(boolean hasFailedPriorAuth) {
        if (authHandler != null) {
            if (!pendingAuth) {
                if (!(this.hasFailedPriorAuth && hasFailedPriorAuth)) {
                    this.hasFailedPriorAuth = hasFailedPriorAuth;
                    pendingAuth = true;
                    Future.runAsync(new Callable<String>() {
                        @Override
                        public String call() throws Exception {
                            return authHandler.onAuthTokenRequested();
                        }
                    }).onSuccess(new Future.SuccessCallback<String>() {
                        @Override
                        public void onSuccess(String authToken) {
                            if (authToken != null) {
                                queueExpirationRefresh(authToken);
                            }
                            IterableApi.getInstance().setAuthToken(authToken);
                            pendingAuth = false;
                            reSyncAuth();
                        }
                    })
                    .onFailure(new Future.FailureCallback() {
                        @Override
                        public void onFailure(Throwable throwable) {
                            IterableLogger.e(TAG, "Error while requesting Auth Token", throwable);
                            pendingAuth = false;
                            reSyncAuth();
                        }
                    });
                }
            } else if (!hasFailedPriorAuth) {
                //setFlag to resync auth after current auth returns
                requiresAuthRefresh = true;
            }

        } else {
            IterableApi.getInstance().setAuthToken(null, true);
        }
    }

    public void queueExpirationRefresh(String encodedJWT) {
        clearRefreshTimer();
        try {
            long expirationTimeSeconds = decodedExpiration(encodedJWT);
            long triggerExpirationRefreshTime = expirationTimeSeconds * 1000L - expiringAuthTokenRefreshPeriod - IterableUtil.currentTimeMillis();
            if (triggerExpirationRefreshTime > 0) {
                scheduleAuthTokenRefresh(triggerExpirationRefreshTime);
            } else {
                IterableLogger.w(TAG, "The expiringAuthTokenRefreshPeriod has already passed for the current JWT");
            }
        } catch (Exception e) {
            IterableLogger.e(TAG, "Error while parsing JWT for the expiration", e);
        }
    }

    void resetFailedAuth() {
        hasFailedPriorAuth = false;
    }

    void reSyncAuth() {
        if (requiresAuthRefresh) {
            requiresAuthRefresh = false;
            requestNewAuthToken(false);
        }
    }

    private void scheduleAuthTokenRefresh(long timeDuration) {
        timer = new Timer(true);
        try {
            timer.schedule(new TimerTask() {
                @Override
                public void run() {
                    api.getAuthManager().requestNewAuthToken(false);
                }
            }, timeDuration);
        } catch (Exception e) {
            IterableLogger.e(TAG, "timer exception: " + timer, e);
        }
    }

    private static long decodedExpiration(String encodedJWT) throws Exception {
        long exp = 0;
            String[] split = encodedJWT.split("\\.");
            String body = getJson(split[1]);
            JSONObject jObj = new JSONObject(body);
            exp = jObj.getLong(expirationString);
        return exp;
    }

    private static String getJson(String strEncoded) throws UnsupportedEncodingException {
        byte[] decodedBytes = Base64.decode(strEncoded, Base64.URL_SAFE);
        return new String(decodedBytes, "UTF-8");
    }

    void clearRefreshTimer() {
        if (timer != null) {
            timer.cancel();
            timer = null;
        }
    }
}


@@ -57,16 +57,53 @@
 ase64;%0A%0A
+import androidx.annotation.Nullable;%0A
 import a
@@ -140,16 +140,16 @@
 esting;%0A
-
 %0Aimport 
@@ -1130,32 +1130,234 @@
 ledPriorAuth) %7B%0A
+        requestNewAuthToken(hasFailedPriorAuth, null);%0A    %7D%0A%0A    public synchronized void requestNewAuthToken(boolean hasFailedPriorAuth, @Nullable final IterableHelper.SuccessAuthHandler onSuccess) %7B%0A
         if (auth
@@ -1662,32 +1662,40 @@
                 
+        
 @Override%0A      
@@ -1680,32 +1680,40 @@
       @Override%0A
+        
                 
@@ -1784,24 +1784,32 @@
             
+      
+  
 return authH
@@ -1855,32 +1855,40 @@
                 
+        
 %7D%0A              
@@ -1893,16 +1893,24 @@
         
+ 
+       
 %7D).onSuc
@@ -1969,32 +1969,40 @@
                 
+    
+    
 @Override%0A      
@@ -1987,32 +1987,40 @@
       @Override%0A
+        
                 
@@ -2089,32 +2089,40 @@
                 
+        
 if (authToken !=
@@ -2162,16 +2162,24 @@
         
+  
+      
 queueExp
@@ -2225,34 +2225,49 @@
                 
-%7D%0A
+        %7D%0A%0A      
                 
@@ -2270,32 +2270,34 @@
                 
+  
 IterableApi.getI
@@ -2351,32 +2351,40 @@
                 
+        
 pendingAuth = fa
@@ -2408,32 +2408,40 @@
                 
+ 
+       
 reSyncAuth();%0A  
@@ -2442,57 +2442,243 @@
 ();%0A
-                        %7D%0A                    %7D)%0A
+%0A                                    if (onSuccess != null) %7B%0A                                        onSuccess(authToken);%0A                                    %7D%0A                                %7D%0A                            %7D)%0A        
     
@@ -2751,32 +2751,40 @@
                 
+        
 @Override%0A      
@@ -2793,32 +2793,40 @@
                 
+     
+   
 public void onFa
@@ -2850,24 +2850,32 @@
 hrowable) %7B%0A
+        
             
@@ -2981,32 +2981,40 @@
                 
+        
 pendingAuth = fa
@@ -3038,32 +3038,40 @@
                 
+   
+     
 reSyncAuth();%0A  
@@ -3084,34 +3084,50 @@
                 
-%7D%0A
+        %7D%0A        
                 

add new auth method to add onSuccess handler