package com.alipay.api;

import com.alipay.api.internal.parser.json.ObjectJsonParser;
import com.alipay.api.internal.util.AlipayHashMap;
import com.alipay.api.internal.util.AlipayLogger;
import com.alipay.api.internal.util.AlipaySignature;
import com.alipay.api.internal.util.RequestParametersHolder;
import com.alipay.api.internal.util.StreamUtil;
import com.alipay.api.internal.util.StringUtils;
import com.alipay.api.internal.util.json.JSONWriter;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.StringWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.security.SecureRandom;
import java.security.Security;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TimeZone;
import java.util.TreeMap;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

/**
 * 多媒体文件客户端
 *
 * @author yikai.hu
 * @version $Id: AlipayMobilePublicMultiMediaClient.java, v 0.1 Aug 15, 2014 10:19:01 AM yikai.hu Exp $
 */
public class AlipayMobilePublicMultiMediaClient implements AlipayClient {

    private static final String DEFAULT_CHARSET = AlipayConstants.CHARSET_UTF8;
    private static final String METHOD_POST = "POST";
    private static final String METHOD_GET = "GET";

    private String serverUrl;
    private String appId;
    private String privateKey;
    private String prodCode;
    private String format = AlipayConstants.FORMAT_JSON;
    private String sign_type = AlipayConstants.SIGN_TYPE_RSA;

    private String charset;

    private int connectTimeout = 3000;
    private int readTimeout = 15000;

    private static class DefaultTrustManager implements X509TrustManager {
        public X509Certificate[] getAcceptedIssuers() {
            return null;
        }

        public void checkClientTrusted(X509Certificate[] chain,
                                       String authType) throws CertificateException {
        }

        public void checkServerTrusted(X509Certificate[] chain,
                                       String authType) throws CertificateException {
        }
    }

    static {
        Security.setProperty("jdk.certpath.disabledAlgorithms", "");
    }

    public AlipayMobilePublicMultiMediaClient(String serverUrl, String appId, String privateKey) {
        this.serverUrl = serverUrl;
        this.appId = appId;
        this.privateKey = privateKey;
    }

    public AlipayMobilePublicMultiMediaClient(String serverUrl, String appId, String privateKey,
                                              String format) {
        this(serverUrl, appId, privateKey);
        this.format = format;
    }

    public AlipayMobilePublicMultiMediaClient(String serverUrl, String appId, String privateKey,
                                              String format, String charset) {
        this(serverUrl, appId, privateKey);
        this.format = format;
        this.charset = charset;
    }

    public AlipayMobilePublicMultiMediaClient(String serverUrl, String appId, String privateKey,
                                              String format, String charset, String signType) {
        this(serverUrl, appId, privateKey, format, charset);

        this.sign_type = signType;
    }

    /**
     * @see AlipayClient#execute(AlipayRequest)
     */
    public <T extends AlipayResponse> T execute(AlipayRequest<T> request) throws AlipayApiException {
        return execute(request, null);
    }

    /**
     * @see AlipayClient#execute(AlipayRequest, String)
     */
    public <T extends AlipayResponse> T execute(AlipayRequest<T> request,
                                                String accessToken) throws AlipayApiException {

        return execute(request, accessToken, null);
    }

    /**
     * @see AlipayClient#execute(AlipayRequest, String, String, String)
     */
    public <T extends AlipayResponse> T execute(AlipayRequest<T> request, String accessToken,
                                                String appAuthToken) throws AlipayApiException {

        return _execute(request, accessToken, appAuthToken);
    }

    private <T extends AlipayResponse> T _execute(AlipayRequest<T> request, String authToken,
                                                  String appAuthToken) throws AlipayApiException {

        return doGet(request, appAuthToken);
    }

