package com.library.TopUp.Http;

import com.google.gson.Gson;
import org.apache.commons.lang3.StringUtils;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSession;
import javax.servlet.http.HttpServletResponse;



/**
 * https post发送工具  绕过ssl
 * 
 * @author 超神之巅
 *
 */
public class HttpsTool {
    private static final String LINE_SEPARATOR = System.getProperty("line.separator");
    
    static HostnameVerifier hv = new HostnameVerifier() {
        public boolean verify(String urlHostName, SSLSession session) {
            System.out.println("Warning: URL Host: " + urlHostName + " vs. "
                    + session.getPeerHost());
            return true;
        }
    };
    
    private static void trustAllHttpsCertificates() throws Exception {
        javax.net.ssl.TrustManager[] trustAllCerts = new javax.net.ssl.TrustManager[1];
        javax.net.ssl.TrustManager tm = new miTM();
        trustAllCerts[0] = tm;
        javax.net.ssl.SSLContext sc = javax.net.ssl.SSLContext
                .getInstance("SSL");
        sc.init(null, trustAllCerts, null);
        javax.net.ssl.HttpsURLConnection.setDefaultSSLSocketFactory(sc
                .getSocketFactory());
    }

    static class miTM implements javax.net.ssl.TrustManager,
            javax.net.ssl.X509TrustManager {
        public java.security.cert.X509Certificate[] getAcceptedIssuers() {
            return null;
        }

        public boolean isServerTrusted(
                java.security.cert.X509Certificate[] certs) {
            return true;
        }

        public boolean isClientTrusted(
                java.security.cert.X509Certificate[] certs) {
            return true;
        }

        public void checkServerTrusted(
                java.security.cert.X509Certificate[] certs, String authType)
                throws java.security.cert.CertificateException {
            return;
        }

        public void checkClientTrusted(
                java.security.cert.X509Certificate[] certs, String authType)
                throws java.security.cert.CertificateException {
            return;
        }
    }
    
