package org.jenkinsci.plugins.servicenow;

/**
 * Created by c5403 on 1/22/2018.
 */

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Strings;
import com.google.common.io.ByteStreams;
import com.google.common.io.CharStreams;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.entity.ContentType;
import org.apache.http.impl.client.CloseableHttpClient;
import org.jenkinsci.plugins.scriptsecurity.sandbox.whitelists.Whitelisted;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Serializable;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class ResponseContentSupplier implements Serializable, AutoCloseable {

    public enum ResponseHandle {
        NONE,
        LEAVE_OPEN,
        STRING
    }
    private static final long serialVersionUID = 1L;

    private int status;
    private Map<String, List<String>> headers = new HashMap<>();
    private String charset;

    private ResponseHandle responseHandle;
    private String content;
    @SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED")
    private transient InputStream contentStream;
    @SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED")
    private transient CloseableHttpClient httpclient;

    public ResponseContentSupplier(String content, int status) {
        this.content = content;
        this.status = status;
    }

    public ResponseContentSupplier(ResponseHandle responseHandle, HttpResponse response) {
        this.status = response.getStatusLine().getStatusCode();
        this.responseHandle = responseHandle;
        readHeaders(response);
        readCharset(response);

        try {
            HttpEntity entity = response.getEntity();
            InputStream entityContent = entity != null ? entity.getContent() : null;

            if (responseHandle == ResponseHandle.STRING && entityContent != null) {
                byte[] bytes = ByteStreams.toByteArray(entityContent);
                contentStream = new ByteArrayInputStream(bytes);
                content = new String(bytes, Strings.isNullOrEmpty(charset) ?
                        Charset.defaultCharset().name() : charset);
            } else {
                contentStream = entityContent;
            }
        } catch (IOException e) {
            throw new IllegalStateException(e);
        }
    }

    @Whitelisted
    public int getStatus() {
        return this.status;
    }

    @Whitelisted
    public Map<String, List<String>> getHeaders() {
        return this.headers;
    }

    @Whitelisted
    public String getCharset() {
        return charset;
    }

    @Whitelisted
    public String getContent() {
        if (responseHandle == ResponseHandle.STRING) {
            return content;
        }
        if (content != null) {
            return content;
        }

        try (InputStreamReader in = new InputStreamReader(contentStream,
                Strings.isNullOrEmpty(charset) ? Charset.defaultCharset().name() : charset)) {
            content = CharStreams.toString(in);
            return content;
        } catch (IOException e) {
            throw new IllegalStateException("Error reading response. " +
                    "If you are reading the content in pipeline you should pass responseHandle: 'LEAVE_OPEN' and " +
                    "close the response(response.close()) after consume it.", e);
        }
    }

    @Whitelisted
    public Map<String, Object> getContentMap() {
        String content = getContent();
        try{
            return new ObjectMapper().readValue(content, new TypeReference<Map<String, Object>>(){});
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Whitelisted
    public InputStream getContentStream() {
        return contentStream;
    }

    private void readCharset(HttpResponse response) {
        Charset charset = null;
        ContentType contentType = ContentType.get(response.getEntity());
        if (contentType != null) {
            charset = contentType.getCharset();
            if (charset == null) {
                ContentType defaultContentType = ContentType.getByMimeType(contentType.getMimeType());
                if (defaultContentType != null) {
                    charset = defaultContentType.getCharset();
                }
            }
        }
        if (charset != null) {
            this.charset = charset.name();
        }
    }

    private void readHeaders(HttpResponse response) {
        Header[] respHeaders = response.getAllHeaders();
        for (Header respHeader : respHeaders) {
            List<String> hs = headers.get(respHeader.getName());
            if (hs == null) {
                headers.put(respHeader.getName(), hs = new ArrayList<>());
            }
            hs.add(respHeader.getValue());
        }
    }

    @Override
    public String toString() {
        return "Status: " + this.status;
    }

    @Override
    public void close() throws IOException {
        if (httpclient != null) {
            httpclient.close();
        }
        if (contentStream != null) {
            contentStream.close();
        }
    }

    void setHttpClient(CloseableHttpClient httpclient) {
        this.httpclient = httpclient;
    }
}