//
// Welcome to the annotated FaceTec Device SDK core code for performing secure Enrollment!
//
package Processors;

import android.app.Activity;
import android.content.Context;
import android.util.Log;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.widget.ImageView;

import com.bumptech.glide.Glide;
import com.bumptech.glide.request.target.CustomTarget;
import com.bumptech.glide.request.transition.Transition;

import androidx.annotation.NonNull;

import com.facetec.sdk.*;
import com.reactnativefacetec.FacetecModule;

import com.facebook.react.bridge.ReadableMap;

import org.json.JSONException;
import org.json.JSONArray;
import org.json.JSONObject;

import java.io.IOException;
import java.util.ArrayList;

import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.MediaType;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.OkHttpClient;

// This is an example self-contained class to perform Enrollment with the FaceTec SDK.
// You may choose to further componentize parts of this in your own Apps based on your specific requirements.

// Android Note 1:  Some commented "Parts" below are out of order so that they can match iOS and Browser source for this same file on those platforms.
// Android Note 2:  Android does not have a onFaceTecSDKCompletelyDone function that you must implement like "Part 10" of iOS and Android Samples.  Instead, onActivityResult is used as the place in code you get control back from the FaceTec SDK.
public class PhotoIDMatchProcessor extends Processor implements FaceTecFaceScanProcessor, FaceTecIDScanProcessor {
  private boolean success = false;
  private boolean faceScanWasSuccessful = false;
  private final FacetecModule activity;
  private String DeviceKeyIdentifier;
  private String apiUrl;
  private String FaceScanBase64;

