/*
 * 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.jt400.it;

import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

import com.ibm.as400.access.QueuedMessage;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import org.apache.camel.CamelContext;
import org.apache.camel.ConsumerTemplate;
import org.apache.camel.Exchange;
import org.apache.camel.ProducerTemplate;
import org.apache.camel.component.jt400.Jt400Component;
import org.apache.camel.component.jt400.Jt400Endpoint;
import org.eclipse.microprofile.config.inject.ConfigProperty;

@Path("/jt400")
@ApplicationScoped
public class Jt400Resource {

    @ConfigProperty(name = "cq.jt400.url")
    String jt400Url;

    @ConfigProperty(name = "cq.jt400.username")
    String jt400Username;

    @ConfigProperty(name = "cq.jt400.password")
    String jt400Password;

    @ConfigProperty(name = "cq.jt400.keyed-queue")
    String jt400KeyedQueue;

    @ConfigProperty(name = "cq.jt400.library")
    String jt400Library;

    @ConfigProperty(name = "cq.jt400.lifo-queue")
    String jt400LifoQueue;

    @ConfigProperty(name = "cq.jt400.message-queue")
    String jt400MessageQueue;

    @ConfigProperty(name = "cq.jt400.message-replyto-queue")
    String jt400MessageReplyToQueue;

    @ConfigProperty(name = "cq.jt400.user-space")
    String jt400UserSpace;

    @Inject
    ProducerTemplate producerTemplate;

    @Inject
    ConsumerTemplate consumerTemplate;

    @Inject
    CamelContext context;

    @Inject
    InquiryMessageHolder inquiryMessageHolder;

    @Path("/dataQueue/read/")
    @POST
    @Produces(MediaType.APPLICATION_JSON)
    public Response keyedDataQueueRead(String key, @QueryParam("format") String format,
            @QueryParam("searchType") String searchType) {

        boolean keyed = key != null && !key.isEmpty();
        String _format = Optional.ofNullable(format).orElse("text");
        String _searchType = Optional.ofNullable(searchType).orElse("EQ");
        StringBuilder suffix = new StringBuilder();

        if (keyed) {
            suffix.append(jt400KeyedQueue)
                    .append(String.format("?keyed=true&format=%s&searchKey=%s&searchType=%s", _format, key, _searchType));
        } else {
            suffix.append(jt400LifoQueue).append(String.format("?readTimeout=100&format=%s", _format));
        }

        Exchange ex = consumerTemplate.receive(getUrlForLibrary(suffix.toString()));

        if ("binary".equals(format)) {
            return generateResponse(new String(ex.getIn().getBody(byte[].class), StandardCharsets.UTF_8), ex);
        }
        return generateResponse(ex.getIn().getBody(String.class), ex);

    }

    @Path("/dataQueue/write/")
    @POST
    @Consumes(MediaType.TEXT_PLAIN)
    @Produces(MediaType.TEXT_PLAIN)
    public Response keyedDataQueueWrite(@QueryParam("key") String key,
            @QueryParam("format") String format,
            String data) {
        String _format = Optional.ofNullable(format).orElse("text");
        boolean keyed = key != null;
        StringBuilder suffix = new StringBuilder();
        Map<String, Object> headers = new HashMap<>();
        String msg;

        if (keyed) {
            suffix.append(jt400KeyedQueue).append("?keyed=true").append("&format=").append(_format);
            headers.put(Jt400Endpoint.KEY, key);
            msg = "Hello From KDQ: " + data;
        } else {
            suffix.append(jt400LifoQueue).append("?format=").append(_format);
            msg = "Hello From DQ: " + data;
        }

        Object retVal;
        if ("binary".equals(format)) {
            byte[] result = (byte[]) producerTemplate.requestBodyAndHeaders(
                    getUrlForLibrary(suffix.toString()),
                    ("Hello (bin) " + data).getBytes(StandardCharsets.UTF_8),
                    headers);
            retVal = new String(result, StandardCharsets.UTF_8);
        } else {
            retVal = producerTemplate.requestBodyAndHeaders(
                    getUrlForLibrary(suffix.toString()),
                    msg,
                    headers);
        }

        return Response.ok().entity(retVal).build();
    }

    @Path("/route/start/{route}")
    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public Response startRoute(@PathParam("route") String routeName) throws Exception {
        if (context.getRouteController().getRouteStatus(routeName).isStartable()) {
            context.getRouteController().startRoute(routeName);
        }

        return Response.ok().entity(context.getRouteController().getRouteStatus(routeName).isStarted()).build();
    }

    @Path("/route/stop/{route}")
    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public Response stopRoute(@PathParam("route") String routeName) throws Exception {
        if (context.getRouteController().getRouteStatus(routeName).isStoppable()) {
            context.getRouteController().stopRoute(routeName);
        }
        boolean resp = context.getRouteController().getRouteStatus(routeName).isStopped();

        return Response.ok().entity(resp).build();
    }

    @Path("/component/stopWrong")
    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public Response stopComponent() throws Exception {
        Jt400Component comp = context.getComponent("jt400", Jt400Component.class);
        comp.close();
        //this second call to close connection won't wprk, because the connection pool is already closing
        //the call would need to read from a resource bundle therefore it covers existence of resource bundle in the native
        comp.getConnectionPool().close();
        return Response.ok().build();
    }

    @Path("/inquiryMessageSetExpected")
    @POST
    public void inquiryMessageSetExpected(String msg) {
        inquiryMessageHolder.setMessageText(msg);
    }

    @Path("/inquiryMessageProcessed")
    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String inquiryMessageProcessed() {
        return String.valueOf(inquiryMessageHolder.isProcessed());
    }

    @Path("/messageQueue/write/")
    @POST
    @Produces(MediaType.TEXT_PLAIN)
    public Response messageQueueWrite(String data) {
        Object ex = producerTemplate.requestBody(getUrlForLibrary(jt400MessageQueue), "Hello from MQ: " + data);

        return Response.ok().entity(ex).build();
    }

    @Path("/messageQueue/read/")
    @POST
    @Produces(MediaType.APPLICATION_JSON)
    public Response messageQueueRead(@QueryParam("queue") String queue) {
        Exchange ex = consumerTemplate
                .receive(getUrlForLibrary(queue == null ? jt400MessageQueue : queue) + "?messageAction=SAME");

        return generateResponse(ex.getIn().getBody(String.class), ex);
    }

    @Path("/programCall")
    @POST
    @Consumes(MediaType.TEXT_PLAIN)
    @Produces(MediaType.APPLICATION_JSON)
    public Response programCall() throws Exception {
        Exchange ex = producerTemplate.request(getUrl("/qsys.lib/QUSRTVUS.PGM?fieldsLength=20,4,4,16&outputFieldsIdx=3"),
                exchange -> {
                    String userSpace = String.format("%-10s", jt400UserSpace);
                    String userLib = String.format("%-10s", jt400Library);

                    Object[] parms = new Object[] {
                            userSpace + userLib, // Qualified user space name
                            1, // starting position
                            16, // length of data
                            "" // output
                    };
                    exchange.getIn().setBody(parms);
                });

        return Response.ok().entity(ex.getIn().getBody(Object[].class)[3]).build();
    }

    private String getUrlForLibrary(String suffix) {
        return String.format("jt400://%s:%s@%s%s", jt400Username, jt400Password, jt400Url,
                "/QSYS.LIB/" + jt400Library + ".LIB/" + suffix);
    }

    private String getUrl(String suffix) {
        return String.format("jt400://%s:%s@%s%s", jt400Username, jt400Password, jt400Url, suffix);
    }

    Response generateResponse(String result, Exchange ex) {
        Map<String, Object> retVal = new HashMap<>();

        retVal.put("result", result);
        ex.getIn().getHeaders().entrySet().stream().forEach(e -> {
            if (e.getValue() instanceof QueuedMessage) {
                retVal.put(e.getKey(), "QueuedMessage: " + ((QueuedMessage) e.getValue()).getText());
            } else {
                retVal.put(e.getKey(), e.getValue());
            }
        });

        return Response.ok().entity(retVal).build();

    }
}
