package org.commcare.util.screen;

import org.commcare.modern.session.SessionWrapper;
import org.commcare.session.CommCareSession;
import org.commcare.suite.model.Entry;
import org.commcare.suite.model.PostRequest;
import org.commcare.suite.model.RemoteRequestEntry;
import org.commcare.util.cli.ApplicationHost;

import java.io.IOException;
import java.io.PrintStream;
import java.util.Hashtable;

import okhttp3.Credentials;
import okhttp3.HttpUrl;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;

/**
 * Screen to make a sync request to HQ after a case claim. Unlike other all other screens,
 * SyncScreen does not take input - it simply processes the PostRequest object, makes the request,
 * and displays the result (if necessary)
 */
public class SyncScreen extends Screen {

    protected SessionWrapper sessionWrapper;
    private String username;
    private String password;
    private PrintStream printStream;
    private boolean syncSuccessful;

    public SyncScreen(String username, String password, PrintStream printStream) {
        super();
        this.username = username;
        this.password = password;
        this.printStream = printStream;
    }

    @Override
    public void init (SessionWrapper sessionWrapper) throws CommCareSessionException {
        this.sessionWrapper = sessionWrapper;
        parseMakeRequest();
    }

    private void parseMakeRequest() throws CommCareSessionException {
        String command = sessionWrapper.getCommand();
        Entry commandEntry = sessionWrapper.getPlatform().getEntry(command);
        if (commandEntry instanceof RemoteRequestEntry) {
            PostRequest syncPost = ((RemoteRequestEntry)commandEntry).getPostRequest();
            Response response = makeSyncRequest(syncPost);
            if (response == null || !response.isSuccessful()) {
                printStream.println(String.format("Sync failed with response %s", response));
                printStream.println("Press 'enter' to retry.");
                return;
            }
            syncSuccessful = true;
            ApplicationHost.restoreUserToSandbox(sessionWrapper.getSandbox(), sessionWrapper, sessionWrapper.getPlatform(), username, password);
            printStream.println(String.format("Sync successful with response %s", response));
            printStream.println("Press 'enter' to continue.");
        } else {
            // expected a sync entry; clear session and show vague 'session error' message to user
            throw new CommCareSessionException("Initialized sync request while not on sync screen");
        }
    }

    private static String buildUrl(String baseUrl) {
        HttpUrl.Builder urlBuilder = HttpUrl.parse(baseUrl).newBuilder();
        return urlBuilder.build().toString();
    }

    private static MultipartBody buildPostBody(Hashtable<String, String> params) {
        MultipartBody.Builder requestBodyBuilder = new MultipartBody.Builder()
                .setType(MultipartBody.FORM);
        // Add buffer param since this is necessary for some reason
        requestBodyBuilder.addFormDataPart("buffer", "buffer");
        for (String key: params.keySet()) {
            requestBodyBuilder.addFormDataPart(key, params.get(key));
        }
        return requestBodyBuilder.build();
    }

    private Response makeSyncRequest(PostRequest syncPost) throws CommCareSessionException {
        Hashtable<String, String> params = syncPost.getEvaluatedParams(sessionWrapper.getEvaluationContext());
        String url = buildUrl(syncPost.getUrl().toString());
        printStream.println(String.format("Syncing with url %s and parameters %s", url, params));
        MultipartBody postBody = buildPostBody(params);
        String credential = Credentials.basic(username, password);

        Request request = new Request.Builder()
                .url(url)
                .method("POST", RequestBody.create(null, new byte[0]))
                .header("Authorization", credential)
                .post(postBody)
                .build();
        try {
            OkHttpClient client = new OkHttpClient();
            return client.newCall(request).execute();
        } catch (IOException e) {
            return null;
        }
    }

    @Override
    public void prompt(PrintStream printStream) throws CommCareSessionException {
        if (syncSuccessful) {
            printStream.println("Sync complete, press Enter to continue");
        } else {
            printStream.println("Sync failed, press Enter to continue");
        }
    }

    @Override
    public boolean handleInputAndUpdateSession(CommCareSession commCareSession, String s) throws CommCareSessionException {
        if (syncSuccessful) {
            commCareSession.syncState();
            if (commCareSession.finishExecuteAndPop(sessionWrapper.getEvaluationContext())) {
                sessionWrapper.clearVolatiles();
            }
            return false;
        } else {
            parseMakeRequest();
            return true;
        }
    }

    @Override
    public String[] getOptions() {
        return new String[0];
    }
}

@@ -1634,24 +1634,25 @@
 y(command);%0A
+%0A
         if (
@@ -1646,24 +1646,26 @@
 %0A        if 
+(!
 (commandEntr
@@ -1696,23 +1696,231 @@
 stEntry)
+)
  %7B%0A    
+        // expected a sync entry; clear session and show vague 'session error' message to user%0A            throw new CommCareSessionException(%22Initialized sync request while not on sync screen%22);%0A        %7D%0A%0A
         
@@ -1987,32 +1987,46 @@
 tPostRequest();%0A
+        try %7B%0A
             Resp
@@ -2088,28 +2088,8 @@
 if (
-response == null %7C%7C 
 !res
@@ -2164,24 +2164,32 @@
 ormat(%22Sync 
+request 
 failed with 
@@ -2193,37 +2193,81 @@
 th response 
-%25s%22, response
+code %25s and message %25s%22, response.code(), response.body()
 ));%0A        
@@ -2476,64 +2476,144 @@
 x(),
- sessionWrapper, sessionWrapper.getPlatform(), username,
+%0A                    sessionWrapper,%0A                    sessionWrapper.getPlatform(),%0A                    username,%0A                   
  pas
@@ -2620,16 +2620,17 @@
 sword);%0A
+%0A
         
@@ -2792,211 +2792,218 @@
   %7D 
-else %7B%0A            // expected a sync entry; clear session and show vague 'session error' message to user%0A            throw new CommCareSessionException(%22Initialized sync request while not on sync screen
+catch (IOException e) %7B%0A            e.printStackTrace();%0A            printStream.println(String.format(%22Sync failed with exception %25s%22, e.getMessage()));%0A            printStream.println(%22Press 'enter' to retry.
 %22);%0A
@@ -3795,32 +3795,45 @@
 SessionException
+, IOException
  %7B%0A        Hasht
@@ -4469,34 +4469,16 @@
 uild();%0A
-        try %7B%0A    
         
@@ -4523,28 +4523,24 @@
 ();%0A        
-    
 return clien
@@ -4573,77 +4573,8 @@
 ();%0A
-        %7D catch (IOException e) %7B%0A            return null;%0A        %7D%0A
     
@@ -4851,32 +4851,29 @@
 ss Enter to 
-continue
+retry
 %22);%0A        

Clean up I/O exception handling