package com.trade.common.utils.http;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.nio.charset.Charset;
import java.util.*;

import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.FileEntity;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.HttpMultipartMode;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HTTP;
import org.apache.log4j.Logger;
import org.springframework.util.MultiValueMap;
import org.springframework.web.multipart.MultipartFile;

/**
 * <p>
 * Copyright (c) 2019 - 2029 摸金校尉 All Rights Reserved.
 * <p>
 * Created by wanxiangZ 2019/8/15 10:40
 */
public class HttpParams {

    // 传入参数特定类型
    public static final String ENTITY_STRING = "$ENTITY_STRING$";
    public static final String ENTITY_JSON = "$ENTITY_JSON$";
    public static final String ENTITY_FILE = "$ENTITY_FILEE$";
    public static final String ENTITY_BYTES = "$ENTITY_BYTES$";
    public static final String ENTITY_INPUTSTREAM = "$ENTITY_INPUTSTREAM$";
    public static final String ENTITY_SERIALIZABLE = "$ENTITY_SERIALIZABLE$";
    public static final String ENTITY_MULTIPART = "$ENTITY_MULTIPART$";
    public static final String ENTITY_MULTIPART_FILE = "$ENTITY_MULTIPART_FILE$";
    private static final List<String> SPECIAL_ENTITY = Arrays.asList(ENTITY_STRING, ENTITY_JSON, ENTITY_BYTES,
            ENTITY_FILE, ENTITY_INPUTSTREAM, ENTITY_SERIALIZABLE, ENTITY_MULTIPART, ENTITY_MULTIPART_FILE);
    /**
     * 是否开启debug，
     */
    private static boolean debug = false;
    private static final Logger logger = Logger.getLogger("HttpClient(异步)工具类");

    /**
     * 检测url是否含有参数，如果有，则把参数加到参数列表中
     *
     * @param url 资源地址
     * @param nvps 参数列表
     * @param encoding 编码
     * @return 返回去掉参数的url
     */
    public static String checkHasParams(String url, List<NameValuePair> nvps, String encoding) throws IOException {
        if (url.contains("?") && url.indexOf("?") < url.indexOf("=")) {
            Map<String, Object> map = buildParams(url.substring(url.indexOf("?") + 1));
            map2HttpEntity(nvps, map, encoding);
            url = url.substring(0, url.indexOf("?"));
        }
        return url;
    }

    /**
     * 生成参数 参数格式：k1=v1&amp;k2=v2
     *
     * @param paras 参数列表
     * @return 返回参数列表（map）
     */
    public static Map<String, Object> buildParams(String paras) {
        String[] p = paras.split("&");
        String[][] ps = new String[p.length][2];
        int pos = 0;
        for (int i = 0; i < p.length; i++) {
            pos = p[i].indexOf("=");
            ps[i][0] = p[i].substring(0, pos);
            ps[i][1] = p[i].substring(pos + 1);
            pos = 0;
        }
        return buildParams(ps);
    }

    /**
     * 生成参数 参数类型：{{"k1","v1"},{"k2","v2"}}
     *
     * @param paras 参数列表
     * @return 返回参数列表（map）
     */
    public static Map<String, Object> buildParams(String[][] paras) {
        // 创建参数队列
        Map<String, Object> map = new HashMap<>();
        for (String[] para : paras) {
            map.put(para[0], para[1]);
        }
        return map;
    }

