package com.sdtool.common.http;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ProxySelector;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.time.Duration;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.locks.ReentrantLock;
import java.util.zip.GZIPInputStream;

@SuppressWarnings("unused")
public final class HttpTools {

    private static final ReentrantLock clientLock = new ReentrantLock();

    private static HttpClient httpClient;

    static {
        //https://blog.csdn.net/qq_40553917/article/details/128067859
        System.setProperty("jdk.httpclient.allowRestrictedHeaders", "connection,content-length,expect,host,upgrade,X-ERP-DataSource-Id,X-ERP-Supplier-Code");

    }

    public static int downloadFile(String remoteUrl, String localFile) {
        //这里均同步下载，使用时使用线程处理
        HttpClient client = HttpClient.newHttpClient();
        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create(remoteUrl))
                .build();

        try {
            HttpResponse<Path> response = client.send(request, HttpResponse.BodyHandlers.ofFile(Path.of(localFile), StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.SYNC));
            return 0;
        } catch (IOException | InterruptedException e) {
            return -1;
        }
    }

    public static CompletableFuture<String> postHttpContentAsync(String url) {
        return remoteHttpContentAsync("POST", url, 0, null, null).thenApply(out -> out.toString(StandardCharsets.UTF_8));
    }

    public static CompletableFuture<String> postHttpContentAsync(String url, Map<String, String> respHeaders) {
        return remoteHttpContentAsync("POST", url, 0, null, null, respHeaders).thenApply(out -> out.toString(StandardCharsets.UTF_8));
    }

    public static CompletableFuture<String> postHttpContentAsync(String url, int timeoutMs) {
        return remoteHttpContentAsync("POST", url, timeoutMs, null, null).thenApply(out -> out.toString(StandardCharsets.UTF_8));
    }

    public static CompletableFuture<String> postHttpContentAsync(String url, int timeoutMs, Map<String, String> respHeaders) {
        return remoteHttpContentAsync("POST", url, timeoutMs, null, null, respHeaders).thenApply(out -> out.toString(StandardCharsets.UTF_8));
    }

    public static CompletableFuture<String> postHttpContentAsync(String url, String body) {
        return remoteHttpContentAsync("POST", url, 0, null, body).thenApply(out -> out.toString(StandardCharsets.UTF_8));
    }

    public static CompletableFuture<String> postHttpContentAsync(String url, String body, Map<String, String> respHeaders) {
        return remoteHttpContentAsync("POST", url, 0, null, body, respHeaders).thenApply(out -> out.toString(StandardCharsets.UTF_8));
    }

    public static CompletableFuture<String> postHttpContentAsync(String url, int timeoutMs, String body) {
        return remoteHttpContentAsync("POST", url, timeoutMs, null, body).thenApply(out -> out.toString(StandardCharsets.UTF_8));
    }

    public static CompletableFuture<String> postHttpContentAsync(String url, int timeoutMs, String body, Map<String, String> respHeaders) {
        return remoteHttpContentAsync("POST", url, timeoutMs, null, body, respHeaders).thenApply(out -> out.toString(StandardCharsets.UTF_8));
    }

    public static CompletableFuture<String> postHttpContentAsync(String url, Map<String, String> headers, String body) {
        return remoteHttpContentAsync("POST", url, 0, headers, body).thenApply(out -> out.toString(StandardCharsets.UTF_8));
    }

    public static CompletableFuture<String> postHttpContentAsync(String url, Map<String, String> headers, String body, Map<String, String> respHeaders) {
        return remoteHttpContentAsync("POST", url, 0, headers, body, respHeaders).thenApply(out -> out.toString(StandardCharsets.UTF_8));
    }

    public static CompletableFuture<String> postHttpContentAsync(String url, int timeoutMs, Map<String, String> headers, String body) {
        return remoteHttpContentAsync("POST", url, timeoutMs, headers, body).thenApply(out -> out.toString(StandardCharsets.UTF_8));
    }

    public static CompletableFuture<String> postHttpContentAsync(String url, int timeoutMs, Map<String, String> headers, String body, Map<String, String> respHeaders) {
        return remoteHttpContentAsync("POST", url, timeoutMs, headers, body, respHeaders).thenApply(out -> out.toString(StandardCharsets.UTF_8));
    }

    public static CompletableFuture<String> postHttpContentAsync(String url, Charset charset) {
        return remoteHttpContentAsync("POST", url, 0, null, null).thenApply(out -> out.toString(charset));
    }

    public static CompletableFuture<String> postHttpContentAsync(String url, Charset charset, Map<String, String> respHeaders) {
        return remoteHttpContentAsync("POST", url, 0, null, null, respHeaders).thenApply(out -> out.toString(charset));
    }

    public static CompletableFuture<String> postHttpContentAsync(String url, int timeoutMs, Charset charset) {
        return remoteHttpContentAsync("POST", url, timeoutMs, null, null).thenApply(out -> out.toString(charset));
    }

    public static CompletableFuture<String> postHttpContentAsync(String url, int timeoutMs, Charset charset, Map<String, String> respHeaders) {
        return remoteHttpContentAsync("POST", url, timeoutMs, null, null, respHeaders).thenApply(out -> out.toString(charset));
    }

    public static CompletableFuture<String> postHttpContentAsync(String url, Charset charset, String body) {
        return remoteHttpContentAsync("POST", url, 0, null, body).thenApply(out -> out.toString(charset));
    }

    public static CompletableFuture<String> postHttpContentAsync(String url, Charset charset, String body, Map<String, String> respHeaders) {
        return remoteHttpContentAsync("POST", url, 0, null, body, respHeaders).thenApply(out -> out.toString(charset));
    }

    public static CompletableFuture<String> postHttpContentAsync(HttpClient client, String url, Charset charset, String body) {
        return remoteHttpContentAsync(client, "POST", url, 0, null, body).thenApply(out -> out.toString(charset));
    }

    public static CompletableFuture<String> postHttpContentAsync(HttpClient client, String url, Charset charset, String body, Map<String, String> respHeaders) {
        return remoteHttpContentAsync(client, "POST", url, 0, null, body, respHeaders).thenApply(out -> out.toString(charset));
    }

    public static CompletableFuture<String> postHttpContentAsync(HttpClient client, String url, Map<String, String> headers, String body) {
        return remoteHttpContentAsync(client, "POST", url, 0, headers, body).thenApply(out -> out.toString(StandardCharsets.UTF_8));
    }

    public static CompletableFuture<String> postHttpContentAsync(HttpClient client, String url, Map<String, String> headers, String body, Map<String, String> respHeaders) {
        return remoteHttpContentAsync(client, "POST", url, 0, headers, body, respHeaders).thenApply(out -> out.toString(StandardCharsets.UTF_8));
    }

    public static CompletableFuture<String> postHttpContentAsync(HttpClient client, String url, Charset charset, Map<String, String> headers, String body) {
        return remoteHttpContentAsync(client, "POST", url, 0, headers, body).thenApply(out -> out.toString(charset));
    }

    public static CompletableFuture<String> postHttpContentAsync(HttpClient client, String url, Charset charset, Map<String, String> headers, String body, Map<String, String> respHeaders) {
        return remoteHttpContentAsync(client, "POST", url, 0, headers, body, respHeaders).thenApply(out -> out.toString(charset));
    }

    public static CompletableFuture<String> postHttpContentAsync(String url, int timeoutMs, Charset charset, String body) {
        return remoteHttpContentAsync("POST", url, timeoutMs, null, body).thenApply(out -> out.toString(charset));
    }

    public static CompletableFuture<String> postHttpContentAsync(String url, int timeoutMs, Charset charset, String body, Map<String, String> respHeaders) {
        return remoteHttpContentAsync("POST", url, timeoutMs, null, body, respHeaders).thenApply(out -> out.toString(charset));
    }

    public static CompletableFuture<String> postHttpContentAsync(String url, Charset charset, Map<String, String> headers, String body) {
        return remoteHttpContentAsync("POST", url, 0, headers, body).thenApply(out -> out.toString(charset));
    }

    public static CompletableFuture<String> postHttpContentAsync(String url, Charset charset, Map<String, String> headers, String body, Map<String, String> respHeaders) {
        return remoteHttpContentAsync("POST", url, 0, headers, body, respHeaders).thenApply(out -> out.toString(charset));
    }

    public static CompletableFuture<String> postHttpContentAsync(String url, int timeoutMs, Charset charset, Map<String, String> headers, String body) {
        return remoteHttpContentAsync("POST", url, timeoutMs, headers, body).thenApply(out -> out.toString(charset));
    }

    public static CompletableFuture<String> postHttpContentAsync(String url, int timeoutMs, Charset charset, Map<String, String> headers, String body, Map<String, String> respHeaders) {
        return remoteHttpContentAsync("POST", url, timeoutMs, headers, body, respHeaders).thenApply(out -> out.toString(charset));
    }

    public static CompletableFuture<byte[]> postHttpBytesContentAsync(String url) {
        return remoteHttpContentAsync("POST", url, 0, null, null).thenApply(ByteArrayOutputStream::toByteArray);
    }

    public static CompletableFuture<byte[]> postHttpBytesContentAsync(String url, Map<String, String> respHeaders) {
        return remoteHttpContentAsync("POST", url, 0, null, null, respHeaders).thenApply(ByteArrayOutputStream::toByteArray);
    }

    public static CompletableFuture<byte[]> postHttpBytesContentAsync(String url, int timeoutMs) {
        return remoteHttpContentAsync("POST", url, timeoutMs, null, null).thenApply(ByteArrayOutputStream::toByteArray);
    }

    public static CompletableFuture<byte[]> postHttpBytesContentAsync(String url, int timeoutMs, Map<String, String> respHeaders) {
        return remoteHttpContentAsync("POST", url, timeoutMs, null, null, respHeaders).thenApply(ByteArrayOutputStream::toByteArray);
    }

    public static CompletableFuture<byte[]> postHttpBytesContentAsync(String url, Map<String, String> headers, String body) {
        return remoteHttpContentAsync("POST", url, 0, headers, body).thenApply(ByteArrayOutputStream::toByteArray);
    }

    public static CompletableFuture<byte[]> postHttpBytesContentAsync(String url, Map<String, String> headers, String body, Map<String, String> respHeaders) {
        return remoteHttpContentAsync("POST", url, 0, headers, body, respHeaders).thenApply(ByteArrayOutputStream::toByteArray);
    }

    public static CompletableFuture<byte[]> postHttpBytesContentAsync(String url, int timeoutMs, Map<String, String> headers, String body) {
        return remoteHttpContentAsync("POST", url, timeoutMs, headers, body).thenApply(ByteArrayOutputStream::toByteArray);
    }

    public static CompletableFuture<byte[]> postHttpBytesContentAsync(String url, int timeoutMs, Map<String, String> headers, String body, Map<String, String> respHeaders) {
        return remoteHttpContentAsync("POST", url, timeoutMs, headers, body, respHeaders).thenApply(ByteArrayOutputStream::toByteArray);
    }

    public static CompletableFuture<String> getHttpContentAsync(String url) {
        return remoteHttpContentAsync("GET", url, 0, null, null).thenApply(out -> out.toString(StandardCharsets.UTF_8));
    }

    public static CompletableFuture<String> getHttpContentAsync(String url, InetSocketAddress proxy) {
        return remoteHttpContentAsync(httpClient, "GET", url, 0, null, null, null, proxy).thenApply(out -> out.toString(StandardCharsets.UTF_8));
    }

    public static CompletableFuture<String> getHttpContentAsync(String url, Map<String, String> respHeaders) {
        return remoteHttpContentAsync("GET", url, 0, null, null, respHeaders).thenApply(out -> out.toString(StandardCharsets.UTF_8));
    }

    public static CompletableFuture<String> getHttpContentAsync(String url, int timeoutMs) {
        return remoteHttpContentAsync("GET", url, timeoutMs, null, null).thenApply(out -> out.toString(StandardCharsets.UTF_8));
    }

    public static CompletableFuture<String> getHttpContentAsync(String url, int timeoutMs, Map<String, String> respHeaders) {
        return remoteHttpContentAsync("GET", url, timeoutMs, null, null, respHeaders).thenApply(out -> out.toString(StandardCharsets.UTF_8));
    }

    public static CompletableFuture<String> getHttpContentAsync(String url, Map<String, String> headers, String body) {
        return remoteHttpContentAsync("GET", url, 0, headers, body).thenApply(out -> out.toString(StandardCharsets.UTF_8));
    }

    public static CompletableFuture<String> getHttpContentAsync(String url, Map<String, String> headers, String body, Map<String, String> respHeaders) {
        return remoteHttpContentAsync("GET", url, 0, headers, body, respHeaders).thenApply(out -> out.toString(StandardCharsets.UTF_8));
    }

    public static CompletableFuture<String> getHttpContentAsync(String url, int timeoutMs, Map<String, String> headers, String body) {
        return remoteHttpContentAsync("GET", url, timeoutMs, headers, body).thenApply(out -> out.toString(StandardCharsets.UTF_8));
    }

    public static CompletableFuture<String> getHttpContentAsync(String url, int timeoutMs, Map<String, String> headers, String body, Map<String, String> respHeaders) {
        return remoteHttpContentAsync("GET", url, timeoutMs, headers, body, respHeaders).thenApply(out -> out.toString(StandardCharsets.UTF_8));
    }

    public static CompletableFuture<String> getHttpContentAsync(String url, Charset charset) {
        return remoteHttpContentAsync("GET", url, 0, null, null).thenApply(out -> out.toString(charset));
    }

    public static CompletableFuture<String> getHttpContentAsync(String url, Charset charset, Map<String, String> respHeaders) {
        return remoteHttpContentAsync("GET", url, 0, null, null, respHeaders).thenApply(out -> out.toString(charset));
    }

    public static CompletableFuture<String> getHttpContentAsync(String url, int timeoutMs, Charset charset) {
        return remoteHttpContentAsync("GET", url, timeoutMs, null, null).thenApply(out -> out.toString(charset));
    }

    public static CompletableFuture<String> getHttpContentAsync(String url, int timeoutMs, Charset charset, Map<String, String> respHeaders) {
        return remoteHttpContentAsync("GET", url, timeoutMs, null, null, respHeaders).thenApply(out -> out.toString(charset));
    }

    public static CompletableFuture<String> getHttpContentAsync(String url, Charset charset, Map<String, String> headers, String body) {
        return remoteHttpContentAsync("GET", url, 0, headers, body).thenApply(out -> out.toString(charset));
    }

    public static CompletableFuture<String> getHttpContentAsync(String url, Charset charset, Map<String, String> headers, String body, Map<String, String> respHeaders) {
        return remoteHttpContentAsync("GET", url, 0, headers, body, respHeaders).thenApply(out -> out.toString(charset));
    }

    public static CompletableFuture<String> getHttpContentAsync(String url, int timeoutMs, Charset charset, Map<String, String> headers, String body) {
        return remoteHttpContentAsync("GET", url, timeoutMs, headers, body).thenApply(out -> out.toString(charset));
    }

    public static CompletableFuture<String> getHttpContentAsync(String url, int timeoutMs, Charset charset, Map<String, String> headers, String body, Map<String, String> respHeaders) {
        return remoteHttpContentAsync("GET", url, timeoutMs, headers, body, respHeaders).thenApply(out -> out.toString(charset));
    }

    public static CompletableFuture<String> getHttpContentAsync(HttpClient client, String url, String body, Map<String, String> respHeaders) {
        return remoteHttpContentAsync(client, "GET", url, 0, null, body, respHeaders).thenApply(out -> out.toString(StandardCharsets.UTF_8));
    }

    public static CompletableFuture<String> getHttpContentAsync(HttpClient client, String url, Charset charset, String body, Map<String, String> respHeaders) {
        return remoteHttpContentAsync(client, "GET", url, 0, null, body, respHeaders).thenApply(out -> out.toString(charset));
    }

    public static CompletableFuture<String> getHttpContentAsync(HttpClient client, String url, Charset charset, Map<String, String> headers, String body, Map<String, String> respHeaders) {
        return remoteHttpContentAsync(client, "GET", url, 0, headers, body, respHeaders).thenApply(out -> out.toString(charset));
    }

    public static CompletableFuture<byte[]> getHttpBytesContentAsync(String url) {
        return remoteHttpContentAsync("GET", url, 0, null, null).thenApply(ByteArrayOutputStream::toByteArray);
    }

    public static CompletableFuture<byte[]> getHttpBytesContentAsync(String url, Map<String, String> respHeaders) {
        return remoteHttpContentAsync("GET", url, 0, null, null, respHeaders).thenApply(ByteArrayOutputStream::toByteArray);
    }

    public static CompletableFuture<byte[]> getHttpBytesContentAsync(String url, int timeoutMs) {
        return remoteHttpContentAsync("GET", url, timeoutMs, null, null).thenApply(ByteArrayOutputStream::toByteArray);
    }

    public static CompletableFuture<byte[]> getHttpBytesContentAsync(String url, int timeoutMs, Map<String, String> respHeaders) {
        return remoteHttpContentAsync("GET", url, timeoutMs, null, null, respHeaders).thenApply(ByteArrayOutputStream::toByteArray);
    }

    public static CompletableFuture<byte[]> getHttpBytesContentAsync(String url, Map<String, String> headers, String body) {
        return remoteHttpContentAsync("GET", url, 0, headers, body).thenApply(ByteArrayOutputStream::toByteArray);
    }

    public static CompletableFuture<byte[]> getHttpBytesContentAsync(String url, Map<String, String> headers, String body, Map<String, String> respHeaders) {
        return remoteHttpContentAsync("GET", url, 0, headers, body, respHeaders).thenApply(ByteArrayOutputStream::toByteArray);
    }

    public static CompletableFuture<byte[]> getHttpBytesContentAsync(String url, int timeoutMs, Map<String, String> headers, String body) {
        return remoteHttpContentAsync("GET", url, timeoutMs, headers, body).thenApply(ByteArrayOutputStream::toByteArray);
    }

    public static CompletableFuture<byte[]> getHttpBytesContentAsync(String url, int timeoutMs, Map<String, String> headers, String body, Map<String, String> respHeaders) {
        return remoteHttpContentAsync("GET", url, timeoutMs, headers, body, respHeaders).thenApply(ByteArrayOutputStream::toByteArray);
    }

    public static CompletableFuture<byte[]> getHttpBytesContentAsync(HttpClient client, String url, int timeoutMs, Map<String, String> headers, String body, Map<String, String> respHeaders) {
        return remoteHttpContentAsync(client, "GET", url, timeoutMs, headers, body, respHeaders).thenApply(ByteArrayOutputStream::toByteArray);
    }

    public static CompletableFuture<ByteArrayOutputStream> remoteHttpContentAsync(String method, String url, Map<String, String> headers, String body) {
        return remoteHttpContentAsync(method, url, 0, headers, body);
    }

    public static CompletableFuture<ByteArrayOutputStream> remoteHttpContentAsync(String method, String url, Map<String, String> headers, String body, Map<String, String> respHeaders) {
        return remoteHttpContentAsync(method, url, 0, headers, body, respHeaders);
    }

    public static CompletableFuture<ByteArrayOutputStream> remoteHttpContentAsync(String method, String url, int timeoutMs, Map<String, String> headers, String body) {
        return remoteHttpContentAsync(httpClient, method, url, timeoutMs, headers, body);
    }

    public static CompletableFuture<ByteArrayOutputStream> remoteHttpContentAsync(String method, String url, int timeoutMs, Map<String, String> headers, String body, Map<String, String> respHeaders) {
        return remoteHttpContentAsync(httpClient, method, url, timeoutMs, headers, body, respHeaders);
    }

    public static CompletableFuture<ByteArrayOutputStream> remoteHttpContentAsync(HttpClient client, String method, String url, int timeoutMs, Map<String, String> headers, String body) {
        return remoteHttpContentAsync(client, method, url, timeoutMs, headers, body, null);
    }

    public static CompletableFuture<ByteArrayOutputStream> remoteHttpContentAsync(HttpClient client, String method, String url, int timeoutMs, Map<String, String> headers, String body, Map<String, String> respHeaders) {
        return remoteHttpContentAsync(client, method, url, timeoutMs, headers, body, respHeaders, null);
    }

    public static CompletableFuture<ByteArrayOutputStream> remoteHttpContentAsync(HttpClient client, String method, String url, int timeoutMs, Map<String, String> headers, String body, Map<String, String> respHeaders, InetSocketAddress proxy) {
        HttpRequest.Builder builder = HttpRequest.newBuilder()
                .uri(URI.create(url))
                .timeout(Duration.ofMillis(timeoutMs > 0 ? timeoutMs : 6000))
                .method(method, body == null ? HttpRequest.BodyPublishers.noBody() : HttpRequest.BodyPublishers.ofString(body));
        if (headers != null) {
            headers.forEach(builder::header);
        }
        HttpClient c = client == null ? httpClient : client;
        if (c == null) {
            clientLock.lock();
            try {
                if (httpClient == null) {
                    if (proxy == null) {
                        httpClient = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).build();
                    } else {
                        httpClient = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).proxy(ProxySelector.of(proxy)).build();
//                        httpClient = java.net.http.HttpClient.newBuilder().proxy(ProxySelector.of(new InetSocketAddress("127.0.0.1", 10809))).build();
                    }
                }
            } finally {
                clientLock.unlock();
            }
            c = httpClient;
        }
        return c.sendAsync(builder.build(), HttpResponse.BodyHandlers.ofByteArray())
                .thenCompose((HttpResponse<byte[]> resp) -> {
                    final int rs = resp.statusCode();
                    if (rs == 301 || rs == 302) {
                        Optional<String> opt = resp.headers().firstValue("Location");
                        if (opt.isPresent()) {
                            return remoteHttpContentAsync(client, method, opt.get(), timeoutMs, headers, body);
                        } else {
                            return CompletableFuture.failedFuture(new IOException(url + " HttpCode = " + rs + ", but not found Location"));
                        }
                    }
                    byte[] result = resp.body();
                    if (rs == 200 || result != null) {
                        if (respHeaders != null) {
                            resp.headers().map().forEach((k, l) -> {
                                if (!l.isEmpty()) {
                                    respHeaders.put(k, l.get(0));
                                }
                            });
                        }
                        ByteArrayOutputStream out = new ByteArrayOutputStream();
                        if (result != null) {
                            if ("gzip".equalsIgnoreCase(resp.headers().firstValue("content-encoding").orElse(null))) {
                                try {
                                    GZIPInputStream in = new GZIPInputStream(new ByteArrayInputStream(result));
                                    in.transferTo(out);
                                } catch (IOException e) {
                                    return CompletableFuture.failedFuture(e);
                                }
                            } else {
                                out.writeBytes(result);
                            }
                        }
                        return CompletableFuture.completedFuture(out);
                    }
                    return CompletableFuture.failedFuture(new HttpException(rs, url + " HttpCode = " + rs));
                });
    }
}
