package hr.com.port.ips.eracun.provider.pondi;

import com.google.gson.Gson;
import hr.com.port.ips.eracun.provider.pondi.dto.common.ErrorResponse;
import hr.com.port.functions.Functions;
import org.apache.log4j.Logger;

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.Map;

public class PondiClient {

    private static final Logger logger = Logger.getLogger(PondiClient.class);

    private final String baseUrl;
    private final String apiKey;
    private final Gson gson = new Gson();

    private int maxRetries = 2;
    private long retrySleepMs = 500L;

    public PondiClient(String baseUrl, String apiKey) {
        if (baseUrl != null && baseUrl.endsWith("/")) {
            this.baseUrl = baseUrl.substring(0, baseUrl.length() - 1);
        } else {
            this.baseUrl = baseUrl;
        }
        this.apiKey = apiKey;
    }

    public String get(String path, Map<String, String> query) throws Exception {
        String url = buildUrl(path, query);
        return execute("GET", url, null, "application/json");
    }

    public String postJson(String path, String body) throws Exception {
        String url = buildUrl(path, null);
        return execute("POST", url, body, "application/json");
    }

    private String buildUrl(String path, Map<String, String> query) throws Exception {
        StringBuilder sb = new StringBuilder();
        sb.append(this.baseUrl);
        if (!path.startsWith("/")) sb.append("/");
        sb.append(path);
        if (query != null && !query.isEmpty()) {
            sb.append("?");
            boolean first = true;
            for (Map.Entry<String, String> e : query.entrySet()) {
                if (!first) sb.append("&");
                first = false;
                sb.append(URLEncoder.encode(e.getKey(), "UTF-8"));
                sb.append("=");
                sb.append(URLEncoder.encode(e.getValue(), "UTF-8"));
            }
        }
        return sb.toString();
    }

    private String execute(String method, String urlStr, String body, String contentType) throws Exception {
        int attempt = 0;
        while (true) {
            attempt++;
            HttpURLConnection conn = null;
            try {
                if (logger.isDebugEnabled()) logger.debug("HTTP " + method + " " + urlStr);
                URL url = new URL(urlStr);
                conn = (HttpURLConnection) url.openConnection();
                conn.setRequestMethod(method);
                conn.setConnectTimeout(30000);
                conn.setReadTimeout(30000);
                conn.setRequestProperty("Accept", "application/json");
                conn.setRequestProperty("Authorization", apiKey);
                if (body != null) {
                    conn.setDoOutput(true);
                    conn.setRequestProperty("Content-Type", contentType);
                    DataOutputStream wr = new DataOutputStream(conn.getOutputStream());
                    wr.write(body.getBytes("UTF-8"));
                    wr.flush();
                    wr.close();
                }
                int code = conn.getResponseCode();
                String resp = readStream(code < 400 ? conn.getInputStream() : conn.getErrorStream());
                if (code >= 200 && code < 300) return resp;
                if (code >= 500 && attempt <= (1 + maxRetries)) {
                    try { Thread.sleep(retrySleepMs * attempt); } catch (InterruptedException ie) {}
                    continue;
                }
                ErrorResponse er = null;
                try { er = gson.fromJson(resp, ErrorResponse.class); } catch (Exception ignore) {}
                String msg = "HTTP " + code + " error calling " + urlStr + (er != null && er.error != null ? (": " + er.error) : "");
                if (logger.isDebugEnabled()) logger.debug("Response payload: " + resp);
                throw new IOException(msg);
            } catch (Exception ex) {
                if (attempt <= (1 + maxRetries)) {
                    if (logger.isDebugEnabled()) logger.debug("Attempt " + attempt + " failed, will retry. " + ex.getMessage());
                    try { Thread.sleep(retrySleepMs * attempt); } catch (InterruptedException ie) {}
                    continue;
                }
                logger.error(new Functions().logging(ex));
                throw ex;
            } finally {
                if (conn != null) conn.disconnect();
            }
        }
    }

    private String readStream(InputStream in) throws IOException {
        if (in == null) return "";
        BufferedReader br = new BufferedReader(new InputStreamReader(in, "UTF-8"));
        StringBuilder sb = new StringBuilder();
        String line;
        while ((line = br.readLine()) != null) sb.append(line);
        br.close();
        return sb.toString();
    }
}
