/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF 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.camel.quarkus.component.cxf.soap.converter.it;

import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.xml.transform.Source;
import javax.xml.transform.dom.DOMSource;

import org.w3c.dom.Element;

import io.quarkus.runtime.LaunchMode;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.Produces;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.component.cxf.common.CxfPayload;
import org.apache.camel.component.cxf.common.DataFormat;
import org.apache.camel.component.cxf.jaxws.CxfEndpoint;
import org.apache.camel.converter.jaxp.XmlConverter;
import org.apache.camel.wsdl_first.types.GetPerson;
import org.apache.camel.wsdl_first.types.GetPersonResponse;
import org.apache.cxf.binding.soap.SoapHeader;
import org.apache.cxf.ext.logging.LoggingFeature;
import org.apache.cxf.staxutils.StaxUtils;
import org.eclipse.microprofile.config.Config;
import org.eclipse.microprofile.config.ConfigProvider;

@ApplicationScoped
public class CxfSoapConverterRoutes extends RouteBuilder {

    protected static final String ELEMENT_NAMESPACE = "http://camel.apache.org/wsdl-first/types";

    public static final String PATTERN_GET_PERSON = "<GetPerson .*><personId>(.+)</personId></GetPerson>";

    public static final String REQUEST_GET_PERSON = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
            "<GetPerson xmlns=\"http://camel.apache.org/wsdl-first/types\" " +
            "xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">" +
            "<personId>%s</personId>" +
            "</GetPerson>";

    public static final String RESPONSE_GET_PERSON = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
            "<GetPersonResponse xmlns=\"http://camel.apache.org/wsdl-first/types\" " +
            "xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">" +
            "<personId>%s</personId>" +
            "</GetPersonResponse>";

    @Inject
    @Named("loggingFeatureConverter")
    LoggingFeature loggingFeature;

    @Override
    public void configure() {

        from("direct:converterInvoker")
                .process(exchange -> {
                    Map<String, Object> headers = exchange.getIn().getHeaders();
                    List<Source> elements = new ArrayList<>();
                    String reqMessage;
                    headers.put("address", getServerUrl() + "/soapservice/PayLoadConvert/RouterPort");
                    reqMessage = String.format(REQUEST_GET_PERSON, exchange.getIn().getBody(String.class));
                    elements.add(new DOMSource(StaxUtils
                            .read(new StringReader(reqMessage))
                            .getDocumentElement()));
                    CxfPayload payload = new CxfPayload<>(
                            new ArrayList<SoapHeader>(), elements, null);
                    exchange.getIn().setBody(payload);
                })
                .toD("cxf:bean:soapConverterEndpoint?address=${header.address}");

        from("cxf:bean:soapConverterEndpointConsumer")
                .process(exchange -> {
                    String operation = exchange.getIn().getHeader("operation", String.class);
                    if ("pojo".equals(operation)) {
                        // just try to turn the payload to the parameter we want
                        // to use
                        GetPerson request = exchange.getIn().getBody(GetPerson.class);

                        GetPersonResponse getPersonResponse = new GetPersonResponse();
                        getPersonResponse.setPersonId(request.getPersonId() + "pojo");
                        exchange.getMessage().setBody(getPersonResponse);
                    } else if ("xml".equals(operation)) {
                        // just try to turn the payload to the parameter we want
                        // to use
                        GetPerson request = exchange.getIn().getBody(GetPerson.class);

                        exchange.getMessage().setBody(String.format(RESPONSE_GET_PERSON, request.getPersonId() + "xml"));
                    } else if ("consumer".equals(operation)) {
                        @SuppressWarnings("unchecked")
                        CxfPayload<SoapHeader> requestPayload = exchange.getIn().getBody(CxfPayload.class);
                        List<Source> inElements = requestPayload.getBodySources();
                        Element in = new XmlConverter().toDOMElement(inElements.get(0));
                        // Just check the element namespace
                        if (!in.getNamespaceURI().equals(ELEMENT_NAMESPACE)) {
                            throw new IllegalArgumentException("Wrong element namespace");
                        }
                        // You can use a customer toStringConverter to turn a CxfPayLoad message into String as you want
                        String request = exchange.getIn().getBody(String.class);
                        //get value from request and therefore check the request
                        Pattern r = Pattern.compile(PATTERN_GET_PERSON);
                        Matcher m = r.matcher(request);

                        if (m.find()) {
                            exchange.getMessage().setBody(String.format(RESPONSE_GET_PERSON, m.group(1) + "consumer"));
                        } else {
                            throw new IllegalArgumentException("Unexpected content");
                        }
                    }
                });
    }

    @Produces
    @ApplicationScoped
    @Named
    CxfEndpoint soapConverterEndpoint() {
        final CxfEndpoint result = new CxfEndpoint();
        result.setDataFormat(DataFormat.PAYLOAD);
        result.getFeatures().add(loggingFeature);
        result.setServiceClass(org.apache.camel.wsdl_first.Person.class);
        result.setAddress("/PayLoadConvert/RouterPort");
        return result;
    }

    @Produces
    @ApplicationScoped
    @Named
    CxfEndpoint soapConverterEndpointConsumer() {
        return soapConverterEndpoint();
    }

    @Produces
    @ApplicationScoped
    @Named("loggingFeatureConverter")
    public LoggingFeature loggingFeature() {
        final LoggingFeature result = new LoggingFeature();
        result.setPrettyLogging(true);
        return result;
    }

    private static String getServerUrl() {
        Config config = ConfigProvider.getConfig();
        final int port = LaunchMode.current().equals(LaunchMode.TEST) ? config.getValue("quarkus.http.test-port", Integer.class)
                : config.getValue("quarkus.http.port", Integer.class);
        return String.format("http://localhost:%d", port);
    }

}
