/*
 *  Copyright (c) 2024, WSO2 LLC. (http://www.wso2.org) All Rights Reserved.
 *
 *  WSO2 LLC. licenses this file to you under the Apache License,
 *  Version 2.0 (the "License"); you may not use this file except
 *  in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied. See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 */

package org.apache.synapse.util.synapse.expression;

import com.google.gson.GsonBuilder;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.jayway.jsonpath.Configuration;
import com.jayway.jsonpath.Option;
import com.jayway.jsonpath.spi.json.GsonJsonProvider;
import com.jayway.jsonpath.spi.json.JsonProvider;
import com.jayway.jsonpath.spi.mapper.GsonMappingProvider;
import com.jayway.jsonpath.spi.mapper.MappingProvider;
import org.antlr.v4.runtime.CharStream;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.tree.ParseTree;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.util.AXIOMUtil;
import org.apache.axis2.AxisFault;
import org.apache.synapse.commons.json.JsonUtil;
import org.apache.synapse.core.axis2.Axis2MessageContext;
import org.apache.synapse.util.synapse.expression.exception.SyntaxErrorListener;
import org.apache.synapse.util.synapse.expression.visitor.ExpressionVisitor;
import org.apache.synapse.util.synapse_expression.ExpressionLexer;
import org.apache.synapse.util.synapse_expression.ExpressionParser;
import org.apache.synapse.util.xpath.SynapseExpression;
import org.jaxen.JaxenException;

import javax.xml.stream.XMLStreamException;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * Utility class for testing Synapse Expressions.
 */
public class TestUtils {

    private static Axis2MessageContext synCtx = null;
    private static final String PAYLOAD1 = "{\"name\":\"John\",\"age\":30,\"cars\":[ \"Ford\", " +
            "\"BMW\", \"Fiat\", \"Honda\", \"Lexus\", \"KIA\" ],\"index\":1,\"string\":\" Hello World \"," +
            " \"null\" : null }";
    private static final String PAYLOAD2 = "{\n" +
            "  \"store\": {\n" +
            "    \"book\": [\n" +
            "      {\n" +
            "        \"category\": \"reference\",\n" +
            "        \"author\": \"Nigel Rees\",\n" +
            "        \"title\": \"Sayings of the Century\",\n" +
            "        \"price\": 8.95\n" +
            "      },\n" +
            "      {\n" +
            "        \"category\": \"fiction\",\n" +
            "        \"author\": \"Herman Melville\",\n" +
            "        \"title\": \"Moby Dick\",\n" +
            "        \"isbn\": \"0-553-21311-3\",\n" +
            "        \"price\": 8.99\n" +
            "      },\n" +
            "      {\n" +
            "        \"category\": \"fiction\",\n" +
            "        \"author\": \"J.R.R. Tolkien\",\n" +
            "        \"title\": \"The Lord of the Rings\",\n" +
            "        \"isbn\": \"0-395-19395-8\",\n" +
            "        \"price\": 22.99\n" +
            "      },\n" +
            "      {\n" +
            "        \"category\": \"fiction\",\n" +
            "        \"author\": \"Harper Lee\",\n" +
            "        \"title\": \"To Kill a Mockingbird\",\n" +
            "        \"price\": 10.99\n" +
            "      },\n" +
            "      {\n" +
            "        \"category\": \"fiction\",\n" +
            "        \"author\": \"George Orwell\",\n" +
            "        \"title\": \"Animal Farm\",\n" +
            "        \"price\": 7.99\n" +
            "      },\n" +
            "      {\n" +
            "        \"category\": \"biography\",\n" +
            "        \"author\": \"Anne Frank\",\n" +
            "        \"title\": \"The Diary of a Young Girl\",\n" +
            "        \"price\": 6.99\n" +
            "      }\n" +
            "    ],\n" +
            "    \"bicycle\": {\n" +
            "      \"color\": \"red\",\n" +
            "      \"price\": 19.95\n" +
            "    }\n" +
            "  },\n" +
            "  \"expensive\": 10,\n" +
            "  \"selectedCategory\": \"biography\"\n" +
            "}\n";