    /**
     * 参数转换，将map中的参数，转到参数列表中
     *
     * @param nvps 参数列表
     * @param map 参数列表（map）
     * @param encoding 编码
     * @return 返回HttpEntity
     * @throws IOException
     */
    public static HttpEntity map2HttpEntity(List<NameValuePair> nvps, Map<String, Object> map, String encoding)
            throws IOException {
        HttpEntity entity = null;
        if (map != null && map.size() > 0) {
            if (map.containsKey(ENTITY_MULTIPART_FILE)) {
                entity = multipartFileToEntity(map, encoding);
                return entity;
            }
            boolean isSpecial = false;
            // 拼接参数
            for (Map.Entry<String, Object> entry : map.entrySet()) {
                // 判断是否在之中
                if (SPECIAL_ENTITY.contains(entry.getKey())) {
                    isSpecial = true;
                    if (ENTITY_STRING.equals(entry.getKey())) {
                        entity = new StringEntity(String.valueOf(entry.getValue()), encoding);
                        break;
                    } else if (ENTITY_JSON.equals(entry.getKey())) {
                        entity = new StringEntity(String.valueOf(entry.getValue()), encoding);
                        String contentType = "application/json";
                        if (encoding != null) {
                            contentType += ";charset=" + encoding;
                        }
                        ((StringEntity) entity).setContentType(contentType);
                        break;
                    } else if (ENTITY_BYTES.equals(entry.getKey())) {// file
                        entity = new ByteArrayEntity((byte[]) entry.getValue());
                        break;
                    } else if (ENTITY_FILE.equals(entry.getKey())) {// file
                        if (File.class.isAssignableFrom(entry.getValue().getClass())) {
                            entity = new FileEntity((File) entry.getValue(), ContentType.APPLICATION_OCTET_STREAM);
                        } else if (entry.getValue().getClass() == String.class) {
                            entity = new FileEntity(new File((String) entry.getValue()),
                                    ContentType.create("text/plain", "UTF-8"));
                        }
                        break;
                    } else if (ENTITY_INPUTSTREAM.equals(entry.getKey())) {
                        // entity = new InputStreamEntity();
                        break;
                    } else if (ENTITY_SERIALIZABLE.equals(entry.getKey())) {
                        // entity = new SerializableEntity()
                        break;
                    } else if (ENTITY_MULTIPART.equals(entry.getKey())) {// MultipartEntityBuilder
                        File[] files = null;
                        if (File.class.isAssignableFrom(entry.getValue().getClass().getComponentType())) {
                            files = (File[]) entry.getValue();
                        } else if (entry.getValue().getClass().getComponentType() == String.class) {
                            String[] names = (String[]) entry.getValue();
                            files = new File[names.length];
                            for (int i = 0; i < names.length; i++) {
                                files[i] = new File(names[i]);
                            }
                        }
                        MultipartEntityBuilder builder = MultipartEntityBuilder.create();
                        // 设置请求的编码格式
                        builder.setCharset(Charset.forName(encoding));
                        // 设置浏览器兼容模式
                        builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
                        // 支持多文件上传，文件name格式为：file input 标签的name值+"count"(count为文件下标0,1,2,3……)
                        // 当只有一个附件的时候，默认name为file或者inputName参数值，不带下标。
                        if (files.length > 1) {
                            int count = 0;
                            for (File file : files) {
                                // 把文件转换成流对象FileBody
                                // FileBody fileBody = new FileBody(file);
                                // builder.addPart(String.valueOf(map.get(ENTITY_MULTIPART+".name")) +
                                // count++,fileBody);
                                builder.addBinaryBody(String.valueOf(map.get(ENTITY_MULTIPART + ".name")) + count++,
                                        file);
                            }
                        } else {
                            builder.addBinaryBody(String.valueOf(map.get(ENTITY_MULTIPART + ".name")), files[0]);
                        }
                        boolean forceRemoveContentTypeCharset = (Boolean) map.get(ENTITY_MULTIPART + ".rmCharset");
                        Map<String, Object> m = new HashMap<>();
                        m.putAll(map);
                        m.remove(ENTITY_MULTIPART);
                        m.remove(ENTITY_MULTIPART + ".name");
                        m.remove(ENTITY_MULTIPART + ".rmCharset");
                        Iterator<Map.Entry<String, Object>> iterator = m.entrySet().iterator();
                        // 发送的数据
                        while (iterator.hasNext()) {
                            Map.Entry<String, Object> e = iterator.next();
                            // builder.addPart("kyc",new StringBody("kyc",ContentType.APPLICATION_JSON));
                            builder.addTextBody(e.getKey(), String.valueOf(e.getValue()),
                                    ContentType.create("text/plain", encoding));
                        }
                        // 生成 HTTP POST 实体
                        entity = builder.build();
                        // 强制去除contentType中的编码设置，否则，在某些情况下会导致上传失败
                        if (forceRemoveContentTypeCharset) {
                            removeContentTypeCharset(encoding, entity);
                        }
                        break;
                    } else {
                        nvps.add(new BasicNameValuePair(entry.getKey(), String.valueOf(entry.getValue())));
                    }
                } else {
                    nvps.add(new BasicNameValuePair(entry.getKey(), String.valueOf(entry.getValue())));
                }
            }
            if (!isSpecial) {
                entity = new UrlEncodedFormEntity(nvps, encoding);
            }
        }
        return entity;
    }

