package com.sonic.common.rpc;

import java.util.Collections;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import org.apache.commons.lang3.StringUtils;

import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Maps;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import okhttp3.FormBody;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.RequestBody;

/**
 * @author code
 */
@NoArgsConstructor
@AllArgsConstructor
public class RequestParams {

    private static final String AUTHORIZATION_HEADER_TOKEN_PREFIX = "Bearer ";
    private static final String AUTHORIZATION_HEADER_NAME = "Authorization";

    @Getter
    private Map<String, String> headers;

    @Getter
    private ContentType contentType;

    @Getter
    private boolean logEnabled;

    @Getter
    private Object data;

    @Getter
    private Long readTimeoutMillis;

    @Getter
    @AllArgsConstructor
    public enum ContentType {
        /** content-type */
        JSON(MediaType.parse("application/json")),
        FORM(MediaType.parse("application/x-www-form-urlencoded")),
        FILE_FORM(MediaType.parse("application/octet-stream"));

        private MediaType mediaType;
    }

    @Override
    public String toString() {
        Object data = contentType == ContentType.FORM ? ((FormParams) this).getParams(entry -> !(entry.getValue() instanceof byte[])) : this.getData();
        Map<String, String> shortHeaders = Maps.transformEntries(Optional.ofNullable(headers).orElse(Collections.emptyMap()), (k, v) -> {
            String value = v;
            // Authorization 太长不方便日志, 截断.
            if (k.equals("Authorization")) {
                value = StringUtils.abbreviate(v, 43);
            }
            return value;
        });
        return "data = " + JSONObject.toJSONString(data) + ", "
                + "headers = " + JSONObject.toJSONString(shortHeaders) + ", "
                + "contentType =" + contentType;
    }

    public RequestBody toRequestBody() {
        if (contentType == ContentType.JSON) {
            return RequestBody.create(ContentType.JSON.getMediaType(), JSONObject.toJSONString(data));
        }

        RequestParams.FormParams formParams = ((RequestParams.FormParams) this);
        if (contentType == ContentType.FORM) {
            FormBody.Builder formBody = new FormBody.Builder();
            formParams.getParams().forEach((key, value) -> formBody.add(key, String.valueOf(value)));
            return formBody.build();
        }

        Map<String, ?> commonParams = formParams.getParams(entry -> !(entry.getValue() instanceof byte[]));
        MultipartBody.Builder multiBodyBuilder = new MultipartBody.Builder().setType(MultipartBody.FORM);
        // 处理普通参数
        commonParams.entrySet().forEach(entry -> multiBodyBuilder.addFormDataPart(entry.getKey(), String.valueOf(entry.getValue())));
        // 处理byte[]参数
        formParams.getParams().entrySet().stream().
                filter(entry -> !commonParams.containsKey(entry.getKey())).
                forEach(entry -> multiBodyBuilder.addFormDataPart(entry.getKey(), "ignore", RequestBody.create(ContentType.FILE_FORM.getMediaType(), (byte[]) entry.getValue())));
        return multiBodyBuilder.build();
    }

    public void addAuthorization(String token) {
        addHeader(AUTHORIZATION_HEADER_NAME, AUTHORIZATION_HEADER_TOKEN_PREFIX + token);
    }

    public void addHeader(String name, String value) {
        if (headers == null) {
            headers = Maps.newHashMap();
        }
        headers.put(name, value);
    }

    public void addHeaderIfAbsent(String name, String value) {
        if (headers == null) {
            headers = Maps.newHashMap();
        }
        headers.putIfAbsent(name, value);
    }

    @EqualsAndHashCode(callSuper = true)
    @Data
    public static class FormParams extends RequestParams {
        private Map<String, ?> params;

        @Builder
        public FormParams(Map<String, String> headers, boolean logEnable, Map<String, ?> params, Long readTimeoutMillis) {
            super(headers, hashFileByte(params) ? ContentType.FILE_FORM : ContentType.FORM, logEnable, params, readTimeoutMillis);
            this.params = params;
        }

        public Map<String, ?> getParams() {
            return getParams(entry -> true);
        }

        public Map<String, ?> getParams(Predicate<Map.Entry> predicate) {
            Map<String, ?> map = Optional.ofNullable(params).orElse(Collections.EMPTY_MAP);
            return map.entrySet()
                    .stream()
                    .filter(p -> Objects.nonNull(p.getValue()))
                    .filter(predicate)
                    .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
        }

        private static boolean hashFileByte(Map<String, ?> params) {
            Map<String, ?> map = Optional.ofNullable(params).orElse(Collections.EMPTY_MAP);
            return map.entrySet().stream()
                    .anyMatch(entry -> entry.getValue() instanceof byte[]);
        }

        @Override
        public String toString() {
            return super.toString();
        }
    }

    @EqualsAndHashCode(callSuper = true)
    @Data
    public static class BodyParams extends RequestParams {
        @Builder
        public BodyParams(Map<String, String> headers, boolean logEnabled, Object content, Long readTimeoutMillis) {
            super(headers, ContentType.JSON, logEnabled, content, readTimeoutMillis);
        }

        @Override
        public String toString() {
            return super.toString();
        }
    }
}