    /**
     * 发送报文
     * 
     * 通信失败时返回分两种:一种是抛异常,一种是返回"",本方法取前者
     * 
     * @param requestData
     *            请求报文
     * @param headMap
     *            head请求头参数 conn.setRequestProperty(key,value)
     * @param urlStr
     *            请求地址
     * @param sendEncoding
     *            请求编码
     * @param recvEncoding
     *            返回编码
     * @param connectionTimeout
     *            链接超时时间 1000代表 1秒
     * @param readTimeout
     *            读取超时时间 1000代表1秒
     * @return
     * @throws IOException
     * @author 超神之巅
     */
    public static String send(String requestData,Map<String,String> headMap, String urlStr, String sendEncoding, String recvEncoding,
                              int connectionTimeout, int readTimeout,String contentType) throws Exception {
        URL url = null;
        HttpsURLConnection conn = null;
        ByteArrayOutputStream byteOut = null;
        BufferedReader readInfo = null;
        StringBuilder retBuilder = new StringBuilder();// 这里用不着多线程安全的StringBuffer
        OutputStream out = null;
        String line = null;
        StringBuilder reqDetailProcedure = new StringBuilder();// 这里用不着多线程安全的StringBuffer
        Date startTime = new Date();
        reqDetailProcedure.append("请求时间:【" + DATE_FORMATER.format(startTime) + "】").append("\r\n");
        reqDetailProcedure.append("请求地址:【" + urlStr + "】").append("\r\n");
        reqDetailProcedure.append("真实请求方式及编码:【post " + sendEncoding + "】").append("\r\n");
        reqDetailProcedure.append("期望返回编码:【" + recvEncoding + "】").append("\r\n");
        reqDetailProcedure.append("请求超时时间:【" + readTimeout / 1000 + "s】").append("\r\n");
        reqDetailProcedure.append("请求报文:【" + requestData + "】").append("\r\n");
        try {
//            如有必要,给?后的参数加encode(xxx,"UTF-8"),不然目标系统可能收到的request.getParameter()是乱码
//            String arg = java.net.URLEncoder.encode("中国","UTF-8");
//            url = new URL(urlStr+"?deptname="+arg);
            
            url = new URL(urlStr);
            
            trustAllHttpsCertificates();
            HttpsURLConnection.setDefaultHostnameVerifier(hv);
            
            conn = (HttpsURLConnection) url.openConnection();
            conn.setRequestMethod("POST");
            // 新增部分
            //conn.setHostnameVerifier(new TrustAnyHostnameVerifier());
            conn.setRequestProperty("SOAPAction", "\"\"");
            conn.setRequestProperty("Accept", "application/xml, application/json,application/dime, multipart/related, text/*");

            // 如果没有下面这一行代码,服务器端可以通过request.getParameter()和request.getInputStream()都接收到相同信息

            // Content-Type相关知识链接: http://www.cnblogs.com/whatlonelytear/p/6187575.html
            // conn如果不设Content-Type,则默认成application/x-www-form-urlencoded
            // 当然也可以配置成application/x-www-form-urlencoded;charset=UTF-8,此时要求服务端返回UTF-8编码,如果本地还用gbk去解码,有可能得到乱码
            conn.setRequestProperty("Content-Type", contentType);
            // 如果把Content-Type的类型改变成非application/x-www-form-urlencoded,如改成text/xml,
            // 则目标服务器端仅能通过request.getInputStream()接收信息, 如conn.setRequestProperty("Content-Type", "text/xml;charset=GBK");
            // Content-Type各类型解释: http://blog.csdn.net/blueheart20/article/details/45174399
            
            // conn.setRequestProperty("Accept-Charset", "utf-8");//未知
            
            //Cache-Control说明链接:http://huangyunbin.iteye.com/blog/1943310  或 http://www.cnblogs.com/whatlonelytear/articles/6385390.html
            conn.setRequestProperty("Cache-Control", "no-cache");
            
            //设置head头,如果已存在,则覆盖之前的head配置
            if(headMap != null){
                for(Map.Entry<String, String > entry : headMap.entrySet()){
                    conn.setRequestProperty(entry.getKey(),entry.getValue());
                }
                
            }
            
            // conn.setRequestProperty("appName", appName);//各系统需要设置应用系统名,如电销为telesales(无意义,本人自定义)
            conn.setUseCaches(false); // 忽略缓存
            // get请求用不到conn.getOutputStream(),因为参数直接追加在地址后面,因此默认是false.
            // post请求(比如:文件上传)需要往服务区传输大量的数据,这些数据是放在http的body里面的,因此需要在建立连接以后,往服务端写数据.
            // 因为总是使用conn.getInputStream()获取服务端的响应,因此默认值是true.
            conn.setDoOutput(true); // 使用 URL
                                    // 连接进行输出,允许使用conn.getOutputStream().write()
            conn.setDoInput(true); // 使用 URL
                                    // 连接进行输入,允许使用conn.getInputStream().read();
            conn.setConnectTimeout(connectionTimeout);// 链接超时
            conn.setReadTimeout(readTimeout);// 读取超时
            conn.connect();// 建立链接
            byteOut = new ByteArrayOutputStream();
            byteOut.write(requestData.getBytes(sendEncoding));// 以指定编码发送,如果有乱码,修改之
            byte[] buf = byteOut.toByteArray();
            out = conn.getOutputStream();
            out.write(buf);
            out.flush();
            reqDetailProcedure.append("响应码和状态：【" + conn.getResponseCode() + "/" + conn.getResponseMessage() + "】").append("\r\n");
            if (HttpURLConnection.HTTP_OK == conn.getResponseCode()) {// 正确返回
                InputStream tempStream = conn.getInputStream();
                readInfo = new BufferedReader(new java.io.InputStreamReader(tempStream, recvEncoding));// 以指定编码读取返回信息,如果有乱码,修改之
                while ((line = readInfo.readLine()) != null) {
                    retBuilder.append(line).append(LINE_SEPARATOR);
                }
            } else {// 没有正确返回
                // 这个else分支有冗余代码,一来代码不多,二来这个分支是经过长时间思考,还是保留了下来,方便我以后对错误做不抛异常处理等
                InputStream tempStream = conn.getInputStream();// 如果响应码不是200,一般在这里就开始报异常,不会走到下面几行中去的.
                readInfo = new BufferedReader(new java.io.InputStreamReader(tempStream, recvEncoding));// cannotReach
                while ((line = readInfo.readLine()) != null) {// cannotReach
                    retBuilder.append(line);// cannotReach
                }// cannotReach
                reqDetailProcedure.append("@@返回异常报文:【" + retBuilder + "】").append("\r\n");// cannotReach
            }
        } catch (IOException e) {
            e.printStackTrace();
            throw e;
        } finally {
            // 目标服务端用response.setContentType("text/html;charset=UTF-8");然后源调用方可以用conn.getContentType()获取到[猜测,暂未测试]
            // conn.getContentType()包含在Head返回的内容中.
            // 返回如果带编码,可以做成动态编码去把流转成字符串,暂不优化.这是极好的l优化点.
            reqDetailProcedure.append("返回内容类型及编码:【" + conn.getContentType() + "】").append("\r\n");
            reqDetailProcedure.append("返回HEAD头信息:【" + conn.getHeaderFields() + "】").append("\r\n");
            reqDetailProcedure.append("返回报文:【" + retBuilder.toString() + "】").append("\r\n");
            Date endTime = new Date();
            reqDetailProcedure.append("返回时间:【" + DATE_FORMATER.format(endTime) + "】").append("\r\n");
            long diffMilliSecond = endTime.getTime() - startTime.getTime();
            long diffSecond = (diffMilliSecond / 1000);
            reqDetailProcedure.append("耗时:【" + diffMilliSecond + "】毫秒,大约" + "【" + diffSecond + "】秒").append("\r\n");
            System.out.println(reqDetailProcedure.toString());
            //ThreadLocalListContainer.put(reqDetailProcedure.toString());
            // ThreadLocalMapContainer.put(KingConstants.REQ_DETAIL_PROCEDURE,reqDetailProcedure.toString())
            try {
                if (readInfo != null) {
                    readInfo.close();
                }
                if (byteOut != null) {
                    byteOut.close();
                }
                if (out != null) {
                    out.close();
                }
                if (conn != null) {
                    conn.disconnect();
                }
            } catch (Exception e) {
                System.out.println("关闭链接出错!" + e.getMessage());
            }

        }
        return retBuilder.toString();
    }
    