    private static HttpEntity multipartFileToEntity(Map<String, Object> map, String encoding) throws IOException {
        MultiValueMap<String, MultipartFile> fileMap = (MultiValueMap<String, MultipartFile>) map
                .get(ENTITY_MULTIPART_FILE);
        MultipartEntityBuilder builder = MultipartEntityBuilder.create();
        // 设置请求的编码格式
        builder.setCharset(Charset.forName(encoding));
        // 设置浏览器兼容模式
        builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
        for (Map.Entry<String, List<MultipartFile>> itemEntry : fileMap.entrySet()) {
            for (MultipartFile multipartFile : itemEntry.getValue()) {
                builder.addBinaryBody(itemEntry.getKey(), multipartFile.getInputStream(),
                        ContentType.MULTIPART_FORM_DATA, multipartFile.getOriginalFilename());
            }
        }
        Map<String, Object> m = new HashMap<>(map);
        m.remove(ENTITY_MULTIPART_FILE);
        // 发送的数据
        for (Map.Entry<String, Object> e : m.entrySet()) {
            builder.addTextBody(e.getKey(), String.valueOf(e.getValue()), ContentType.create("text/plain", encoding));
        }
        // 生成 HTTP POST 实体
        return builder.build();
    }

    /**
     * 移除content-type中的charset
     *
     * @param encoding 编码
     * @param entity 请求参数及数据信息
     */
    private static void removeContentTypeCharset(String encoding, HttpEntity entity) {
        try {
            Class<?> clazz = entity.getClass();
            Field field = clazz.getDeclaredField("contentType");
            // 将字段的访问权限设为true：即去除private修饰符的影响
            field.setAccessible(true);
            if (Modifier.isFinal(field.getModifiers())) {
                // 去除final修饰符的影响，将字段设为可修改的
                Field modifiersField = Field.class.getDeclaredField("modifiers");
                modifiersField.setAccessible(true);
                modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
            }
            BasicHeader o = (BasicHeader) field.get(entity);
            field.set(entity, new BasicHeader(HTTP.CONTENT_TYPE, o.getValue().replace("; charset=" + encoding, "")));
        } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
            exception(e);
        }
    }

    /**
     * 打印消息
     *
     * @param msg 消息
     */
    public static void info(String msg) {
        if (debug) {
            logger.info(msg);
        }
    }

    /**
     * 打印消息和异常堆栈
     *
     * @param msg 异常消息
     * @param t 异常
     */
    public static void infoException(String msg, Throwable t) {
        if (debug) {
            logger.info(msg, t);
        }
    }

    /**
     * 打印错误消息
     *
     * @param msg 异常消息
     */
    public static void error(String msg) {
        logger.error(msg);
    }

    /**
     * 打印错误消息和异常堆栈
     *
     * @param msg 异常消息
     * @param t 异常
     */
    public static void errorException(String msg, Throwable t) {
        logger.error(msg, t);
    }

    /**
     * 打印异常堆栈
     *
     * @param t 异常
     */
    public static void exception(Throwable t) {
        logger.error(t);
    }

    /**
     * 开启打印日志
     */
    public static void debug() {
        debug(true);
    }

    /**
     * 开启或关闭打印日志
     *
     * @param debug 是否开启debug
     */
    public static void debug(boolean debug) {
        HttpParams.debug = debug;
    }
}
