/*-
 * ~~~~~~licensing~~~~~~
 * error-handler
 * ==========
 * Copyright (C) 2020 - 2025 EmDev LLC
 * ==========
 * You may not use this file except in accordance with the License Terms of the Copyright
 * Holder located at: https://entaxy.ru/eula . All copyrights, all intellectual property
 * rights to the Software and any copies are the property of the Copyright Holder. Unless
 * it is explicitly allowed the Copyright Holder, the User is prohibited from using the
 * Software for commercial purposes to provide services to third parties.
 * 
 * The Copyright Holder hereby declares that the Software is provided on an "AS IS".
 * Under no circumstances does the Copyright Holder guarantee or promise that the
 * Software provided by him will be suitable or not suitable for the specific purposes
 * of the User, that the Software will meet all commercial and personal subjective
 * expectations of the User, that the Software will work properly, without technical
 * errors, quickly and uninterruptedly.
 * 
 * Under no circumstances shall the Copyright Holder or its Affiliates is not liable
 * to the User for any direct or indirect losses of the User, his expenses or actual
 * damage, including, downtime; loss of bussines; lost profit; lost earnings; loss
 * or damage to data, property, etc.
 * ~~~~~~/licensing~~~~~~
 */
package ru.entaxy.esb.system.core.common.error.handler.interceptor;

import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.camel.CamelContext;
import org.apache.camel.ProducerTemplate;
import org.apache.camel.component.cxf.common.message.CxfConstants;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.cxf.binding.soap.SoapMessage;
import org.apache.cxf.binding.soap.interceptor.AbstractSoapInterceptor;
import org.apache.cxf.endpoint.Endpoint;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.message.Exchange;
import org.apache.cxf.message.Message;
import org.apache.cxf.phase.Phase;
import org.apache.cxf.transport.Conduit;
import org.apache.cxf.transport.http.Headers;

import ru.entaxy.esb.system.common.util.SystemHeadersConstants;

public class HandleOutFaultInterceptor extends AbstractSoapInterceptor {
    private static final Log LOG = LogFactory.getLog(HandleOutFaultInterceptor.class);

    private final ProducerTemplate template;
    private final String endpointUri;

    public HandleOutFaultInterceptor(CamelContext camelContext, String endpointUri) {
        super(Phase.POST_PROTOCOL);
        this.template = camelContext.createProducerTemplate();
        this.endpointUri = endpointUri;
    }


    public void handleMessage(SoapMessage message) {
        LOG.debug("HandleOutFaultInterceptor.handleMessage invocation");
        // String inMessage = message.getExchange().getInMessage().getContent(String.class);
        // Fault fault = (Fault) message.getContent(Exception.class);
        Map headers = prepareHeaders(message);
        String response = template.requestBodyAndHeaders(this.endpointUri, "", headers, String.class);
        LOG.debug("HandleOutFaultInterceptor.handleMessage result from camel: \n" + response);
        sendErrorResponse(message, 200, response);
    }

    private Map prepareHeaders(SoapMessage message) {
        Fault fault = (Fault) message.getContent(Exception.class);
        Map headers = new HashMap();
        headers.put("NTX_ERROR_HANDLER_SoapFault", fault);
        // headers.put("ERROR_HANDLER_EXCEPTION", fault.getCause());
        // headers.put("NTX_ERROR_HANDLER_ExceptionClass", fault.getCause().getClass().getName());
        // headers.put("NTX_ERROR_HANDLER_ExceptionMessage", fault.getCause().getMessage());
        headers.put(CxfConstants.OPERATION_NAME, getOperationName(message));
        try {
            addSystemHeaders(message, headers);
        } catch (Exception e) {
            LOG.info("Unable to add source system information to headers in HandleOutFaultInterceptor");
        }
        return headers;
    }

    private void addSystemHeaders(SoapMessage message, Map headers) {
        Message inMessage = message.getExchange().getInMessage();
        Map<String, List<String>> inHeaders = Headers.getSetProtocolHeaders(inMessage);
        headers.put(SystemHeadersConstants.HEADER_SYSTEM_NAME,
                inHeaders.get(SystemHeadersConstants.HEADER_SYSTEM_NAME).get(0));
        headers.put(SystemHeadersConstants.HEADER_SYSTEM_ID,
                inHeaders.get(SystemHeadersConstants.HEADER_SYSTEM_ID).get(0));
    }

    private String getOperationName(SoapMessage message) {
        String operation = "";
        try {
            operation = message.getExchange().getBindingOperationInfo().getOperationInfo().getName().getLocalPart();
        } catch (Exception e) {
            LOG.error(e);
        }
        LOG.debug("operation name: " + operation);
        return operation;
    }

    private void sendErrorResponse(Message message, int responseCode, String ret) {
        Message outMessage = getOutMessage(message);
        outMessage.put(Message.RESPONSE_CODE, responseCode);
        // Set the response headers
        Map responseHeaders = (Map) outMessage.get(Message.PROTOCOL_HEADERS);
        if (responseHeaders != null) {
            responseHeaders.clear();
            responseHeaders.put("CAMEL_ERROR", Arrays.asList(ret));
        }
        message.getInterceptorChain().abort();
        try {
            getConduit(message).prepare(outMessage);
            write(outMessage, ret);
        } catch (IOException e) {
            LOG.warn(e.getMessage(), e);
        }
    }

    private Message getOutMessage(Message inMessage) {
        Exchange exchange = inMessage.getExchange();
        Message outMessage = exchange.getOutMessage();
        if (outMessage == null) {
            Endpoint endpoint = exchange.get(Endpoint.class);
            outMessage = endpoint.getBinding().createMessage();
            exchange.setOutMessage(outMessage);
        }
        outMessage.putAll(inMessage);
        return outMessage;
    }

    private Conduit getConduit(Message inMessage) throws IOException {
        Exchange exchange = inMessage.getExchange();
        Conduit conduit = exchange.getDestination().getBackChannel(inMessage);
        exchange.setConduit(conduit);
        return conduit;
    }

    private void write(Message outMessage, String ret) throws IOException {
        OutputStream os = outMessage.getContent(OutputStream.class);
        os.write(ret.getBytes(StandardCharsets.UTF_8));
        os.flush();
        os.close();
    }
}
