package io.castle.client.internal;

import com.google.common.base.Preconditions;
import com.google.common.hash.HashFunction;
import com.google.common.hash.Hashing;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import io.castle.client.api.CastleApi;
import io.castle.client.internal.backend.RestApi;
import io.castle.client.internal.config.CastleSdkInternalConfiguration;
import io.castle.client.internal.utils.CastleContextBuilder;
import io.castle.client.internal.utils.ContextMerge;
import io.castle.client.internal.utils.VerdictBuilder;
import io.castle.client.model.*;

import javax.annotation.Nullable;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;

public class CastleApiImpl implements CastleApi {

    private final HttpServletRequest request;
    private final boolean doNotTrack;
    private final CastleSdkInternalConfiguration configuration;
    private final JsonObject contextJson;

    public CastleApiImpl(HttpServletRequest request, boolean doNotTrack, CastleSdkInternalConfiguration configuration) {
        this.request = request;
        this.doNotTrack = doNotTrack;
        this.configuration = configuration;
        CastleContext castleContext = buildContext();
        this.contextJson = configuration.getModel().getGson().toJsonTree(castleContext).getAsJsonObject();
    }

    private CastleApiImpl(HttpServletRequest request, boolean doNotTrack, CastleSdkInternalConfiguration configuration, JsonObject contextJson) {
        this.request = request;
        this.doNotTrack = doNotTrack;
        this.configuration = configuration;
        this.contextJson = contextJson;
    }

    private CastleContext buildContext() {
        CastleContextBuilder builder = new CastleContextBuilder(configuration.getConfiguration());
        CastleContext context = builder
                .fromHttpServletRequest(this.request)
                .build();
        return context;
    }

    @Override
    public CastleApi mergeContext(Object additionalContext) {
        JsonObject contextToMerge=null;
        if(additionalContext != null) {
            contextToMerge = configuration.getModel().getGson().toJsonTree(additionalContext).getAsJsonObject();
        }
        JsonObject mergedContext = new ContextMerge().merge(this.contextJson, contextToMerge);
        return new CastleApiImpl(request, doNotTrack, configuration, mergedContext);
    }

    @Override
    public CastleApi doNotTrack(boolean doNotTrack) {
        return new CastleApiImpl(request, doNotTrack, configuration);
    }

    @Override
    public Verdict authenticate(String event, String userId) {
        return authenticate(event, userId, null, null);
    }

    @Override
    public Verdict authenticate(String event, String userId, @Nullable Object properties, @Nullable Object traits) {
        if (doNotTrack) {
            return buildVerdictForDoNotTrack(userId);
        }
        RestApi restApi = configuration.getRestApiFactory().buildBackend();
        JsonElement propertiesJson = null;
        if (properties != null) {
            propertiesJson = configuration.getModel().getGson().toJsonTree(properties);
        }
        JsonElement traitsJson = null;
        if (traits != null){
            traitsJson = configuration.getModel().getGson().toJsonTree(traits);
        }
        return restApi.sendAuthenticateSync(event, userId, contextJson, propertiesJson, traitsJson);
    }

    private Verdict buildVerdictForDoNotTrack(String userId) {
        return VerdictBuilder.failover("Castle set to do not track.")
                .withAction(AuthenticateAction.ALLOW)
                .withUserId(userId)
                .build();
    }

    @Override
    public void authenticateAsync(String event, @Nullable String userId, @Nullable Object properties, @Nullable Object traits, AsyncCallbackHandler<Verdict> asyncCallbackHandler) {
        if (doNotTrack) {
            asyncCallbackHandler.onResponse(buildVerdictForDoNotTrack(userId));
        } else {
            Preconditions.checkNotNull(asyncCallbackHandler, "The async handler can not be null");
            RestApi restApi = configuration.getRestApiFactory().buildBackend();
            JsonElement propertiesJson = null;
            if (properties != null) {
                propertiesJson = configuration.getModel().getGson().toJsonTree(properties);
            }
            JsonElement traitsJson = null;
            if (traits != null) {
                traitsJson = configuration.getModel().getGson().toJsonTree(traits);
            }
            restApi.sendAuthenticateAsync(event, userId, contextJson, propertiesJson, traitsJson, asyncCallbackHandler);
        }
    }