  public PhotoIDMatchProcessor(String sessionToken, Context context, FacetecModule activity, String DeviceKeyIdentifier, ReadableMap messages, String apiUrl) {
    this.activity = activity;
    this.DeviceKeyIdentifier = DeviceKeyIdentifier;
    this.apiUrl = apiUrl;

    String uploadIdFrontStart = messages.hasKey("uploadIdFrontStart") ? messages.getString("uploadIdFrontStart") : "Verificando su DNI";
    String uploadIdFrontSlow = messages.hasKey("uploadIdFrontSlow") ? messages.getString("uploadIdFrontSlow") : "Estamos Verificando,\nla conexion no es buena";
    String uploadIdFrontComplete = messages.hasKey("uploadIdFrontComplete") ? messages.getString("uploadIdFrontComplete") : "Verificacion completa";
    String uploadIdFrontProcessing = messages.hasKey("uploadIdFrontProcessing") ? messages.getString("uploadIdFrontProcessing") : "Analizando DNI";
    String uploadIdBackStart = messages.hasKey("uploadIdBackStart") ? messages.getString("uploadIdBackStart") : "Verificando el dorso de su DNI";
    String uploadIdBackSlow = messages.hasKey("uploadIdBackSlow") ? messages.getString("uploadIdBackSlow") : "Estamos Verificando,\nla conexion no es buena";
    String uploadIdBackComplete = messages.hasKey("uploadIdBackComplete") ? messages.getString("uploadIdBackComplete") : "Verificacion Completa";
    String uploadIdBackProcessing = messages.hasKey("uploadIdBackProcessing") ? messages.getString("uploadIdBackProcessing") : "Analizando el dorso de su DNI";
    String uploadUserInfoStart = messages.hasKey("uploadUserInfoStart") ? messages.getString("uploadUserInfoStart") : "Subiendo su analisis";
    String uploadUserInfoSlow = messages.hasKey("uploadUserInfoSlow") ? messages.getString("uploadUserInfoSlow") : "Estamos subiendo...\nLa conexion no es buena";
    String uploadUserInfoComplete = messages.hasKey("uploadUserInfoComplete") ? messages.getString("uploadUserInfoComplete") : "Subida Exitosa";
    String uploadUserInfoProcessing = messages.hasKey("uploadUserInfoProcessing") ? messages.getString("uploadUserInfoProcessing") : "Procesando";
    String uploadNfcStart = messages.hasKey("uploadNfcStart") ? messages.getString("uploadNfcStart") : "Uploading Encrypted\nNFC Details";
    String uploadNfcSlow = messages.hasKey("uploadNfcSlow") ? messages.getString("uploadNfcSlow") : "Still Uploading...\nSlow Connection";
    String uploadNfcComplete = messages.hasKey("uploadNfcComplete") ? messages.getString("uploadNfcComplete") : "Upload Complete";
    String uploadNfcProcessing = messages.hasKey("uploadNfcProcessing") ? messages.getString("uploadNfcProcessing") : "Processing\nNFC Details";
    String uploadIdDetailsStart = messages.hasKey("uploadIdDetailsStart") ? messages.getString("uploadIdDetailsStart") : "Uploading Encrypted\nID Details";
    String uploadIdDetailsSlow = messages.hasKey("uploadIdDetailsSlow") ? messages.getString("uploadIdDetailsSlow") : "Still Uploading...\nSlow Connection";
    String uploadIdDetailsComplete = messages.hasKey("uploadIdDetailsComplete") ? messages.getString("uploadIdDetailsComplete") : "Upload Complete";
    String uploadIdDetailsProcessing = messages.hasKey("uploadIdDetailsProcessing") ? messages.getString("uploadIdDetailsProcessing") : "Processing\nID Details";

    // Asignar los mensajes a FaceTecCustomization
    FaceTecCustomization.setIDScanUploadMessageOverrides(
      uploadIdFrontStart,
      uploadIdFrontSlow,
      uploadIdFrontComplete,
      uploadIdFrontProcessing,
      uploadIdBackStart,
      uploadIdBackSlow,
      uploadIdBackComplete,
      uploadIdBackProcessing,
      uploadUserInfoStart,
      uploadUserInfoSlow,
      uploadUserInfoComplete,
      uploadUserInfoProcessing,
      uploadNfcStart,
      uploadNfcSlow,
      uploadNfcComplete,
      uploadNfcProcessing,
      uploadIdDetailsStart,
      uploadIdDetailsSlow,
      uploadIdDetailsComplete,
      uploadIdDetailsProcessing
    );

    //
    // Part 1:  Starting the FaceTec Session
    //
    // Required parameters:
    // - Context:  Unique for Android, a Context is passed in, which is required for the final onActivityResult function after the FaceTec SDK is done.
    // - FaceTecFaceScanProcessor:  A class that implements FaceTecFaceScanProcessor, which handles the FaceScan when the User completes a Session.  In this example, "self" implements the class.
    // - sessionToken:  A valid Session Token you just created by calling your API to get a Session Token from the Server SDK.
    //
    FaceTecSessionActivity.createAndLaunchSession(context, PhotoIDMatchProcessor.this, PhotoIDMatchProcessor.this, sessionToken);
  }