    private static final String PAYLOAD3 = "[\"When\",\"my\",\"time\",\"comes\",\"Forget\",\"the\",\"wrong\",\"that\"" +
            ",\"I've\",\"done\"]";

    private static final String PAYLOAD4 = "{\n" +
            "    \"payload\" : {\n" +
            "        \"vars\" : {\n" +
            "            \"configs\" : {\n" +
            "                \"payload\" : {\n" +
            "                    \"iop\" : 4\n" +
            "                },\n" +
            "                \"zxc\": 3\n" +
            "            },\n" +
            "            \"pqr\": 2\n" +
            "        },\n" +
            "        \"abc\" : 1\n" +
            "    }\n" +
            "}";

    private static final JsonObject responseHeaders;

    private static final JsonObject responseAttributes;

    private static final Map<String, Object> variableMap1;

    private static final Map<String, Object> variableMap2;

    private static final Map<String, Object> variableMap3;

    private static final Map<String, Object> variableMap4;

    static {
        variableMap1 = new HashMap<>();
        variableMap1.put("name", "John");
        variableMap1.put("age", 30);
        variableMap1.put("cars", JsonParser.parseString("[\"Ford\",\"BMW\",\"Fiat\",\"Honda\",\"Lexus\",\"KIA\"]").getAsJsonArray());
        variableMap1.put("index", "1");
        variableMap1.put("num1", 10);
        variableMap1.put("num2", 5);
        variableMap1.put("num3", -2.5);
        variableMap1.put("num4", -2.0);
        variableMap1.put("encoded", "V1NPMk1J");
        variableMap1.put("empty", "");
        variableMap2 = new HashMap<>();
        variableMap2.put("json1", PAYLOAD1);
        variableMap2.put("json2", PAYLOAD2);
        variableMap2.put("json3", "[1,2,3,\"abc\"]");
        variableMap2.put("payload", PAYLOAD4);
        variableMap2.put("vars", PAYLOAD4);
        variableMap3 = new HashMap<>();
        responseHeaders = new JsonObject();
        responseHeaders.addProperty("Content-Type", "application/json");
        responseHeaders.addProperty("Content-Length", "101");
        responseAttributes = new JsonObject();
        responseAttributes.addProperty("statusCode", 201);
        Map<String, Object> result = new HashMap<>();
        result.put("headers", responseHeaders);
        result.put("attributes", responseAttributes);
        result.put("payload", JsonParser.parseString(PAYLOAD2));
        variableMap3.put("fileRead_1", result);

        variableMap4 = new HashMap<>();
        try {
            // Connector response with a simple person XML payload
            OMElement personXml = AXIOMUtil.stringToOM(
                    "<person><name>John</name><age>30</age>" +
                    "<address><city>Colombo</city><country>Sri Lanka</country></address></person>");
            Map<String, Object> personResponse = new HashMap<>();
            personResponse.put("payload", personXml);
            personResponse.put("headers", new HashMap<>());
            personResponse.put("attributes", new HashMap<>());
            variableMap4.put("personVar", personResponse);

            // Connector response with an order XML payload (contains attributes)
            OMElement orderXml = AXIOMUtil.stringToOM(
                    "<order id=\"ORD-001\"><item quantity=\"3\">" +
                    "<name>Widget</name><price>9.99</price></item></order>");
            Map<String, Object> orderResponse = new HashMap<>();
            orderResponse.put("payload", orderXml);
            variableMap4.put("orderVar", orderResponse);

            // Connector response with a namespaced XML payload
            OMElement nsXml = AXIOMUtil.stringToOM(
                    "<ns:employee xmlns:ns=\"http://example.com\">" +
                    "<ns:name>Jane</ns:name><ns:department>Engineering</ns:department></ns:employee>");
            Map<String, Object> nsResponse = new HashMap<>();
            nsResponse.put("payload", nsXml);
            variableMap4.put("nsVar", nsResponse);

            // Variable that is NOT a Map (plain XML — for error-path testing)
            variableMap4.put("plainXmlVar", AXIOMUtil.stringToOM("<root><value>42</value></root>"));
        } catch (XMLStreamException e) {
            throw new RuntimeException(e);
        }

        try {
            synCtx = org.apache.synapse.mediators.TestUtils.getAxis2MessageContext("<test/>", null);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static String evaluateExpression(String expression) {
        return evaluateExpressionWithPayload(expression, 0);
    }

    public static String evaluateExpressionWithPayload(String expression, int payloadId) {
        return evaluateExpressionWithPayloadAndVariables(expression, payloadId, 0);
    }

    public static String evaluateExpressionWithPayloadAndVariables(String expression, int payloadId, int variableMapId) {
        Configuration.setDefaults(new Configuration.Defaults() {
            private final JsonProvider jsonProvider = new GsonJsonProvider(new GsonBuilder().serializeNulls().create());
            private final MappingProvider mappingProvider = new GsonMappingProvider();

            public JsonProvider jsonProvider() {
                return jsonProvider;
            }

            public MappingProvider mappingProvider() {
                return mappingProvider;
            }

            public Set<Option> options() {
                return EnumSet.noneOf(Option.class);
            }
        });
        try {
            switch (payloadId) {
                case 1:
                    JsonUtil.getNewJsonPayload(synCtx.getAxis2MessageContext(), PAYLOAD1, true, true);
                    break;
                case 2:
                    JsonUtil.getNewJsonPayload(synCtx.getAxis2MessageContext(), PAYLOAD2, true, true);
                    break;
                case 3:
                    JsonUtil.getNewJsonPayload(synCtx.getAxis2MessageContext(), PAYLOAD3, true, true);
                    break;
                case 4:
                    JsonUtil.getNewJsonPayload(synCtx.getAxis2MessageContext(), PAYLOAD4, true, true);
                    break;
                default:
                    break;
            }
            if (variableMapId == 1) {
                for (Map.Entry<String, Object> entry : variableMap1.entrySet()) {
                    synCtx.setVariable(entry.getKey(), entry.getValue());
                }
            } else if (variableMapId == 2) {
                for (Map.Entry<String, Object> entry : variableMap2.entrySet()) {
                    synCtx.setVariable(entry.getKey(), entry.getValue());
                }
            } else if (variableMapId == 3) {
                for (Map.Entry<String, Object> entry : variableMap3.entrySet()) {
                    synCtx.setVariable(entry.getKey(), entry.getValue());
                }
            } else if (variableMapId == 4) {
                for (Map.Entry<String, Object> entry : variableMap4.entrySet()) {
                    synCtx.setVariable(entry.getKey(), entry.getValue());
                }
            }
            SynapseExpression synapsePath = new SynapseExpression(expression);
            return synapsePath.stringValueOf(synCtx);
        } catch (JaxenException | AxisFault e) {
            throw new RuntimeException(e);
        }
    }

    public static String evaluateExpressionWithNamespaces(String expression, int variableMapId,
                                                          Map<String, String> namespaces) {
        try {
            if (variableMapId == 4) {
                for (Map.Entry<String, Object> entry : variableMap4.entrySet()) {
                    synCtx.setVariable(entry.getKey(), entry.getValue());
                }
            }
            SynapseExpression synapsePath = new SynapseExpression(expression);
            for (Map.Entry<String, String> ns : namespaces.entrySet()) {
                synapsePath.addNamespace(ns.getKey(), ns.getValue());
            }
            return synapsePath.stringValueOf(synCtx);
        } catch (JaxenException e) {
            throw new RuntimeException(e);
        }
    }

    public static void evaluateWithErrorListener(SyntaxErrorListener errorListener, String expression) {
        CharStream input = CharStreams.fromString(expression);
        ExpressionLexer lexer = new ExpressionLexer(input);
        CommonTokenStream tokens = new CommonTokenStream(lexer);
        ExpressionParser parser = new ExpressionParser(tokens);
        parser.removeErrorListeners();
        parser.addErrorListener(errorListener);
        // trigger parsing
        ParseTree tree = parser.expression();
        ExpressionVisitor visitor = new ExpressionVisitor();
        visitor.visit(tree);
    }

    /**
     * used in tests to get an Axis2MessageContext with a given payload
     */
    public static void clearMessageContext() {
        try {
            synCtx = org.apache.synapse.mediators.TestUtils.getAxis2MessageContext("<test/>", null);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

}