    @Override
    public void authenticateAsync(String event, String userId, AsyncCallbackHandler<Verdict> asyncCallbackHandler) {
        authenticateAsync(event, userId, null, null, asyncCallbackHandler);
    }

    @Override
    public void track(String event) {
        track(event, null, null, null);
    }

    @Override
    public void track(String event, String userId) {
        track(event, userId, null, null);
    }

    @Override
    public void track(String event, String userId, Object properties) {
        track(event, userId, properties, null);
    }

    @Override
    public void track(String event, @Nullable String userId, @Nullable Object properties,  @Nullable AsyncCallbackHandler<Boolean> asyncCallbackHandler) {
        Preconditions.checkNotNull(event);
        if (doNotTrack) {
            return;
        }
        RestApi restApi = configuration.getRestApiFactory().buildBackend();
        JsonElement propertiesJson = null;
        if (properties != null) {
            propertiesJson = configuration.getModel().getGson().toJsonTree(properties);
        }
        restApi.sendTrackRequest(event, userId, contextJson, propertiesJson, asyncCallbackHandler);
    }

    @Override
    public void identify(String userId, @Nullable Object traits, boolean active) {
        Preconditions.checkNotNull(userId);
        if (doNotTrack) {
            return;
        }
        JsonElement traitsJson = null;
        if (traits != null) {
            traitsJson = configuration.getModel().getGson().toJsonTree(traits);
        }
        RestApi restApi = configuration.getRestApiFactory().buildBackend();
        restApi.sendIdentifyRequest(userId, contextJson, active, traitsJson);
    }

    @Override
    public void identify(String userId) {
        identify(userId, null, true);
    }

    @Override
    public void identify(String userId, @Nullable Object traits) {
        Preconditions.checkNotNull(userId);
        identify(userId, traits, true);
    }


    @Override
    public Review review(String reviewId) {
        Preconditions.checkNotNull(reviewId);
        RestApi restApi = configuration.getRestApiFactory().buildBackend();
        return restApi.sendReviewRequestSync(reviewId);
    }

    @Override
    public void reviewAsync(String reviewId, AsyncCallbackHandler<Review> asyncCallbackHandler) {
        Preconditions.checkNotNull(reviewId);
        Preconditions.checkNotNull(asyncCallbackHandler);
        RestApi restApi = configuration.getRestApiFactory().buildBackend();
        restApi.sendReviewRequestAsync(reviewId, asyncCallbackHandler);
    }

}

@@ -799,54 +799,8 @@
  %7B%0A%0A
-    private final HttpServletRequest request;%0A
     
@@ -829,24 +829,24 @@
 doNotTrack;%0A
+
     private 
@@ -1065,40 +1065,8 @@
 ) %7B%0A
-        this.request = request;%0A
     
@@ -1194,16 +1194,23 @@
 Context(
+request
 );%0A     
@@ -1348,36 +1348,8 @@
 mpl(
-HttpServletRequest request, 
 bool
@@ -1440,40 +1440,8 @@
 ) %7B%0A
-        this.request = request;%0A
     
@@ -1604,16 +1604,42 @@
 Context(
+HttpServletRequest request
 ) %7B%0A    
@@ -1813,21 +1813,16 @@
 Request(
-this.
 request)
@@ -2282,33 +2282,24 @@
 stleApiImpl(
-request, 
 doNotTrack, 
@@ -2437,25 +2437,16 @@
 ApiImpl(
-request, 
 doNotTra
@@ -2462,16 +2462,28 @@
 guration
+,contextJson
 );%0A    %7D

Use the request reference only on the constructor code