package cn.quantgroup.xyqb.service.captcha;

import cn.quantgroup.xyqb.Constants;
import cn.quantgroup.xyqb.util.encrypt.Md5Util;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpStatus;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * Java SDK
 *
 * @author geetet
 */
@Slf4j
public class GeetestLib {
    protected final String verName = "4.0";

    private String apiUrl;

    protected final String registerUrl = "/register.php";
    protected final String validateUrl = "/validate.php";

    protected final String json_format = "1";

    /**
     * 公钥
     */
    private String captchaId;

    /**
     * 私钥
     */
    private String privateKey;

    /**
     * 是否开启新的failback
     */
    private boolean newFailback = false;

    /**
     * 返回字符串
     */
    private Map<String, String> responseStr;

    /**
     * 调试开关，是否输出调试日志
     */
    public boolean debugCode = true;

    /**
     * 带参数构造函数
     *
     * @param captchaId
     * @param privateKey
     */
    public GeetestLib(String captchaId, String privateKey, boolean newFailback, String apiUrl) {
        this.captchaId = captchaId;
        this.privateKey = privateKey;
        this.newFailback = newFailback;
        this.apiUrl = apiUrl;
    }

    /**
     * 获取本次验证初始化返回字符串
     *
     * @return 初始化结果
     */
    public Map<String, String> getResponseStr(HashMap<String, String> param) {
        preProcess(param);
        return responseStr;
    }


    /**
     * 预处理失败后的返回格式串
     *
     * @return
     */
    private Map<String, String> getFailPreProcessRes() {
        return null;
    }

    /**
     * 预处理成功后的标准串
     */
    private Map<String, String> getSuccessPreProcessRes(String challenge) {
        gtlog("challenge:" + challenge);
        Map<String, String> data = new HashMap<>(3);
        data.put("success", "1");
        data.put("gt", this.captchaId);
        data.put("challenge", challenge);
        return data;
    }

    /**
     * 验证初始化预处理
     *
     * @return 1表示初始化成功，0表示初始化失败
     */
    public int preProcess(HashMap<String, String> data) {

        if (registerChallenge(data) != 1) {

            this.responseStr = this.getFailPreProcessRes();
            return 0;

        }

        return 1;

    }

    /**
     * 用captchaID进行注册，更新challenge
     *
     * @return 1表示注册成功，0表示注册失败
     */
    private int registerChallenge(HashMap<String, String> data) {

        try {
            String userId = data.get("user_id");
            String clientType = data.get("client_type");
            String ipAddress = data.get("ip_address");

            String getUrl = apiUrl + registerUrl + "?";
            String param = "gt=" + this.captchaId + "&json_format=" + this.json_format;

            if (userId != null) {
                param = param + "&user_id=" + userId;
            }
            if (clientType != null) {
                param = param + "&client_type=" + clientType;
            }
            if (ipAddress != null) {
                param = param + "&ip_address=" + ipAddress;
            }

            gtlog("GET_URL:" + getUrl + param);
            String result_str = readContentFromGet(getUrl + param);
            if (Objects.equals(result_str, Constants.CHECK_FAIL)) {

                gtlog("gtServer register challenge failed");
                return 0;

            }

            gtlog("result:" + result_str);
            JSONObject jsonObject = new JSONObject(result_str);
            String return_challenge = jsonObject.getString("challenge");

            gtlog("return_challenge:" + return_challenge);

            if (return_challenge.length() == Constants.MD5_LENGTH) {

                this.responseStr = this.getSuccessPreProcessRes(Md5Util.build(return_challenge + this.privateKey));

                return 1;

            } else {

                gtlog("gtServer register challenge error");

                return 0;

            }
        } catch (Exception e) {

            gtlog(e.toString());
            gtlog("exception:register api");

        }
        return 0;
    }

    /**
     * 判断一个表单对象值是否为空
     *
     * @param gtObj
     * @return
     */
    protected boolean objIsEmpty(Object gtObj) {

        if (gtObj == null) {

            return true;

        }

        if (gtObj.toString().trim().length() == 0) {

            return true;

        }

        return false;
    }

    /**
     * 检查客户端的请求是否合法,三个只要有一个为空，则判断不合法
     *
     * @param challenge
     * @param validate
     * @param seccode
     * @return
     */
    private boolean resquestIsLegal(String challenge, String validate, String seccode) {

        if (objIsEmpty(challenge)) {

            return false;

        }

        if (objIsEmpty(validate)) {

            return false;

        }

        if (objIsEmpty(seccode)) {

            return false;

        }

        return true;
    }