    public <T extends AlipayResponse> T doGet(AlipayRequest<T> request,
                                              String appAuthToken) throws AlipayApiException {

        if (StringUtils.isEmpty(charset)) {
            charset = AlipayConstants.CHARSET_UTF8;
        }

        RequestParametersHolder requestHolder = new RequestParametersHolder();
        AlipayHashMap appParams = new AlipayHashMap(request.getTextParams());

        // 仅当API包含biz_content参数且值为空时，序列化bizModel填充bizContent
        try {
            if (request.getClass().getMethod("getBizContent") != null
                    && StringUtils.isEmpty(appParams.get(AlipayConstants.BIZ_CONTENT_KEY))
                    && request.getBizModel() != null) {
                appParams.put(AlipayConstants.BIZ_CONTENT_KEY,
                        new JSONWriter().write(request.getBizModel(), true));
            }
        } catch (NoSuchMethodException e) {
            // 找不到getBizContent则什么都不做
        } catch (SecurityException e) {
            AlipayLogger.logBizError(e);
        }

        if (!StringUtils.isEmpty(appAuthToken)) {
            appParams.put(AlipayConstants.APP_AUTH_TOKEN, appAuthToken);
        }

        requestHolder.setApplicationParams(appParams);

        AlipayHashMap protocalMustParams = new AlipayHashMap();
        protocalMustParams.put(AlipayConstants.METHOD, request.getApiMethodName());
        protocalMustParams.put(AlipayConstants.VERSION, request.getApiVersion());
        protocalMustParams.put(AlipayConstants.APP_ID, this.appId);
        protocalMustParams.put(AlipayConstants.SIGN_TYPE, this.sign_type);
        protocalMustParams.put("charset", charset);

        Long timestamp = System.currentTimeMillis();
        DateFormat df = new SimpleDateFormat(AlipayConstants.DATE_TIME_FORMAT);
        df.setTimeZone(TimeZone.getTimeZone(AlipayConstants.DATE_TIMEZONE));
        protocalMustParams.put(AlipayConstants.TIMESTAMP, df.format(new Date(timestamp)));
        protocalMustParams.put(AlipayConstants.FORMAT, format);
        requestHolder.setProtocalMustParams(protocalMustParams);

        if (AlipayConstants.SIGN_TYPE_RSA.equals(this.sign_type)) {

            String signContent = AlipaySignature.getSignatureContent(requestHolder);

            protocalMustParams.put(AlipayConstants.SIGN,
                    AlipaySignature.rsaSign(signContent, privateKey, charset));

        } else if (AlipayConstants.SIGN_TYPE_RSA2.equals(this.sign_type)) {

            String signContent = AlipaySignature.getSignatureContent(requestHolder);

            protocalMustParams.put(AlipayConstants.SIGN,
                    AlipaySignature.rsa256Sign(signContent, privateKey, charset));

        } else {
            protocalMustParams.put(AlipayConstants.SIGN, "");
        }

        AlipayMobilePublicMultiMediaDownloadResponse rsp = null;
        try {

            if (request instanceof AlipayMobilePublicMultiMediaDownloadRequest) {
                OutputStream outputStream = ((AlipayMobilePublicMultiMediaDownloadRequest) request)
                        .getOutputStream();
                rsp = doGet(serverUrl, requestHolder, charset, connectTimeout, readTimeout,
                        outputStream);
            }
        } catch (IOException e) {
            throw new AlipayApiException(e);
        }
        return (T) rsp;
    }

