package io.castle.client.internal.utils;

import io.castle.client.internal.config.CastleConfiguration;
import io.castle.client.internal.json.CastleGsonModel;
import io.castle.client.model.CastleContext;
import io.castle.client.model.CastleDevice;
import io.castle.client.model.CastleHeader;
import io.castle.client.model.CastleHeaders;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.Enumeration;

public class CastleContextBuilder {

    private CastleContext context;
    private CastleHeaders headers;
    private final CastleGsonModel model;
    private final CastleConfiguration configuration;
    private final HeaderNormalizer headerNormalizer = new HeaderNormalizer();

    public CastleContextBuilder(CastleConfiguration configuration, CastleGsonModel model) {
        this.configuration = configuration;
        this.model = model;
        context = new CastleContext();
    }

    public CastleContext build() {
        context.setHeaders(headers);
        return context;
    }

    public CastleContextBuilder active(boolean active) {
        context.setActive(active);
        return this;
    }

    public CastleContextBuilder clientId(String clientId) {
        context.setClientId(clientId);
        return this;
    }

    public CastleContextBuilder device(CastleDevice device) {
        context.setDevice(device);
        return this;
    }

    public CastleContextBuilder ip(String ip) {
        context.setIp(ip);
        return this;
    }

    public CastleContextBuilder headers(CastleHeaders headers) {
        this.headers = headers;
        return this;
    }

    public CastleContextBuilder userAgent(String userAgent) {
        context.setUserAgent(userAgent);
        return this;
    }

    public CastleContextBuilder fromHttpServletRequest(HttpServletRequest request) {
        context.setClientId(setClientIdFromHttpServletRequest(request));
        this.headers = setCastleHeadersFromHttpServletRequest(request);
        context.setUserAgent(request.getHeader("User-Agent"));
        context.setIp(request.getRemoteAddr());
        return this;
    }

    public CastleContextBuilder fromJson(String contextString) {
        this.context = model.getGson().fromJson(contextString, CastleContext.class);
        this.headers = context.getHeaders();
        return this;
    }

    public String toJson() {
        return model.getGson().toJson(build());
    }

    /**
     * Load the headers from the HttpRequest.
     * A header will be passed only when it is not on the blacklist and it appears on the whitelist
     *
     * @param request The HttpRequest containing the headers.
     * @return headers Model for castle backend.
     */
    private CastleHeaders setCastleHeadersFromHttpServletRequest(HttpServletRequest request) {
        ArrayList<CastleHeader> castleHeadersList = new ArrayList<>();
        for (Enumeration<String> headerNames = request.getHeaderNames(); headerNames.hasMoreElements(); ) {
            String key = headerNames.nextElement();
            String headerValue = request.getHeader(key);
            addHeaderValue(castleHeadersList, key, headerValue);
        }
        //A CGI specific header is added for compliance with other castle sdk libraries
        addHeaderValue(castleHeadersList, "REMOTE_ADDR",request.getRemoteAddr());

        CastleHeaders headers = new CastleHeaders();
        headers.setHeaders(castleHeadersList);
        return headers;
    }

    private void addHeaderValue(ArrayList<CastleHeader> castleHeadersList, String key, String headerValue) {
        String keyNormalized = headerNormalizer.normalize(key);
        if (!configuration.getBlackListHeaders().contains(keyNormalized)
                && configuration.getWhiteListHeaders().contains(keyNormalized)) {
            castleHeadersList.add(new CastleHeader(key, headerValue));
        }
    }

    /**
     * Extract the clientId from the request.
     * If __cid cookie setup the use that value, if not use header 'X-Castle-Client-Id', if not use empty string ''
     * @param request
     * @return
     */
    private String setClientIdFromHttpServletRequest(HttpServletRequest request) {
        String cid = request.getHeader("X-Castle-Client-Id");
        if( cid == null) {
            //Default value is empty string, so client_id always will be send.
            cid = "";
        }

        Cookie[] cookies = request.getCookies();
        if (cookies != null) {
            for (Cookie cookie : cookies) {
                if (cookie.getName().equals("__cid")) {
                    cid = cookie.getValue();
                }
            }
        }
        return cid;
    }

}

@@ -3993,30 +3993,42 @@
  If 
-__cid cookie setup the
+header 'X-Castle-Client-Id' is set
  use
@@ -4055,35 +4055,20 @@
 use 
-header 'X-Castle-Client-Id'
+__cid cookie
 , if
@@ -4282,24 +4282,25 @@
 t-Id%22);%0A
+%0A
         
 if( cid 
@@ -4295,105 +4295,60 @@
     
-if( cid == null) %7B%0A            //Default value is empty string, so client_id always will be send.
+// If client id header is not included, check cookie
 %0A   
@@ -4356,33 +4356,48 @@
     
-    
+if (
 cid =
- %22%22;%0A    
+= null %7C%7C cid.isEmpty()) %7B%0A
     
-%7D%0A%0A
     
@@ -4441,32 +4441,36 @@
 kies();%0A        
+   
+ 
 if (cookies != n
@@ -4472,24 +4472,28 @@
  != null) %7B%0A
+    
             
@@ -4532,32 +4532,36 @@
                 
+    
 if (cookie.getNa
@@ -4604,16 +4604,20 @@
         
+ 
+   
 cid = co
@@ -4629,24 +4629,46 @@
 getValue();%0A
+                    %7D%0A
             
@@ -4689,32 +4689,172 @@
     %7D%0A        %7D%0A
+%0A        if (cid == null) %7B%0A            //Default value is empty string, so client_id always will be sent.%0A            cid = %22%22;%0A        %7D%0A%0A
         return c

Use header client id if included, otherwise fall back to cookie value or lastly fall back to emtpy string