package com.sonic.common.rpc;

import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.BiFunction;
import java.util.stream.Collectors;

import org.springframework.util.CollectionUtils;

import com.google.common.base.Function;
import com.google.common.net.UrlEscapers;

import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.Getter;
import lombok.NoArgsConstructor;
import okhttp3.Request;

/**
 * Http请求客户端. 支持常用的method, 自定义header和自定义处理器.
 * @author code
 */
public interface HttpClient {

    String execute(HttpMethod httpMethod, String url, RequestParams requestParams);

    <R> R execute(HttpMethod httpMethod, String url, RequestParams requestParams, BiFunction<byte[], Map<String, List<String>>, R> responder);

    default String post(String url, RequestParams requestParams) {
        return execute(HttpMethod.POST, url, requestParams);
    }

    default String get(String url, RequestParams requestParams) {
        return execute(HttpMethod.GET, url, requestParams);
    }

    default String put(String url, RequestParams requestParams) {
        return execute(HttpMethod.PUT, url, requestParams);
    }

    default String delete(String url, RequestParams requestParams) {
        return execute(HttpMethod.DELETE, url, requestParams);
    }

    default <R> R post(String url, RequestParams requestParams, BiFunction<byte[], Map<String, List<String>>, R> responder) {
        return execute(HttpMethod.POST, url, requestParams, responder);
    }

    default <R> R get(String url, RequestParams requestParams, BiFunction<byte[], Map<String, List<String>>, R> responder) {
        return execute(HttpMethod.GET, url, requestParams, responder);
    }

    default <R> R put(String url, RequestParams requestParams, BiFunction<byte[], Map<String, List<String>>, R> responder) {
        return execute(HttpMethod.PUT, url, requestParams, responder);
    }

    default <R> R delete(String url, RequestParams requestParams, BiFunction<byte[], Map<String, List<String>>, R> responder) {
        return execute(HttpMethod.DELETE, url, requestParams, responder);
    }

    @Getter
    @AllArgsConstructor
    enum HttpMethod {
        GET((s, requestParams) -> {
            return buildUrl(s, requestParams);
        }, (builder, requestParams) -> {
            return builder.get();
        }),
        POST((s, requestParams) -> s, (builder, requestParams) -> {
            return builder.post(requestParams.toRequestBody());
        }),
        PUT((s, requestParams) -> s, (builder, requestParams) -> {
            return builder.put(requestParams.toRequestBody());
        }),
        DELETE((s, requestParams) -> {
            return buildUrl(s, requestParams);
        }, (builder, requestParams) -> {
            return builder.delete();
        });

        BiFunction<String, RequestParams, String> urlBuilder;

        BiFunction<Request.Builder, RequestParams, Request.Builder> requestBuilder;

        private static String buildUrl(String url, RequestParams requestParams) {
            if (requestParams.getContentType() == RequestParams.ContentType.JSON) {
                return url;
            }

            Map<String, ?> commonParams = ((RequestParams.FormParams) requestParams)
                    .getParams(entry -> !(entry.getValue() instanceof byte[]));
            if (CollectionUtils.isEmpty(commonParams)) {
                return url;
            }

            Function<String, String> escape = UrlEscapers.urlPathSegmentEscaper().asFunction();
            String queryString = commonParams.entrySet()
                    .stream()
                    .map(p -> escape.apply(p.getKey() + "=" + p.getValue()))
                    .collect(Collectors.joining("&"));
            return url.contains("?")
                    ? new StringBuilder().append(url).append("&").append(queryString).toString()
                    : new StringBuilder().append(url).append("?").append(queryString).toString();
        }
    }

    @Data
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    class Config {
        public final static Config EMPTY = Config.builder().build();

        private static final Long DEFAULT_CONNECT_TIMEOUT = 60_000L;
        private static final Long DEFAULT_READ_TIMEOUT = 10_000L;
        private static final Long DEFAULT_WRITE_TIMEOUT = 10_000L;

        private static final Integer DEFAULT_MAX_IDLE_CONNECTIONS = 100;
        private static final Integer DEFAULT_KEEP_ALIVE_MINUTES = 1;
        private static final Integer DEFAULT_ASYNC_MAX_REQUESTS = 100;
        private static final Integer DEFAULT_ASYNC_MAX_REQUESTS_PRE_HOST = 20;

        @Getter(AccessLevel.NONE)
        private Long connectTimeOut;

        @Getter(AccessLevel.NONE)
        private Long writeTimeOut;

        @Getter(AccessLevel.NONE)
        private Long readTimeOut;

        // 线程池最大空闲连接数 默认100
        @Getter(AccessLevel.NONE)
        private Integer maxIdleConnections;

        //空闲保持时间:分钟 默认 5分钟
        @Getter(AccessLevel.NONE)
        private Integer keepAliveMinutes;

        //异步操作最大QPS
        @Getter(AccessLevel.NONE)
        private Integer asyncMaxRequests;

        //异步操作同一个目的Host:Port的最大并行数
        @Getter(AccessLevel.NONE)
        private Integer asyncMaxRequestsPerHost;

        public Long getConnectTimeOut() {
            return Objects.isNull(connectTimeOut) ? DEFAULT_CONNECT_TIMEOUT : connectTimeOut;
        }

        public Long getWriteTimeOut() {
            return Objects.isNull(writeTimeOut) ? DEFAULT_WRITE_TIMEOUT : writeTimeOut;
        }

        public Long getReadTimeOut() {
            return Objects.isNull(readTimeOut) ? DEFAULT_READ_TIMEOUT : readTimeOut;
        }

        public int getMaxIdleConnections() {
            return Objects.isNull(maxIdleConnections) ? DEFAULT_MAX_IDLE_CONNECTIONS : maxIdleConnections;
        }

        public int getKeepAliveMinutes() {
            return Objects.isNull(keepAliveMinutes) ? DEFAULT_KEEP_ALIVE_MINUTES : keepAliveMinutes;
        }

        public int getAsyncMaxRequests() {
            return Objects.isNull(asyncMaxRequests) ? DEFAULT_ASYNC_MAX_REQUESTS : asyncMaxRequests;
        }

        public int getAsyncMaxRequestsPerHost() {
            return Objects.isNull(asyncMaxRequestsPerHost) ? DEFAULT_ASYNC_MAX_REQUESTS_PRE_HOST : asyncMaxRequestsPerHost;
        }
    }
}