    /**
     * @param url
     * @param params
     * @param charset
     * @param connectTimeout
     * @param readTimeout
     * @return
     * @throws IOException
     * @throws AlipayApiException
     */
    @SuppressWarnings("unchecked")
    public static AlipayMobilePublicMultiMediaDownloadResponse doGet(String url,
                                                                     RequestParametersHolder requestHolder,
                                                                     String charset,
                                                                     int connectTimeout,
                                                                     int readTimeout,
                                                                     OutputStream output) throws AlipayApiException,
            IOException {
        HttpURLConnection conn = null;
        AlipayMobilePublicMultiMediaDownloadResponse response = null;

        try {

            Map<String, String> params = new TreeMap<String, String>();
            AlipayHashMap appParams = requestHolder.getApplicationParams();
            if (appParams != null && appParams.size() > 0) {
                params.putAll(appParams);
            }
            AlipayHashMap protocalMustParams = requestHolder.getProtocalMustParams();
            if (protocalMustParams != null && protocalMustParams.size() > 0) {
                params.putAll(protocalMustParams);
            }
            AlipayHashMap protocalOptParams = requestHolder.getProtocalOptParams();
            if (protocalOptParams != null && protocalOptParams.size() > 0) {
                params.putAll(protocalOptParams);
            }

            String ctype = "application/x-www-form-urlencoded;charset=" + charset;
            String query = buildQuery(params, charset);
            try {
                conn = getConnection(buildGetUrl(url, query), METHOD_GET, ctype);
                conn.setConnectTimeout(connectTimeout);
                conn.setReadTimeout(readTimeout);
            } catch (IOException e) {
                Map<String, String> map = getParamsFromUrl(url);
                AlipayLogger.logCommError(e, url, map.get("app_key"), map.get("method"), params);
                throw e;
            }

            try {
                if (conn.getResponseCode() == 200) {
                    if (conn.getContentType() != null
                            && conn.getContentType().toLowerCase().contains("text/plain")) {
                        String rsp = getResponseAsString(conn);
                        ObjectJsonParser<AlipayMobilePublicMultiMediaDownloadResponse> parser = new ObjectJsonParser<AlipayMobilePublicMultiMediaDownloadResponse>(
                                AlipayMobilePublicMultiMediaDownloadResponse.class);
                        response = parser.parse(rsp);
                        response.setBody(rsp);
                        response.setParams(appParams);
                        return response;
                    }

                    StreamUtil.io(conn.getInputStream(), output);
                    response = new AlipayMobilePublicMultiMediaDownloadResponse();
                    response.setCode("200");
                    response.setMsg("成功");
                    response.setBody(
                            "{\"alipay_mobile_public_multimedia_download_response\":{\"code\":200,\"msg\":\"成功\"}}");
                    response.setParams(appParams);
                } else {
                    response = new AlipayMobilePublicMultiMediaDownloadResponse();
                    response.setCode(String.valueOf(conn.getResponseCode()));
                    response.setMsg(conn.getResponseMessage());
                    response.setParams(appParams);
                }
            } catch (IOException e) {
                Map<String, String> map = getParamsFromUrl(url);
                AlipayLogger.logCommError(e, conn, map.get("app_key"), map.get("method"), params);
                throw e;
            }

        } finally {
            if (conn != null) {
                conn.disconnect();
            }
            if (output != null) {
                output.close();
            }
        }
        return response;

    }

    private static HttpURLConnection getConnection(URL url, String method,
                                                   String ctype) throws IOException {
        HttpURLConnection conn = null;
        if ("https".equals(url.getProtocol())) {
            SSLContext ctx = null;
            try {
                ctx = SSLContext.getInstance("TLS");
                ctx.init(new KeyManager[0], new TrustManager[]{new DefaultTrustManager()},
                        new SecureRandom());
            } catch (Exception e) {
                throw new IOException(e);
            }
            HttpsURLConnection connHttps = (HttpsURLConnection) url.openConnection();
            connHttps.setSSLSocketFactory(ctx.getSocketFactory());
            connHttps.setHostnameVerifier(new HostnameVerifier() {
                public boolean verify(String hostname, SSLSession session) {
                    return false;
                }
            });
            conn = connHttps;
        } else {
            conn = (HttpURLConnection) url.openConnection();
        }

        conn.setRequestMethod(method);
        conn.setDoInput(true);
        conn.setDoOutput(true);
        conn.setRequestProperty("Accept", "text/xml,text/javascript,text/html");
        conn.setRequestProperty("User-Agent", "aop-sdk-java");
        conn.setRequestProperty("Content-Type", ctype);
        return conn;
    }

