package com.armundia.app.utilities.rest;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.StringJoiner;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.GZIPInputStream;

import javax.ws.rs.HttpMethod;
import javax.ws.rs.core.MediaType;

public class RestClient {
    public static final Logger log = Logger.getLogger(RestClient.class.getName());

    private String urlString = "";
    private String requestMethod = null;
    private MediaType contentType = null;
    private MediaType acceptType = MediaType.APPLICATION_JSON_TYPE;
    private Map<String, String> headers = null;
    private Map<String, Object> body = null;

    private HttpURLConnection conn;

    private RestClient(String urlString) {
        this.urlString = urlString;
    }

    public static RestClient create(String urlString) {
        return new RestClient(urlString);
    }

    public RestClient setRequestMethod(String requestMethod) {
        this.requestMethod = requestMethod;
        return this;
    }

    public RestClient setContentType(MediaType contentType) {
        this.contentType = contentType;
        return this;
    }

    public RestClient setAcceptType(MediaType acceptType) {
        log.log(Level.WARNING,
                "setAcceptType: operation not supported yet, please implement other type of accept header");
        return this;
    }

    public RestClient body(Map<String, Object> body) {
        this.body = body;
        return this;
    }

    public <T> RestResult getResult(Class<T> type) {
        try {
            prepareConnection();
            return getResponse(type);
        } catch (IOException e) {
            log.log(Level.SEVERE, "Errore di lettura response", e);
        }
        return null;
    }

    private void prepareConnection() throws IOException {
        String bodyString = this.getBodyString();
        
        URL url = null;
        if (this.body != null && this.requestMethod.equals(HttpMethod.GET)) {
            url = new URL(this.urlString + '?' + bodyString);
        } else {
            url = new URL(urlString);
        }

        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        this.conn = connection;

        if (headers != null) {
            for (String header : this.headers.keySet()) {
                connection.setRequestProperty(header, this.headers.get(header));
            }
        }

        connection.setRequestMethod(this.requestMethod);
        if (this.contentType != null) {
            connection.setRequestProperty("Content-Type", this.contentType.toString());
        }
        connection.setRequestProperty("Accept", this.acceptType.toString());

        if (!bodyString.equals("")) {
            connection.setDoOutput(true);
            if (this.contentType.equals(MediaType.APPLICATION_JSON_TYPE) || this.contentType.equals(MediaType.APPLICATION_FORM_URLENCODED_TYPE)) {
                connection.getOutputStream().write(bodyString.toString().getBytes(StandardCharsets.UTF_8));
            }
        }
    }

    private String getBodyString() {
        String bodyString = "";
        if (this.body != null) {
            if (this.contentType == null || this.contentType.equals(MediaType.APPLICATION_FORM_URLENCODED_TYPE)) {
                try {
                    StringJoiner sj = new StringJoiner("&");
                    for (Map.Entry<String, Object> entry : this.body.entrySet()) {
                        sj.add(URLEncoder.encode(entry.getKey(), "UTF-8") + "=" + URLEncoder.encode(entry.getValue().toString(), "UTF-8"));
                    }
                    bodyString = sj.toString();
                } catch (UnsupportedEncodingException e) {
                    log.log(Level.SEVERE, "Error during URL encoding", e);
                    bodyString = "";
                }
            } else if (this.contentType.equals(MediaType.APPLICATION_JSON_TYPE)) {
                bodyString = JsonbUtils.toJson(this.body);
            }
        }
        return bodyString;
    }

    private <T> RestResult getResponse(Class<T> type) throws IOException {
        HttpURLConnection connection = this.conn;
        connection.connect();
        // Read the response
        int codeResp = connection.getResponseCode();
        String output = "";
        RestResult result = new RestResult();
        result.setCode(codeResp);
        if (codeResp == 200) {
            String contentEncoding = connection.getHeaderField("Content-Encoding");
            BufferedReader reader = null;
            // Se il contenuto è compresso con GZIP
            if ("gzip".equalsIgnoreCase(contentEncoding)) {
                reader = new BufferedReader(
                        new InputStreamReader(new GZIPInputStream(connection.getInputStream()), StandardCharsets.UTF_8));
            } else {
                reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8));
            }

            String outputTmp;
            while ((outputTmp = reader.readLine()) != null) {
                output = outputTmp;
            }
            log.log(Level.INFO, "Response CHIAMATA' " + output);
            
            if (output != null) {
                try {
                    if (type.equals(String.class)) {
                        result.setData(output);
                    } else if (this.acceptType.equals(MediaType.APPLICATION_JSON_TYPE)) {
                        result.setData(JsonbUtils.fromJson(output, type));
                    }

                    return result;
                } catch (Exception e) {
                    log.log(Level.SEVERE, "Errore in JsonbUtils.fromJson" + e);
                }
            }
        } else {
            if (connection.getErrorStream() != null) {
                try (BufferedReader br = new BufferedReader(new InputStreamReader(connection.getErrorStream()))) {
                    String outputErr = extractOutput(br);
                    result.setMessage(outputErr);
                    log.log(Level.SEVERE, "Errore nella chiamata' " + outputErr);
                }
            }
        }
        return result;
    }

    private static String extractOutput(BufferedReader br) {
        String output = "";
        try {
            String outputTmp;
            while ((outputTmp = br.readLine()) != null) {
                output = outputTmp;
            }
        } catch (IOException e) {
            log.log(Level.SEVERE, "Errore di lettura response", e);
        }
        return output;
    }
}
