package com.yihu.hos.http;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.Consts;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.auth.AuthScope;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.*;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.FileEntity;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;

import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * @created Airhead 2016/8/24.
 */
class HTTPClientDefaultImpl implements HTTPClient {
    private static final Log log = LogFactory.getLog(HTTPClientDefaultImpl.class);

    private CloseableHttpClient httpClient;
    private HttpClientContext httpClientContext;

    HTTPClientDefaultImpl(CloseableHttpClient httpClient, CredentialsProvider credentialsProvider) {
        this.httpClient = httpClient;
        if (credentialsProvider != null) {
            this.httpClientContext = HttpClientContext.create();
            httpClientContext.setCredentialsProvider(credentialsProvider);
        }
    }

    public HTTPResponse get(String url) {
        return get(url, null);
    }

    public HTTPResponse get(String url, Map<String, String> params) {
        return get(url, params, null);
    }

    public HTTPResponse get(String url, Map<String, String> params, Map<String, String> headers) {
        CloseableHttpResponse response;
        HttpEntity entity = null;
        try {
            HttpGet httpGet = new HttpGet(formatURL(url, params));
            authenticate(url, httpClientContext);
            addHeads(httpGet, headers);

            response = httpClient.execute(httpGet, httpClientContext);
            entity = response.getEntity();

            return new HTTPResponse(response.getStatusLine().getStatusCode(), EntityUtils.toString(entity));
        } catch (IOException ex) {
            log.error(ex.getMessage());
        } finally {
            try {
                EntityUtils.consume(entity);
            } catch (IOException ex) {
                log.error(ex.getMessage());
            }
        }

        return new HTTPResponse(500, "");
    }

    public HTTPResponse post(String url) {
        return post(url, null);
    }

    public HTTPResponse post(String url, Map<String, String> params) {
        return post(url, params, null);
    }

    public HTTPResponse post(String url, Map<String, String> params, Map<String, String> headers) {
        CloseableHttpResponse response;
        HttpEntity entity = null;
        try {
            HttpPost httpPost = new HttpPost(url);
            authenticate(url, httpClientContext);
            addHeads(httpPost, headers);
            setEntity(httpPost, params);

            response = httpClient.execute(httpPost, httpClientContext);
            entity = response.getEntity();

            return new HTTPResponse(response.getStatusLine().getStatusCode(), EntityUtils.toString(entity));
        } catch (IOException ex) {
            log.error(ex.getMessage());
        } finally {
            try {
                EntityUtils.consume(entity);
            } catch (IOException ex) {
                log.error(ex.getMessage());
            }
        }

        return new HTTPResponse(500, "");
    }

    public HTTPResponse postFile(String url, String path) {
        return postFile(url, path, null);
    }

    public HTTPResponse postFile(String url, String path, Map<String, String> params) {
        return postFile(url, path, params, null);
    }

    public HTTPResponse postFile(String url, String path, Map<String, String> params, Map<String, String> headers) {
        CloseableHttpResponse response;
        HttpEntity entity = null;
        try {
            HttpPost httpPost = new HttpPost(url);
            authenticate(url, httpClientContext);
            addHeads(httpPost, headers);

            File file = new File(path);
            FileEntity fileEntity = new FileEntity(file, ContentType.create("text/plain", "UTF-8"));

            httpPost.setEntity(fileEntity);

            response = httpClient.execute(httpPost, httpClientContext);
            entity = response.getEntity();

            return new HTTPResponse(response.getStatusLine().getStatusCode(), EntityUtils.toString(entity));
        } catch (IOException ex) {
            log.error(ex.getMessage());
        } finally {
            try {
                EntityUtils.consume(entity);
            } catch (IOException ex) {
                log.error(ex.getMessage());
            }
        }

        return new HTTPResponse(500, "");
    }

    public HTTPResponse put(String url) {
        return put(url, null);
    }

    public HTTPResponse put(String url, Map<String, String> params) {
        return put(url, params, null);
    }