    /**
     * 服务正常的情况下使用的验证方式,向gt-server进行二次验证,获取验证结果
     *
     * @param challenge
     * @param validate
     * @param seccode
     * @return 验证结果, 1表示验证成功0表示验证失败
     */
    public int enhencedValidateRequest(String challenge, String validate, String seccode, HashMap<String, String> data) {
        if (!resquestIsLegal(challenge, validate, seccode)) {
            return 0;
        }
        gtlog("request legitimate");
        String userId = data.get("user_id");
        String clientType = data.get("client_type");
        String ipAddress = data.get("ip_address");
        String postUrl = this.apiUrl + this.validateUrl;
        String param = String.format("challenge=%s&validate=%s&seccode=%s&json_format=%s",
                challenge, validate, seccode, this.json_format);
        if (userId != null) {
            param = param + "&user_id=" + userId;
        }
        if (clientType != null) {
            param = param + "&client_type=" + clientType;
        }
        if (ipAddress != null) {
            param = param + "&ip_address=" + ipAddress;
        }
        gtlog("param:" + param);
        String response = "";
        try {
            if (validate.length() <= 0) {
                return 0;
            }
            if (!checkResultByPrivate(challenge, validate)) {
                return 0;
            }
            gtlog("checkResultByPrivate");
            response = readContentFromPost(postUrl, param);
            gtlog("response: " + response);
        } catch (Exception e) {
            log.error("向gt-server进行二次验证", e);
        }
        String return_seccode = "";
        try {
            JSONObject return_map = new JSONObject(response);
            return_seccode = return_map.getString("seccode");
            gtlog("md5: " + Md5Util.build(return_seccode));
            if (return_seccode.equals(Md5Util.build(seccode))) {
                return 1;
            } else {
                return 0;
            }
        } catch (JSONException e) {
            gtlog("json load error");
            return 0;
        }
    }

    /**
     * failback使用的验证方式
     *
     * @param challenge
     * @param validate
     * @param seccode
     * @return 验证结果, 1表示验证成功0表示验证失败
     */
    public int failbackValidateRequest(String challenge, String validate, String seccode) {

        gtlog("in failback validate");

        if (!resquestIsLegal(challenge, validate, seccode)) {
            return 0;
        }
        gtlog("request legitimate");

        return 1;
    }

    /**
     * 输出debug信息，需要开启debugCode
     *
     * @param message
     */
    public void gtlog(String message) {
        if (debugCode) {
            log.info("gtlog: {}", message);
        }
    }

    protected boolean checkResultByPrivate(String challenge, String validate) {
        String encodeStr = Md5Util.build(privateKey + "geetest" + challenge);
        return validate.equals(encodeStr);
    }

    /**
     * 发送GET请求，获取服务器返回结果
     *
     * @param URL
     * @return 服务器返回结果
     * @throws IOException
     */
    private String readContentFromGet(String URL) throws IOException {

        URL getUrl = new URL(URL);
        HttpURLConnection connection = (HttpURLConnection) getUrl
                .openConnection();
        // 设置连接主机超时（单位：毫秒）
        connection.setConnectTimeout(2000);
        // 设置从主机读取数据超时（单位：毫秒）
        connection.setReadTimeout(2000);

        // 建立与服务器的连接，并未发送数据
        connection.connect();

        if (connection.getResponseCode() == HttpStatus.SC_OK) {
            // 发送数据到服务器并使用Reader读取返回的数据
            StringBuilder sBuffer = new StringBuilder();

            InputStream inStream = null;
            byte[] buf = new byte[1024];
            inStream = connection.getInputStream();
            for (int n; (n = inStream.read(buf)) != -1; ) {
                sBuffer.append(new String(buf, 0, n, "UTF-8"));
            }
            inStream.close();
            // 断开连接
            connection.disconnect();

            return sBuffer.toString();
        } else {
            return Constants.CHECK_FAIL;
        }
    }

    /**
     * 发送POST请求，获取服务器返回结果
     *
     * @param URL
     * @param data
     * @return 服务器返回结果
     * @throws IOException
     */
    private String readContentFromPost(String URL, String data) throws IOException {

        gtlog(data);
        URL postUrl = new URL(URL);
        HttpURLConnection connection = (HttpURLConnection) postUrl
                .openConnection();
        // 设置连接主机超时（单位：毫秒）
        connection.setConnectTimeout(2000);
        // 设置从主机读取数据超时（单位：毫秒）
        connection.setReadTimeout(2000);
        connection.setRequestMethod("POST");
        connection.setDoInput(true);
        connection.setDoOutput(true);
        connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");

        // 建立与服务器的连接，并未发送数据
        connection.connect();

        OutputStreamWriter outputStreamWriter = new OutputStreamWriter(connection.getOutputStream(), "utf-8");
        outputStreamWriter.write(data);
        outputStreamWriter.flush();
        outputStreamWriter.close();

        if (connection.getResponseCode() == HttpStatus.SC_OK) {
            // 发送数据到服务器并使用Reader读取返回的数据
            StringBuilder sBuffer = new StringBuilder();

            InputStream inStream = null;
            byte[] buf = new byte[1024];
            inStream = connection.getInputStream();
            for (int n; (n = inStream.read(buf)) != -1; ) {
                sBuffer.append(new String(buf, 0, n, "UTF-8"));
            }
            inStream.close();
            // 断开连接
            connection.disconnect();

            return sBuffer.toString();
        } else {

            return Constants.CHECK_FAIL;
        }
    }

}