    protected static String getResponseAsString(HttpURLConnection conn) throws IOException {
        String charset = getResponseCharset(conn.getContentType());
        InputStream es = conn.getErrorStream();
        if (es == null) {
            return getStreamAsString(conn.getInputStream(), charset);
        } else {
            String msg = getStreamAsString(es, charset);
            if (StringUtils.isEmpty(msg)) {
                throw new IOException(conn.getResponseCode() + ":" + conn.getResponseMessage());
            } else {
                throw new IOException(msg);
            }
        }
    }

    private static String getStreamAsString(InputStream stream, String charset) throws IOException {
        try {
            BufferedReader reader = new BufferedReader(new InputStreamReader(stream, charset));
            StringWriter writer = new StringWriter();

            char[] chars = new char[256];
            int count = 0;
            while ((count = reader.read(chars)) > 0) {
                writer.write(chars, 0, count);
            }

            return writer.toString();
        } finally {
            if (stream != null) {
                stream.close();
            }
        }
    }

    private static String getResponseCharset(String ctype) {
        String charset = DEFAULT_CHARSET;

        if (!StringUtils.isEmpty(ctype)) {
            String[] params = ctype.split(";");
            for (String param : params) {
                param = param.trim();
                if (param.startsWith("charset")) {
                    String[] pair = param.split("=", 2);
                    if (pair.length == 2) {
                        if (!StringUtils.isEmpty(pair[1])) {
                            charset = pair[1].trim();
                        }
                    }
                    break;
                }
            }
        }

        return charset;
    }

    private static Map<String, String> getParamsFromUrl(String url) {
        Map<String, String> map = null;
        if (url != null && url.indexOf('?') != -1) {
            map = splitUrlQuery(url.substring(url.indexOf('?') + 1));
        }
        if (map == null) {
            map = new HashMap<String, String>();
        }
        return map;
    }

    private static URL buildGetUrl(String strUrl, String query) throws IOException {
        URL url = new URL(strUrl);
        if (StringUtils.isEmpty(query)) {
            return url;
        }

        if (StringUtils.isEmpty(url.getQuery())) {
            if (strUrl.endsWith("?")) {
                strUrl = strUrl + query;
            } else {
                strUrl = strUrl + "?" + query;
            }
        } else {
            if (strUrl.endsWith("&")) {
                strUrl = strUrl + query;
            } else {
                strUrl = strUrl + "&" + query;
            }
        }

        return new URL(strUrl);
    }

    public static String buildQuery(Map<String, String> params, String charset) throws IOException {
        if (params == null || params.isEmpty()) {
            return null;
        }

        StringBuilder query = new StringBuilder();
        Set<Entry<String, String>> entries = params.entrySet();
        boolean hasParam = false;

        for (Entry<String, String> entry : entries) {
            String name = entry.getKey();
            String value = entry.getValue();

            if (StringUtils.areNotEmpty(name, value)) {
                if (hasParam) {
                    query.append("&");
                } else {
                    hasParam = true;
                }

                query.append(name).append("=").append(URLEncoder.encode(value, charset));
            }
        }

        return query.toString();
    }

    /**
     * @param query
     * @return
     */
    public static Map<String, String> splitUrlQuery(String query) {
        Map<String, String> result = new HashMap<String, String>();

        String[] pairs = query.split("&");
        if (pairs != null && pairs.length > 0) {
            for (String pair : pairs) {
                String[] param = pair.split("=", 2);
                if (param != null && param.length == 2) {
                    result.put(param[0], param[1]);
                }
            }
        }

        return result;
    }

    public <T extends AlipayResponse> T pageExecute(AlipayRequest<T> request) throws AlipayApiException {
        return null;
    }

    public <T extends AlipayResponse> T pageExecute(AlipayRequest<T> request,
                                                    String method) throws AlipayApiException {
        return null;
    }

    public <TR extends AlipayResponse, T extends AlipayRequest<TR>> TR parseAppSyncResult(Map<String, String> result,
                                                                                          Class<T> requsetClazz) throws AlipayApiException {
        return null;
    }

    public <T extends AlipayResponse> T sdkExecute(AlipayRequest<T> request) throws AlipayApiException {
        return null;
    }

}
