package io.wealthwizards.appsynctesting;

import io.wealthwizards.appsynctesting.directive.Return;
import io.wealthwizards.appsynctesting.extensions.Extensions;
import io.wealthwizards.appsynctesting.utils.*;
import io.wealthwizards.appsynctesting.utils.Math;
import io.wealthwizards.appsynctesting.utils.dynamodb.DynamoDB;
import io.wealthwizards.appsynctesting.utils.transform.Transform;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.runtime.RuntimeConstants;
import org.apache.velocity.util.introspection.UberspectImpl;
import org.apache.velocity.util.introspection.UberspectPublicFields;

import java.io.StringWriter;
import java.util.HashMap;

public final class Evaluator {
    private final VelocityContext velocityContext;
    private final VelocityEngine velocityEngine;
    private final Util util;
    private final Extensions extensions;

    public Evaluator(AppSyncContext context, AppSyncState state) {
        super();

        velocityEngine = new VelocityEngine();

        var uberspectClasses = UberspectPublicFields.class.getName() + ", " + UberspectImpl.class.getName();
        velocityEngine.setProperty(RuntimeConstants.UBERSPECT_CLASSNAME, uberspectClasses);
        velocityEngine.setProperty(RuntimeConstants.EVENTHANDLER_INCLUDE, AppSyncIncludeEventHandler.class.getName());
        velocityEngine.setProperty(RuntimeConstants.VM_PERM_ALLOW_INLINE, "false");
        velocityEngine.setProperty(RuntimeConstants.CUSTOM_DIRECTIVES, Return.class.getName());

        velocityEngine.init();

        // directives must be removed after initalised from defaults ¯\_(ツ)_/¯
        velocityEngine.removeDirective("evaluate");

        velocityContext = new VelocityContext();
        velocityContext.put("context", context);
        velocityContext.put("ctx", context);

        util = new Util(
                state,
                new Time(state.getCurrentTime()),
                new DynamoDB(),
                new List(),
                new Map(),
                new Str(),
                new Math(),
                new Transform(),
                new Rds(),
                new Http(),
                new Xml()
        );
        velocityContext.put("util", util);
        velocityContext.put("utils", util);

        extensions = new Extensions();
        velocityContext.put("extensions", extensions);
    }

    public BridgeResponse evaluate(String template) throws NoSuchFieldException, IllegalAccessException {
        var writer = new StringWriter();
        var logTag = "[log]";
        
        String result = null;
        AppSyncException error = null;

        try {
            velocityEngine.evaluate(velocityContext, writer, logTag, template);

            result = writer.toString();
        } catch (Exception exception) {
            if (exception.getCause() instanceof AppSyncException) {
                error = (AppSyncException) exception.getCause();
            } else {
                throw exception;
            }
        }
        
        var errors = getUtilErrors();

        return new BridgeResponse(result, error, errors);
    }
    
    private java.util.List<AppSyncException> getUtilErrors() throws NoSuchFieldException, IllegalAccessException {
        if (util == null) {
            throw new NullPointerException();
        }
        
        var field = StatefulUtil.class.getDeclaredField("errors");
        
        field.setAccessible(true);

        @SuppressWarnings("unchecked")
        var errors = (java.util.List<AppSyncException>) field.get(util);

        field.setAccessible(false);

        return errors;
    }
}