    //
  // Part 2:  Handling the Result of a FaceScan
  //
  public void processSessionWhileFaceTecSDKWaits(final FaceTecSessionResult sessionResult, final FaceTecFaceScanResultCallback faceScanResultCallback) {
    //
    // DEVELOPER NOTE:  These properties are for demonstration purposes only so the Sample App can get information about what is happening in the processor.
    // In the code in your own App, you can pass around signals, flags, intermediates, and results however you would like.
    //
    //sampleAppActivity.setLatestSessionResult(sessionResult);

    //
    // Part 3:  Handles early exit scenarios where there is no FaceScan to handle -- i.e. User Cancellation, Timeouts, etc.
    //
    if (sessionResult.getStatus() != FaceTecSessionStatus.SESSION_COMPLETED_SUCCESSFULLY) {
      NetworkingHelpers.cancelPendingRequests();
      faceScanResultCallback.cancel();
      return;
    }

    //
    // Part 4:  Get essential data off the FaceTecSessionResult
    //
    JSONObject parameters = new JSONObject();
    try {
      parameters.put("faceScan", sessionResult.getFaceScanBase64());
      parameters.put("auditTrailImage", sessionResult.getAuditTrailCompressedBase64()[0]);
      parameters.put("lowQualityAuditTrailImage", sessionResult.getLowQualityAuditTrailCompressedBase64()[0]);
      parameters.put("externalDatabaseRefID", activity.getLatestExternalDatabaseRefID());

      this.FaceScanBase64 = sessionResult.getAuditTrailCompressedBase64()[0];
    } catch (JSONException e) {
      e.printStackTrace();
      Log.d("FaceTecSDKSampleApp", "Exception raised while attempting to create JSON payload for upload.");
    }

    //
    // Part 5:  Make the Networking Call to Your Servers.  Below is just example code, you are free to customize based on how your own API works.
    //
    okhttp3.Request request = new okhttp3.Request.Builder()
      .url(Config.BaseURL + "/enrollment-3d")
      .header("Content-Type", "application/json")
      .header("X-Device-Key", this.DeviceKeyIdentifier)
      .header("User-Agent", FaceTecSDK.createFaceTecAPIUserAgentString(sessionResult.getSessionId()))

      //
      // Part 7:  Demonstrates updating the Progress Bar based on the progress event.
      //
      .post(new ProgressRequestBody(RequestBody.create(MediaType.parse("application/json; charset=utf-8"), parameters.toString()),
        new ProgressRequestBody.Listener() {
          @Override
          public void onUploadProgressChanged(long bytesWritten, long totalBytes) {
            final float uploadProgressPercent = ((float) bytesWritten) / ((float) totalBytes);
            faceScanResultCallback.uploadProgress(uploadProgressPercent);
          }
        }))
      .build();

    //
    // Part 8:  Actually send the request.
    //
    NetworkingHelpers.getApiClient().newCall(request).enqueue(new Callback() {
      @Override
      public void onResponse(@NonNull Call call, @NonNull okhttp3.Response response) throws IOException {

        //
        // Part 6:  In our Sample, we evaluate a boolean response and treat true as was successfully processed and should proceed to next step,
        // and handle all other responses by cancelling out.
        // You may have different paradigms in your own API and are free to customize based on these.
        //

        String responseString = response.body().string();
        response.body().close();
        try {
          JSONObject responseJSON = new JSONObject(responseString);
          Log.d("response", "response");
          Log.d("response", responseString);
          boolean wasProcessed = responseJSON.getBoolean("wasProcessed");
          String scanResultBlob = responseJSON.getString("scanResultBlob");

          // In v9.2.0+, we key off a new property called wasProcessed to determine if we successfully processed the Session result on the Server.
          // Device SDK UI flow is now driven by the proceedToNextStep function, which should receive the scanResultBlob from the Server SDK response.
          if (wasProcessed) {
            Log.d("INICIPU", "PROCEDIO");
            // Demonstrates dynamically setting the Success Screen Message.
            FaceTecCustomization.overrideResultScreenSuccessMessage = "Liveness\nConfirmed";

            // In v9.2.0+, simply pass in scanResultBlob to the proceedToNextStep function to advance the User flow.
            // scanResultBlob is a proprietary, encrypted blob that controls the logic for what happens next for the User.
            faceScanWasSuccessful = faceScanResultCallback.proceedToNextStep(scanResultBlob);
          } else {
            Log.d("cancel", "cancel");
            // CASE:  UNEXPECTED response from API.  Our Sample Code keys off a wasProcessed boolean on the root of the JSON object --> You define your own API contracts with yourself and may choose to do something different here based on the error.
            faceScanResultCallback.cancel();
          }
        } catch (JSONException e) {
          // CASE:  Parsing the response into JSON failed --> You define your own API contracts with yourself and may choose to do something different here based on the error.  Solid server-side code should ensure you don't get to this case.
          e.printStackTrace();
          Log.d("FaceTecSDKSampleApp", "Exception raised while attempting to parse JSON result.");
          faceScanResultCallback.cancel();
        }
      }

      @Override
      public void onFailure(@NonNull Call call, @NonNull IOException e) {
        // CASE:  Network Request itself is erroring --> You define your own API contracts with yourself and may choose to do something different here based on the error.
        Log.d("FaceTecSDKSampleApp", "Exception raised while attempting HTTPS call.");
        faceScanResultCallback.cancel();
      }
    });
  }

