/*
 * Copyright 2016 Red Hat, Inc. and/or its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 *
 *      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.kie.server.router.handlers;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Deque;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

import io.undertow.server.HttpHandler;
import io.undertow.server.HttpServerExchange;
import io.undertow.server.handlers.ResponseCodeHandler;
import io.undertow.server.handlers.proxy.ProxyHandler;
import io.undertow.util.HeaderValues;
import io.undertow.util.Headers;
import io.undertow.util.HttpString;
import org.kie.server.router.proxy.aggragate.ResponseAggregator;

public class QueriesDataHttpHandler extends AbstractAggregateHttpHandler {

    public QueriesDataHttpHandler(HttpHandler httpHandler, AdminHttpHandler adminHandler) {
        super(httpHandler, adminHandler);
    }

    @Override
    public void handleRequest(HttpServerExchange exchange) throws Exception {
        if (exchange.getRequestMethod().equals(HttpString.tryFromString("OPTIONS"))) {
            handleOptions(exchange);
            return;
        }
        
        if (exchange.getRequestMethod().equals(HttpString.tryFromString("POST"))) {

            Map<String, Deque<String>> queryParams = exchange.getQueryParameters();
            // collect and alter paging
            Integer page = 0;
            Integer pageSize = 10;


            Deque<String> originalPage = queryParams.get("page");
            if (originalPage != null && !originalPage.isEmpty()) {
                page = Integer.parseInt(originalPage.getFirst());
            }
            Deque<String> originalPageSize = queryParams.remove("pageSize");
            if (originalPageSize != null && !originalPageSize.isEmpty()) {
                pageSize = Integer.parseInt(originalPageSize.getFirst());
            }
            boolean useAdvanced = pageSize.intValue() != -1;

            final String routerPage = "0";
            // need to add 1 to page for proper size of page
            final String routerPageSize = String.valueOf((1 + page) * pageSize);

            // collect sorting
            String sortBy = null;
            boolean sortOder = true;
            Deque<String> originalSortBy = queryParams.get("sort");
            if (originalSortBy != null && !originalSortBy.isEmpty()) {
                sortBy = originalSortBy.getFirst();
            }
            Deque<String> originalSortOrder = queryParams.get("sortOrder");
            if (originalSortOrder != null && !originalSortOrder.isEmpty()) {
                sortOder = Boolean.parseBoolean(originalSortOrder.getFirst());
            }


            StringBuilder requestBody = new StringBuilder();
            // collect body of the request
            exchange.getRequestReceiver().receiveFullString((ex, data) -> {
                requestBody.append(data);
            });

            final String body = requestBody.toString();

            final Map<String,List<String>> responseHeaders = new ConcurrentHashMap<>();
            List<String> returnResponses = getServerHosts().parallelStream().map(url -> {
                String response = null;
                try {
                    response = sendPostRequest(url, body, exchange, responseHeaders, routerPage, routerPageSize);
                } catch (Exception e) {
                    log.error("Error when forwarding request to server", e);
                    removeHostOnException(url, e);
                }

                return response;
            })
            .filter(msg -> msg != null && !msg.trim().isEmpty())
            .collect(Collectors.toList());

            HeaderValues accept = exchange.getRequestHeaders().get(Headers.ACCEPT);
            HeaderValues kieContentType = exchange.getRequestHeaders().get("X-KIE-ContentType");

            String response = "";

            if (returnResponses.size() > 0) {
                ResponseAggregator responseAggregator = adminHandler.getAggregators().stream().filter(a -> a.supports(kieContentType, accept, DEFAULT_ACCEPT)).findFirst().orElseThrow(() ->
                                new RuntimeException("not possible to find response aggregator for " + responseHeaders.get(Headers.ACCEPT))
                );

                if (supportAdvancedAggregate() && useAdvanced) {
                    response = responseAggregator.aggregate(returnResponses, sortBy, sortOder, page, pageSize);
                } else {
                    response = responseAggregator.aggregate(returnResponses);
                }
            }
            responseHeaders.forEach((name, value) -> {
                exchange.getResponseHeaders().putAll(HttpString.tryFromString(name), value);
            });

            exchange.getResponseHeaders().put(Headers.CONTENT_LENGTH, response.getBytes("UTF-8").length);
            exchange.getResponseSender().send(response);


        } else if (exchange.getRequestMethod().equals(HttpString.tryFromString("PUT"))) {

            StringBuilder requestBody = new StringBuilder();
            // collect body of the request
            exchange.getRequestReceiver().receiveFullString((ex, data) -> {
                requestBody.append(data);
            });

            final String body = requestBody.toString();

            final Map<String,List<String>> responseHeaders = new ConcurrentHashMap<>();
            List<String> returnResponses = getServerHosts().parallelStream().map(url -> {
                String response = null;
                try {
                    response = sendPutRequest(url, body, exchange, responseHeaders);
                } catch (Exception e) {
                    log.error("Error when forwarding request to server", e);
                    removeHostOnException(url, e);
                }

                return response;
            })
                    .filter(msg -> msg != null)
                    .collect(Collectors.toList());

            responseHeaders.forEach((name, value) -> {
                exchange.getResponseHeaders().putAll(HttpString.tryFromString(name), value);
            });

            if (returnResponses.size() == 0) {
                ResponseCodeHandler.HANDLE_404.handleRequest(exchange);
                return;
            }
            new ResponseCodeHandler(201).handleRequest(exchange);


        }  else if (exchange.getRequestMethod().equals(HttpString.tryFromString("DELETE"))) {

            final Map<String,List<String>> responseHeaders = new ConcurrentHashMap<>();
            getServerHosts().parallelStream().forEach(url -> {

                try {
                    sendDeleteRequest(url, exchange, responseHeaders);
                } catch (Exception e) {
                    log.error("Error when forwarding request to server", e);
                    removeHostOnException(url, e);
                }


            });

            responseHeaders.forEach((name, value) -> {
                exchange.getResponseHeaders().putAll(HttpString.tryFromString(name), value);
            });

            new ResponseCodeHandler(204).handleRequest(exchange);
            return;



        } else {

            super.handleRequest(exchange);
        }
    }

    protected String sendPostRequest(String url, String body, HttpServerExchange exchange, Map<String,List<String>> responseHeaders, String page, String pageSize) throws Exception {

        URL obj = new URL(url + exchange.getRequestPath() + "?" + exchange.getQueryString().replaceAll(REPLACE_PAGE, "page=" + page).replaceAll(REPLACE_PAGE_SIZE, "pageSize=" + pageSize));
        HttpURLConnection con = (HttpURLConnection) obj.openConnection();
        con.setRequestMethod("POST");

        //add request headers
        exchange.getRequestHeaders().forEach(h -> {
            con.setRequestProperty(h.getHeaderName().toString(), h.getFirst());
        });

        con.setDoOutput(true);
        if (body != null) {
            con.getOutputStream().write(body.getBytes("UTF-8"));
        }

        log.debugf("Sending 'POST' request to URL : %s", obj);
        int responseCode = con.getResponseCode();
        log.debugf("Response Code : %s", responseCode);

        Map<String, List<String>> headers = con.getHeaderFields();
        headers.forEach((k, v) -> {
            if (k != null) {
                responseHeaders.put(k, v);
            }
        });

        BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
        String inputLine;
        StringBuffer response = new StringBuffer();
        while ((inputLine = in.readLine()) != null) {
            response.append(inputLine);

        }
        in.close();

        return response.toString();
    }

    protected String sendPutRequest(String url, String body, HttpServerExchange exchange, Map<String,List<String>> responseHeaders) throws Exception {

        URL obj = new URL(url + exchange.getRequestPath() + "?" + exchange.getQueryString());
        HttpURLConnection con = (HttpURLConnection) obj.openConnection();
        con.setRequestMethod("PUT");

        //add request headers
        exchange.getRequestHeaders().forEach(h -> {
            con.setRequestProperty(h.getHeaderName().toString(), h.getFirst());
        });

        con.setDoOutput(true);
        if (body != null) {
            con.getOutputStream().write(body.getBytes("UTF-8"));
        }

        log.debugf("Sending 'PUT' request to URL : %s", obj);
        int responseCode = con.getResponseCode();
        log.debugf("Response Code : %s", responseCode);

        Map<String, List<String>> headers = con.getHeaderFields();
        headers.forEach((k, v) -> {
            if (k != null) {
                responseHeaders.put(k, v);
            }
        });

        BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
        String inputLine;
        StringBuffer response = new StringBuffer();

        while ((inputLine = in.readLine()) != null) {
            response.append(inputLine);

        }
        in.close();
        return response.toString();
    }

    protected String sendDeleteRequest(String url, HttpServerExchange exchange, Map<String,List<String>> responseHeaders) throws Exception {

        URL obj = new URL(url + exchange.getRequestPath() + "?" + exchange.getQueryString());
        HttpURLConnection con = (HttpURLConnection) obj.openConnection();
        con.setRequestMethod("DELETE");

        //add request headers
        exchange.getRequestHeaders().forEach(h -> {
            con.setRequestProperty(h.getHeaderName().toString(), h.getFirst());
        });

        con.setDoOutput(true);

        log.debugf("Sending 'DELETE' request to URL : %s", obj);
        int responseCode = con.getResponseCode();
        log.debugf("Response Code : %s", responseCode);

        Map<String, List<String>> headers = con.getHeaderFields();
        headers.forEach((k, v) -> {
            if (k != null) {
                responseHeaders.put(k, v);
            }
        });

        BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
        String inputLine;
        StringBuffer response = new StringBuffer();

        while ((inputLine = in.readLine()) != null) {
            response.append(inputLine);

        }
        in.close();

        return response.toString();
    }

}
