/*-
 * ~~~~~~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.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.camel.CamelContext;
import org.apache.camel.ProducerTemplate;
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;

/**
 * 
 * Prepare soap response and send back to calling system. 
 * Body can be simple string.
 * 
 * Example:
 * 
 * <bean id="handleOutFaultInterceptor" class="ru.entaxy.esb.system.core.common.error.handler.interceptor.EntaxyCxfFaultInterceptor">
 *      <argument ref="current-context-id"/>
 *      <argument value="direct:error_from_cxf"/>
 *      <property name="wrap" value="true"/>
 *      <property name="log" value="true"/>
 *  </bean>
 *  
 * append bean to cxf service description
 * 
 * <camelcxf:outFaultInterceptors>
 *     <ref component-id="handleOutFaultInterceptor"/>
 * </camelcxf:outFaultInterceptors>
 *
 */
public class EntaxyCxfFaultInterceptor extends AbstractSoapInterceptor {

	private static final Log LOG = LogFactory.getLog(EntaxyCxfFaultInterceptor.class);

    private final ProducerTemplate template;
    /**
     * endpoint to custom error handler route for 
     * preparing soap response
     */
    private final String endpointUri;
    /**
     * wrap with soap envelope
     */
    private boolean wrap = false;
    /**
     * wrapper endpoint, can be custom
     */
    private String wrapEndpoint = "direct-vm:soap-wrap";
    /**
     * 1 line logging:
     * <loggingKey> <errorMessage>
     */
    private boolean log = false;
    /**
     * use service logging key
     */
    private boolean loggingKeyUse = false;
    /**
     * default header with logging key
     */
    private String loggingKeyHeader = "NTX_loggingKey";

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

    public void handleMessage(SoapMessage message) {
        LOG.debug("EntaxyCxfFaultInterceptor.handleMessage invocation");
        Map<String, Object> headers = prepareHeaders(message);
        logging(headers);
        String response = template.requestBodyAndHeaders(this.endpointUri, "",  headers, String.class);
        if (wrap) {
            response = template.requestBodyAndHeaders(this.wrapEndpoint, response, headers, String.class);
        }
        LOG.debug("EntaxyCxfFaultInterceptor.handleMessage result from camel: \n" + response);
        sendErrorResponse(message, 200, response, headers);
    }

    protected void logging(Map<String, Object> headers) {
        if (log) {
        	Fault fault = (Fault) headers.get("NTX_ERROR_HANDLER_SoapFault");
        	StringBuilder errorMessage = new StringBuilder();
        	if (loggingKeyUse && headers.get(loggingKeyHeader) != null) {
        		 errorMessage.append(headers.get(loggingKeyHeader));
        		 errorMessage.append(" ");
        	}
        	errorMessage.append(fault.getCause().getMessage());
        	LOG.error(errorMessage);
        }
	}

    protected Map<String, Object> prepareHeaders(SoapMessage message) {
        Map<String, List<String>> inHeaders = Headers.getSetProtocolHeaders(message);
        
        Map<String, Object> headers = new HashMap<>();
        Object value;
        for (Map.Entry<String, List<String>> e : inHeaders.entrySet()) {
        	if (e.getValue() != null && e.getValue().size() == 1 && e.getValue().get(0) instanceof String) {
        		value = e.getValue().get(0);
        	} else {
        		value = e.getValue();
        	}
        	headers.put(e.getKey(), value);
        }
        
        Fault fault = (Fault) message.getContent(Exception.class);
        headers.put("NTX_ERROR_HANDLER_SoapFault", fault);
        return headers;
	}
    

    protected void sendErrorResponse(Message message, int responseCode, String ret,  Map<String, Object> headers) {
        Message outMessage = getOutMessage(message);
        outMessage.put(Message.ENCODING, StandardCharsets.UTF_8.name());
        outMessage.put(Message.RESPONSE_CODE, responseCode);
        // Clear the response headers
        @SuppressWarnings("rawtypes")
		Map responseHeaders = (Map) outMessage.get(Message.PROTOCOL_HEADERS);
        if (responseHeaders != null) {
            responseHeaders.clear();
        }
        message.getInterceptorChain().abort();
        try {
            getConduit(message).prepare(outMessage);
            write(outMessage, ret);
        } catch (IOException e) {
            LOG.warn(e.getMessage(), e);
        }
    }

    protected 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;
    }

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

    protected void write(Message outMessage, String ret) throws IOException {
    	try (OutputStream os = outMessage.getContent(OutputStream.class);) {
    		os.write(ret.getBytes(StandardCharsets.UTF_8));
            os.flush();
    	}
    }
    
    public void setWrap(boolean wrap) {
        this.wrap = wrap;
    }
    public void setWrapEndpoint(String wrapEndpoint) {
        this.wrapEndpoint = wrapEndpoint;
    }
	public void setLog(boolean log) {
		this.log = log;
	}
	public void setLoggingKeyUse(boolean loggingKeyUse) {
		this.loggingKeyUse = loggingKeyUse;
	}
	public void setLoggingKeyHeader(String loggingKeyHeader) {
		this.loggingKeyHeader = loggingKeyHeader;
	}
    
}
