package com.nagarro.server;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import org.omg.CORBA.portable.ApplicationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.isomorphic.base.UpdateWithoutPKException;
import com.isomorphic.datasource.DSRequest;
import com.isomorphic.datasource.DSResponse;
import com.isomorphic.rpc.RPCManager;
import com.isomorphic.rpc.RPCRequest;
import com.isomorphic.rpc.RPCResponse;
import com.isomorphic.servlet.IDACall;
import com.isomorphic.servlet.RequestContext;
import com.isomorphic.util.DataTools;

/**
 * Overrides {@link IDACall} and sends errors from DSRequests and RPC DMI methods to the error log instead. This also handles {@link ApplicationException}
 * errorCode as the response code sent back to the client.
 */
public class IDACallWithErrors extends IDACall {

    private static final long serialVersionUID = 7475524051629447406L;

    private static final Logger LOGGER = LoggerFactory.getLogger(IDACallWithErrors.class);

    @Override
    public DSResponse handleDSRequestError(final DSRequest dsRequest, final RPCManager rpc, final RequestContext context, final Throwable t) {
        final String errorToString = t.toString();
        final StackTraceElement[] stack = t.getStackTrace();
        final Throwable t2 = t.fillInStackTrace();
        t.setStackTrace(stack);
        String exceptionError = null;
        if ((errorToString != null) && !errorToString.isEmpty() && (t2 != null)) {
            // Sending to our logger as an error instead
            LOGGER.error("dsRequest.execute() failed: ", t);
        } else {
            t.setStackTrace(stack);
            // Sending to our logger as an error instead
            LOGGER.error(
                    "dsRequest.execute() failed: " + t.getClass().getName() + ": " + t.getMessage() + " (The original exception may be missing information)",
                    t);
        }

        DSResponse dsResponse;

        try {
            dsResponse = new DSResponse(dsRequest == null ? null : dsRequest.getDataSource());
        } catch (final Exception e) {
            log.warn("Got Exception trying to instantiate error DSResponse - going to try without the DataSource reference", e);
            dsResponse = new DSResponse();
        }

        if (t instanceof UpdateWithoutPKException) {
            dsResponse.setStatus(-9);
        } else if (t instanceof SecurityException) {
            dsResponse.setStatus(-3);
        } else if ((dsRequest != null) && "downloadFile".equals(dsRequest.getOperationType())) {
            exceptionError = t.getMessage();
            final Map<String, Object> data = new HashMap<>();
            final Map criteria = dsRequest.getCriteria();
            final String binaryField = (String) criteria.get("download_fieldname");
            data.put(binaryField, exceptionError.getBytes());
            data.put(binaryField + "_filename", "errors.txt");
            data.put(binaryField + "_filesize", exceptionError.length());
            data.put(binaryField + "_date_created", new Date());
            dsResponse.setData(data);
            dsResponse.setStatus(0);
            dsRequest.copyExportSettingsToResponse(dsResponse);
        } else {
            dsResponse.setStatus(-1);
        }

        if (exceptionError == null) {
            dsResponse.setData(t.getLocalizedMessage());
        }

        final boolean trace = shouldReturnStacktraceReflection(context);
        if (trace) {
            dsResponse.setParameter("stacktrace", DataTools.getStackTrace(t));
        }

        return dsResponse;
    }

    @Override
    public RPCResponse handleRPCRequestError(final RPCRequest rpcRequest, final RPCManager rpc, final RequestContext context, final Throwable t) {
        // Sending to our logger as an error instead
        LOGGER.error("rpcRequest.execute() failed: ", t);

        final RPCResponse rpcResponse = new RPCResponse();

        // Use the error code from ApplicationException if possible

        rpcResponse.setStatus(-1);

        rpcResponse.setData(t.getLocalizedMessage());
        if (shouldReturnStacktraceReflection(context)) {
            rpcResponse.setStacktrace(DataTools.getStackTrace(t));
        }

        return rpcResponse;
    }

    /**
     * Executes package private method {@link IDACall#shouldReturnStacktrace(RequestContext)}.
     *
     * @param context {@link RequestContext}
     * @return boolean from shouldReturnStacktrace() method
     */
    private boolean shouldReturnStacktraceReflection(final RequestContext context) {
        try {
            final Method method = IDACall.class.getDeclaredMethod("shouldReturnStacktrace", RequestContext.class);
            method.setAccessible(true);
            return (boolean) method.invoke(this, context);
        } catch (final NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
            LOGGER.error("Error executing IDACall.shouldReturnStackTrace()", e);
            return false;
        }
    }

}