    public HTTPResponse put(String url, Map<String, String> params, Map<String, String> headers) {
        CloseableHttpResponse response;
        HttpEntity entity = null;
        try {
            HttpPut httpPut = new HttpPut(url);
            authenticate(url, httpClientContext);
            addHeads(httpPut, headers);
            setEntity(httpPut, params);

            response = httpClient.execute(httpPut, httpClientContext);
            entity = response.getEntity();

            return new HTTPResponse(response.getStatusLine().getStatusCode(), EntityUtils.toString(entity));
        } catch (IOException ex) {
            log.error(ex.getMessage());
        } finally {
            try {
                EntityUtils.consume(entity);
            } catch (IOException ex) {
                log.error(ex.getMessage());
            }
        }

        return new HTTPResponse(500, "");
    }

    public HTTPResponse delete(String url) {
        return delete(url, null);
    }

    public HTTPResponse delete(String url, Map<String, String> params) {
        return delete(url, params, null);
    }

    public HTTPResponse delete(String url, Map<String, String> params, Map<String, String> headers) {
        CloseableHttpResponse response;
        HttpEntity entity = null;
        try {
            HttpDelete httpDelete = new HttpDelete(formatURL(url, params));
            authenticate(url, httpClientContext);
            addHeads(httpDelete, headers);

            response = httpClient.execute(httpDelete, httpClientContext);
            entity = response.getEntity();

            return new HTTPResponse(response.getStatusLine().getStatusCode(), EntityUtils.toString(entity));
        } catch (IOException ex) {
            log.error(ex.getMessage());
        } finally {
            try {
                EntityUtils.consume(entity);
            } catch (IOException ex) {
                log.error(ex.getMessage());
            }
        }

        return new HTTPResponse(500, "");
    }

    public HTTPResponse request(String method, String url, Map<String, String> params, Map<String, String> headers) {
        if (method.equals(POST)) {
            return post(url, params, headers);
        }

        if (method.equals(GET)) {
            return get(url, params, headers);
        }

        if (method.equals(PUT)) {
            return put(url, params, headers);
        }

        if (method.equals(DELETE)) {
            return delete(url, params, headers);
        }

        return get(url, params, headers);
    }

    private void addHeads(HttpRequestBase httpRequestBase, Map<String, String> headers) {
        if (headers == null) {
            return;
        }

        headers.forEach(httpRequestBase::addHeader);
    }

    private String formatURL(String url, Map<String, String> params) {
        if (params == null) {
            return url;
        }
        
        final String[] query = {""};
        params.forEach((name, value) -> {
            try {
                query[0] += "&" + name + URLEncoder.encode(value, "UTF-8");
            } catch (UnsupportedEncodingException e) {
                throw new RuntimeException("encode url ");
            }
        });

        if (url.contains("?")) {
            return url + query[0];
        }

        return url + "?" + query[0].substring(1);
    }

    private void authenticate(String url, HttpClientContext httpClientContext) throws MalformedURLException {
        URL r = new URL(url);
        String userInfo = r.getUserInfo();
        if (userInfo == null) {
            return;
        }

        String[] user = userInfo.split(":");
        String host = r.getHost();
        int port = r.getPort();
        CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
        credentialsProvider.setCredentials(new AuthScope(host, port),
                new org.apache.http.auth.UsernamePasswordCredentials(user[0], user[1]));
        if (httpClientContext == null) {
            httpClientContext = HttpClientContext.create();
        }

        httpClientContext.setCredentialsProvider(credentialsProvider);
    }

    private void setEntity(HttpEntityEnclosingRequestBase httpEntityEnclosingRequestBase, Map<String, String> params) {
        if (params == null) {
            return;
        }

        List<NameValuePair> nameValuePairs = new ArrayList<>();
        params.forEach((name, value) -> nameValuePairs.add(new BasicNameValuePair(name, value)));

        UrlEncodedFormEntity postEntity = new UrlEncodedFormEntity(nameValuePairs, Consts.UTF_8);
        httpEntityEnclosingRequestBase.setEntity(postEntity);
    }
}
