package org.lazyluke.wintermock;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.jayway.jsonpath.Configuration;
import com.jayway.jsonpath.JsonPath;
import com.jayway.jsonpath.Option;
import net.minidev.json.JSONArray;

public class JsonPathChecker {

    private static final Logger LOGGER = LoggerFactory.getLogger(JsonPathChecker.class);
    public static final String VALUE_KEYWORD = "value";
    public static final String JSON_PATH_ROOT_ELEMENT = "$";
    public static final String JSON_PATH_FOR_ANY_CHILD_FROM_ROOT = "$..";
    public static final String JSON_PATH_FIRST_ELEMENT_OF_ROOT_ARRAY = "$[0]";

    public static boolean verifyContains(String json, final Map<String, String> parameterExpressionMap) {

        json = makeJsonIfPlainString(json);

        Object document = Configuration.defaultConfiguration().addOptions(Option.ALWAYS_RETURN_LIST).jsonProvider().parse(json);

        for (String jsonPathExpression : parameterExpressionMap.keySet()) {
            String modifiedJsonPathExpression = ensureJsonPathExpressionIfNotAbsolutePath(jsonPathExpression);

            Object jsonPathReadResult = JsonPath.read(document, modifiedJsonPathExpression);
            Object matchedElement;
            if (jsonPathReadResult instanceof JSONArray) {
                JSONArray jsonArray = (JSONArray) jsonPathReadResult;
                List<Object> nonNullJsonArray = getAsNonNullList(jsonArray);
                if (nonNullJsonArray.size() == 0) {
                    LOGGER.info("No match for {} in {}", modifiedJsonPathExpression, json);
                    return false;
                }
                checkMoreThanOneElementAndThrow(json, jsonPathExpression, nonNullJsonArray);
                matchedElement = nonNullJsonArray.get(0);
            } else {
                matchedElement = jsonPathReadResult;
            }

            String expectedValue = parameterExpressionMap.get(jsonPathExpression);
            if (matchedElement == null) {
                if (!expectedValue.equals("null")) {
                    logNoMatch(modifiedJsonPathExpression, matchedElement, expectedValue);
                    return false;
                }
            } else if (matchedElement instanceof String) {
                if (!((String) matchedElement).matches(expectedValue)) {
                    logNoMatch(modifiedJsonPathExpression, matchedElement, expectedValue);
                    return false;
                }
            } else if (matchedElement instanceof Boolean) {
                if (!matchedElement.equals(Boolean.valueOf(expectedValue))) {
                    logNoMatch(modifiedJsonPathExpression, matchedElement, expectedValue);
                    return false;
                }
            } else if (matchedElement instanceof Integer) {
                if (!matchedElement.equals(Integer.valueOf(expectedValue))) {
                    logNoMatch(modifiedJsonPathExpression, matchedElement, expectedValue);
                    return false;
                }
            } else {
                throw new IllegalStateException("JsonPath expression " + jsonPathExpression + " returns a type " + matchedElement.getClass().getCanonicalName() + " not yet handled. Please extend!");
            }
        }
        // All expressions in parameterExpressionMap have been found
        return true;
    }

    private static void logNoMatch(String modifiedJsonPathExpression, Object matchedElement, String expectedValue) {
        LOGGER.info("No match >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
        LOGGER.info("No match {} expected:<{}> but was:<{}>",
                modifiedJsonPathExpression, expectedValue, matchedElement);
    }

    private static String makeJsonIfPlainString(String json) {
        json = json.trim();
        // by making an array with one element we can match it with $[0]
        if (!json.startsWith("{") && !json.startsWith("[")) {
            json = "[\"" + json + "\"]";
        }
        return json;
    }

    private static void checkMoreThanOneElementAndThrow(String json, String jsonPathExpression, List<Object> nonNullJsonArray) {
        if (nonNullJsonArray.size() > 1) {
            throw new IllegalArgumentException("JsonPath expression '" + jsonPathExpression +
                    "' is not expected to match more than one element in " + json +
                    "\nbut matches: " + nonNullJsonArray + "." +
                    "\nConsider using an absolute path to the element starting with a $ such as $[0].locationNumber. See JsonPath documentation."
            );
        }
    }

    private static List<Object> getAsNonNullList(JSONArray jsonArray) {
        List<Object> nonNullJsonArray = new ArrayList<>();
        for (Object o : jsonArray) {
            if (o != null) {
                nonNullJsonArray.add(o);
            }
        }
        return nonNullJsonArray;
    }

    private static String ensureJsonPathExpressionIfNotAbsolutePath(String jsonPathExpression) {
        if (jsonPathExpression.equals(VALUE_KEYWORD)) {
            return JSON_PATH_FIRST_ELEMENT_OF_ROOT_ARRAY;
        }
        return (!jsonPathExpression.startsWith(JSON_PATH_ROOT_ELEMENT)) ? JSON_PATH_FOR_ANY_CHILD_FROM_ROOT + jsonPathExpression : jsonPathExpression;
    }
}

@@ -335,24 +335,78 @@
 hChecker %7B%0A%0A
+    private static final String NULL_STRING = %22null%22;%0A
     private 
@@ -1351,16 +1351,100 @@
 lement;%0A
+            String expectedValue = parameterExpressionMap.get(jsonPathExpression);%0A%0A
         
@@ -1689,16 +1689,54 @@
 e() == 0
+ && !expectedValue.equals(NULL_STRING)
 ) %7B%0A    
@@ -1988,50 +1988,94 @@
     
-matchedElement = nonNullJsonArray.get(0);%0A
+if(expectedValue.equals(NULL_STRING))%7B%0A                    matchedElement = null;%0A    
     
@@ -2099,32 +2099,36 @@
                 
+    
 matchedElement =
@@ -2128,36 +2128,44 @@
 ement = 
-jsonPathReadResult;%0A
+nonNullJsonArray.get(0);%0A   
         
@@ -2164,26 +2164,26 @@
             
+ 
 %7D
-%0A
 %0A           
@@ -2187,78 +2187,84 @@
     
-String expectedValue = parameterExpressionMap.get(jsonPathExpression);
+%7D else %7B%0A                matchedElement = jsonPathReadResult;%0A            %7D%0A
 %0A   
@@ -2344,22 +2344,27 @@
 .equals(
-%22null%22
+NULL_STRING
 )) %7B%0A   
@@ -5643,9 +5643,8 @@
 %0A    %7D%0A%7D
-%0A

fix json path checker for null lists and arrays