    /**
     * 发送报文
     * 
     * 通信失败时返回分两种:一种是抛异常,一种是返回"",本方法取前者
     * 
     * @param requestData
     *            请求报文
     * @param urlStr
     *            请求地址
     * @param sendEncoding
     *            请求编码
     * @param recvEncoding
     *            返回编码
     * @param connectionTimeout
     *            链接超时时间 1000代表 1秒
     * @param readTimeout
     *            读取超时时间 1000代表1秒
     * @return
     * @throws IOException
     * @author 超神之巅
     */
    public static String send(String requestData, String urlStr, String sendEncoding, String recvEncoding, int connectionTimeout, int readTimeout) throws Exception {
        boolean flag = isJson(requestData);//是否JSON
        String contentType="application/soap+xml";
        if(flag) {
            contentType = "application/json";
        }
        return send( requestData,null, urlStr, sendEncoding, recvEncoding, connectionTimeout, readTimeout,contentType);
    }

    /**
     * 
     * @param filePath
     *            文件绝对路径
     * @param encoding
     *            读取文件的编码
     * @return
     * @author 超神之巅
     * @throws Exception
     */
    public static String readStringFromFile(String filePath, String encoding) {
        File file = new File(filePath);
        // System.out.println("文件 "+filePath+"存在与否?: "+ file.exists());
        String tempLine = null;
        String retStr = "";
        InputStreamReader isr = null;// way1:
        // FileReader fr = null;//way2
        StringBuilder sb = new StringBuilder();
        try {
            if (file.exists()) {
                isr = new InputStreamReader(new FileInputStream(file), encoding);// way1:
                // fr = new FileReader(file);//way2
                BufferedReader br = new BufferedReader(isr);// way1:
                // BufferedReader br = new BufferedReader(fr);;//way2:
                tempLine = br.readLine();
                while (tempLine != null) {
                    sb.append(tempLine).append(System.getProperty("line.separator"));
                    tempLine = br.readLine();
                }
                retStr = sb.toString();
            }
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (isr != null)
                    isr.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        // System.out.println("读到的文件内容如下:");
        // System.out.println(retStr);
        return retStr;
    }
    /**
     * 判断字符串是否为JSON格式
     * @param str
     * @return
     */
    public static boolean isJson(String str){
        if (StringUtils.isBlank(str)) {
            return false;
        }
        
        boolean flag1 = (str.startsWith("{") && str.endsWith("}"));
        boolean flag2 = (str.startsWith("[") && str.endsWith("]"));
        
        return (flag1 || flag2);
    }

    /*public static final void writeInfo(HttpServletResponse resp, String errorCode, String respInfo, Map<String, Object> map) {
        map.put("errorCode", errorCode);
        map.put("respInfo", respInfo);
        String detail = ThreadLocalListContainer.getAll();
        map.put(KingConstants.DETAIL_PROCEDURE, detail);
        ThreadLocalListContainer.clearAll();
        // map.put(KingConstants.DETAIL_PROCEDURE, detail);
        try {
//            String gsonInfo = new Gson().toJson(map);//old
            Gson gson = new GsonBuilder().setPrettyPrinting().create();//new
            String gsonInfo = gson.toJson(map);
            PrintWriter pw = resp.getWriter();
            pw.print(gsonInfo);
            pw.flush();
            pw.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }*/

    /**
     * 
     * @param resp
     * @param errorCode
     * @param respInfo
     * @param map
     * ......
     * @time 2017年4月23日 上午9:12:43
     * @author 超神之巅
     */
    /*public static final void writeInfo(HttpServletResponse resp, KingResp result) {
        String detail = ThreadLocalListContainer.getAll();
        result.setDetailProcedure(detail);
        ThreadLocalListContainer.clearAll();
        try {
//            String gsonInfo = new Gson().toJson(map);//old
            Gson gson = new GsonBuilder().setPrettyPrinting().create();//new
            String gsonInfo = gson.toJson(result);
            FileTool.writeStringToFile("d:/temp/myinfo.txt", gsonInfo, "gbk", false);
            PrintWriter pw = resp.getWriter();
            pw.print(gsonInfo);
            pw.flush();
            pw.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }*/
    
    public static final void writeInfo(HttpServletResponse resp, String respInfo) {
        try {
            PrintWriter pw = resp.getWriter();
            pw.print(respInfo);
            pw.flush();
            pw.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    public static final SimpleDateFormat DATE_FORMATER = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:sss");

    public static void main(String[] args) throws Exception {

        Map<String,String> heardMap=new HashMap<>();
        heardMap.put("Authorization","epa 2478e27af578e11a6af86a8320562124240f5c972e8039cd9c564e53d643bb31");
        heardMap.put("e-language","English");
        heardMap.put("Content-Type","application/json");
        heardMap.put("Accept","application/json");
//
//
        String requestUrl  = "https://36.37.242.116:8301/telco/metfone-bill/info";
        Map params=new HashMap();

//        params.put("serviceType", "TOPUP");
//        params.put("transAmount", "1");
//        params.put("currency", "USD");
//        params.put("refId", "1121111");
//        params.put("customerPhoneNumber", "855883970777");

        params.put("paymentCode", "0979530750");

        String sencXml = HttpsTool.send(new Gson().toJson(params),heardMap, requestUrl, "utf-8",
                "utf-8", 300 * 1000, 300 * 1000,"application/json");// 大家最终只要使用这一句代码就可调用

        System.out.println(sencXml);


//        String retStr="bh1bBzuyQofPoxj8VfZrxWeD2lkULIXI4qvpBndZciAdS/mdT4EAln1AtvNoR5p73B/mxM3CzZCQHwpiEIPW+yM5xxweoI96cvwgnOUn9uixOhf99f/9pB4gbpHQmyqlwCV3qvjLMyeBWXGouJBtYWcFdKKl1PB+FDgynTRfFvcBph0tyWr0y4Rgm64OBaKQlD8O5gId1HyqQPLHofgKevU6R7+P8ZcB9pIXzWF7nPgRRC0n0aqUk+RpmKn798kjxeIv/UYH/uPrBw/Ux5ZVPOsL7RlJpDJ5rGlwHhQKWK+SnjITrp87D1nYDBsGP6hyuBwEu9OUe7ldutvKQCG3/A==";
//
//        Map params=new HashMap();
//
//        params.put("txPaymentTokenId", retStr);
//        String conURL="https://36.37.242.116:8301/ePayTest/telco/confirm";
//        String sencXml = HttpsTool.send(new Gson().toJson(params), heardMap, conURL, "utf-8",
//                "utf-8", 300 * 1000, 300 * 1000, "application/json");// 大家最终只要使用这一句代码就可调用
//
//        System.out.println(sencXml);

    }
}