  //
  // Part 9:  Handling the Result of an IDScan
  //
  public void processIDScanWhileFaceTecSDKWaits(final FaceTecIDScanResult idScanResult, final FaceTecIDScanResultCallback idScanResultCallback) {
    Log.d("INICIPU", "PASO 9");
    Log.d("INICIPU", idScanResult.toString());

    //
    // DEVELOPER NOTE:  These properties are for demonstration purposes only so the Sample App can get information about what is happening in the processor.
    // In the code in your own App, you can pass around signals, flags, intermediates, and results however you would like.
    //
    //sampleAppActivity.setLatestIDScanResult(idScanResult);

    //
    // Part 10:  Handles early exit scenarios where there is no IDScan to handle -- i.e. User Cancellation, Timeouts, etc.
    //
    if (idScanResult.getStatus() != FaceTecIDScanStatus.SUCCESS) {
      NetworkingHelpers.cancelPendingRequests();
      idScanResultCallback.cancel();
      return;
    }

    // IMPORTANT:  FaceTecSDK.FaceTecIDScanStatus.Success DOES NOT mean the IDScan 3d-2d Matching was Successful.
    // It simply means the User completed the Session and a 3D FaceScan was created. You still need to perform the IDScan 3d-2d Matching on your Servers.

    //
    // minMatchLevel allows Developers to specify a Match Level that they would like to target in order for success to be true in the response.
    // minMatchLevel cannot be set to 0.
    // minMatchLevel setting does not affect underlying Algorithm behavior.
    final int minMatchLevel = 3;

    //
    // Part 11: Get essential data off the FaceTecIDScanResult
    //
    JSONObject parameters = new JSONObject();
    try {
      parameters.put("externalDatabaseRefID", activity.getLatestExternalDatabaseRefID());
      parameters.put("idScan", idScanResult.getIDScanBase64());
      parameters.put("minMatchLevel", minMatchLevel);

      ArrayList<String> frontImagesCompressedBase64 = idScanResult.getFrontImagesCompressedBase64();
      ArrayList<String> backImagesCompressedBase64 = idScanResult.getBackImagesCompressedBase64();
      if (frontImagesCompressedBase64.size() > 0) {
        parameters.put("idScanFrontImage", frontImagesCompressedBase64.get(0));
      }
      if (backImagesCompressedBase64.size() > 0) {
        parameters.put("idScanBackImage", backImagesCompressedBase64.get(0));
      }

      if (frontImagesCompressedBase64.size() > 0 && backImagesCompressedBase64.size() > 0) {
        JSONArray filesArray = new JSONArray();

        // Crear el primer objeto para el documento dorso
        JSONObject backDocument = new JSONObject();
        backDocument.put("externalReferenceId", activity.getLatestExternalDatabaseRefID());
        backDocument.put("image", backImagesCompressedBase64.get(0));
        backDocument.put("fileName", "documento_dorso");

        // Crear el segundo objeto para el documento frente
        JSONObject frontDocument = new JSONObject();
        frontDocument.put("externalReferenceId", activity.getLatestExternalDatabaseRefID());
        frontDocument.put("image", frontImagesCompressedBase64.get(0));
        frontDocument.put("fileName", "documento_frente");

        // Crear el tercer objeto para selfie
        JSONObject photoSelfie = new JSONObject();
        photoSelfie.put("externalReferenceId", activity.getLatestExternalDatabaseRefID());
        photoSelfie.put("image", this.FaceScanBase64);
        photoSelfie.put("fileName", "selfie");

        // Agregar los objetos al array
        filesArray.put(backDocument);
        filesArray.put(frontDocument);
        filesArray.put(photoSelfie);

        // Crear el JSONObject principal que contiene el array de archivos
        JSONObject requestBody = new JSONObject();
        requestBody.put("files", filesArray);

        // Convertir el JSONObject a String
        String jsonBody = requestBody.toString();

        // Crear la solicitud HTTP
        OkHttpClient client = new OkHttpClient();
        MediaType JSON = MediaType.parse("application/json; charset=utf-8");
        RequestBody body = RequestBody.create(JSON, jsonBody);

        Request request = new Request.Builder()
          .url(apiUrl)
          .header("Content-Type", "application/json")
          .post(body)
          .build();


          Log.d("SERVICIO ADELANTAR", "Intenta el TRY");
          Log.d("ADELANTAR", activity.getLatestExternalDatabaseRefID());
          NetworkingHelpers.getApiClient().newCall(request).enqueue(new Callback() {
            @Override
            public void onResponse(@NonNull Call call, @NonNull okhttp3.Response response) throws IOException {
              String responseString = response.body().string();
              response.body().close();
              Log.d("ADELANTAR", "WARNING.");
            }

            @Override
            public void onFailure(@NonNull Call call, @NonNull IOException e) {
              // CASE:  Network Request itself is erroring --> You define your own API contracts with yourself and may choose to do something different here based on the error.
              Log.d("ADELANTAR", "ERROR.");
            }
          });






      }
    } catch (JSONException e) {
      e.printStackTrace();
      Log.d("FaceTecSDKSampleApp", "Exception raised while attempting to create JSON payload for upload.");
    }

    //
    // Part 13:  Make the Networking Call to Your Servers.  Below is just example code, you are free to customize based on how your own API works.
    //
    okhttp3.Request request = new okhttp3.Request.Builder()
      .url(Config.BaseURL + "/match-3d-2d-idscan")
      .header("Content-Type", "application/json")
      .header("X-Device-Key", this.DeviceKeyIdentifier)
      .header("User-Agent", FaceTecSDK.createFaceTecAPIUserAgentString(idScanResult.getSessionId()))

      //
      // Part 14:  Demonstrates updating the Progress Bar based on the progress event.
      //
      .post(new ProgressRequestBody(RequestBody.create(MediaType.parse("application/json; charset=utf-8"), parameters.toString()),
        new ProgressRequestBody.Listener() {
          @Override
          public void onUploadProgressChanged(long bytesWritten, long totalBytes) {
            final float uploadProgressPercent = ((float) bytesWritten) / ((float) totalBytes);
            idScanResultCallback.uploadProgress(uploadProgressPercent);
          }
        }))
      .build();

    //
    // Part 15:  Actually send the request.
    //
    NetworkingHelpers.getApiClient().newCall(request).enqueue(new Callback() {
      @Override
      public void onResponse(@NonNull Call call, @NonNull okhttp3.Response response) throws IOException {

        //
        // Part 16:  In our Sample, we evaluate a boolean response and treat true as was successfully processed and should proceed to next step,
        // and handle all other responses by cancelling out.
        // You may have different paradigms in your own API and are free to customize based on these.
        //

        String responseString = response.body().string();
        response.body().close();
        try {
          JSONObject responseJSON = new JSONObject(responseString);

          boolean wasProcessed = responseJSON.getBoolean("wasProcessed");
          String scanResultBlob = responseJSON.getString("scanResultBlob");

          // In v9.2.0+, we key off a new property called wasProcessed to determine if we successfully processed the Session result on the Server.
          // Device SDK UI flow is now driven by the proceedToNextStep function, which should receive the scanResultBlob from the Server SDK response.
          if (wasProcessed) {
            activity.setResult(responseJSON);
            activity.setTipo("EnrollmentWithId");
            // In v9.2.0+, configure the messages that will be displayed to the User in each of the possible cases.
            // Based on the internal processing and decision logic about how the flow gets advanced, the FaceTec SDK will use the appropriate, configured message.
            // Please note that this programmatic API overrides these same Strings that can also be set via our standard, non-programmatic Text Customization & Localization APIs.
            FaceTecCustomization.setIDScanResultScreenMessageOverrides(
              "Front Scan Complete", // Successful scan of ID front-side (ID Types with no back-side).
              "Front of ID\nScanned", // Successful scan of ID front-side (ID Types that have a back-side).
              "Front of ID\nScanned", // Successful scan of ID front-side (ID Types that do have NFC but do not have a back-side).
              "ID Scan Complete", // Successful scan of the ID back-side (ID Types that do not have NFC).
              "Back of ID\nScanned", // Successful scan of the ID back-side (ID Types that do have NFC).
              "Passport Scan Complete", // Successful scan of a Passport that does not have NFC.
              "Passport Scanned", // Successful scan of a Passport that does have NFC.
              "Photo ID Scan\nComplete", // Successful upload of final IDScan containing User-Confirmed ID Text.
              "ID Scan Complete", // Successful upload of the scanned NFC chip information.
              "ID Photo Capture\nComplete", // Successful upload of final Photo ID Scan result but scan is flagged as requiring additional review.
              "Face Didn't Match\nHighly Enough", // Case where a Retry is needed because the Face on the Photo ID did not Match the User's Face highly enough.
              "ID Document\nNot Fully Visible", // Case where a Retry is needed because a Full ID was not detected with high enough confidence.
              "ID Text Not Legible", // Case where a Retry is needed because the OCR did not produce good enough results and the User should Retry with a better capture.
              "ID Type Mismatch\nPlease Try Again", // Case where there is likely no OCR Template installed for the document the User is attempting to scan.
              "ID Details\nUploaded" // Case where NFC Scan was skipped due to the user's interaction or an unexpected error.
            );

            // In v9.2.0+, simply pass in scanResultBlob to the proceedToNextStep function to advance the User flow.
            // scanResultBlob is a proprietary, encrypted blob that controls the logic for what happens next for the User.
            // Cases:
            //   1.  User must re-scan the same side of the ID that they just tried.
            //   2.  User succeeded in scanning the Front Side of the ID, there is no Back Side, and the User is now sent to the User OCR Confirmation UI.
            //   3.  User succeeded in scanning the Front Side of the ID, there is a Back Side, and the User is sent to the Auto-Capture UI for the Back Side of their ID.
            //   4.  User succeeded in scanning the Back Side of the ID, and the User is now sent to the User OCR Confirmation UI.
            //   5.  The entire process is complete.  This occurs after sending up the final IDScan that contains the User OCR Data.
            success = idScanResultCallback.proceedToNextStep(scanResultBlob);
          } else {
            // CASE:  UNEXPECTED response from API.  Our Sample Code keys off a wasProcessed boolean on the root of the JSON object --> You define your own API contracts with yourself and may choose to do something different here based on the error.
            idScanResultCallback.cancel();
          }
        } catch (JSONException e) {
          // CASE:  Parsing the response into JSON failed --> You define your own API contracts with yourself and may choose to do something different here based on the error.  Solid server-side code should ensure you don't get to this case.
          e.printStackTrace();
          Log.d("FaceTecSDKSampleApp", "Exception raised while attempting to parse JSON result.");
          idScanResultCallback.cancel();
        }
      }

      @Override
      public void onFailure(@NonNull Call call, @NonNull IOException e) {
        // CASE:  Network Request itself is erroring --> You define your own API contracts with yourself and may choose to do something different here based on the error.
        Log.d("FaceTecSDKSampleApp", "Exception raised while attempting HTTPS call.");
        idScanResultCallback.cancel();
      }
    });
  }

  public boolean isSuccess() {
    return this.success;
  }
}
