package com.library.test;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import javax.net.ssl.HttpsURLConnection;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.MessageDigest;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * 测试环境
 * point_id: 181775
 * password: link_asia_test_pwd
 * md5: nS/Ou9oiC4LcSvEEn+54Jg==
 */
public class PaygoTest {

    private static final String mAddress = "https://api2.paygo24.com/paygoservice.asmx";
    private static final String mUsername = "181775"; //point_id
    private static final String mPassword = encodePass("link_asia_test_pwd");


    public static void main(String[] args) {
        Date now = new Date();

        SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMddHHmmss");
        SimpleDateFormat ticket_format = new SimpleDateFormat("HHmmss");
        SimpleDateFormat format3339 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
        PaygoTest test = new PaygoTest();
        Payment payment = new Payment();
        payment.setId(UUID.randomUUID().toString());
        payment.setAccount("969414416");
        payment.setServiceId("2");
        payment.setCommission("0.00");
        payment.setValue("1.00");
        payment.setTicket(ticket_format.format(now));
        payment.setNumber(formatter.format(now));
        payment.setTime(format3339.format(now));
        payment.setEncashmentNumber("0");
        payment.setCurrency("USD");
        payment.setPaymentTool("1");

        List params = new ArrayList<>();
        params.add(new Payment.PaymentParam("account", "969414416"));
        //account parameter
        payment.setParams(params);
        try {
            //Checking payment parameters
            test.registerCheckRequest(payment);

            //2 sec delay before getting result
            Thread.sleep(2000);

            //Getting check result
            test.getCheckResultRequest(payment);
            //In your system you have to repeat request results while
            //State is not 0, 1 or 2
            //If State==1 reject payment proceeding

            //Sending payment
            test.sendPaymentRequest(payment);
            //In this method if you get ErrorResponse check Error Code
            //if Code==7 (Payment is already exists) call checkStatusRequest()

            //5 sec delay before checking payment status
            Thread.sleep(5000);

            //Checking payment status
            test.checkStatusRequest(payment);
            //In your system you have to repeat request check status while
            //State is not final - 1, 2 (rejected) or 5 (completed)
            //If you got status 3 or 4 and do not receive final state for 40 seconds complete the payment on your side


        } catch (Exception ex) {
            Logger.getLogger(PaygoTest.class.getName()).log(Level.SEVERE, null, ex);
        }

    }


    private static final String encodePass(String pass) {
        try {
            byte messageDigest[] = null;
            MessageDigest digest = java.security.MessageDigest
                    .getInstance("MD5");
            digest.update(pass.getBytes());
            messageDigest = digest.digest();
            pass = Base64.getEncoder().encodeToString(messageDigest);
            return pass;
        } catch (Exception ex) {
            return "";
        }
    }

    public InputStream sendRequest(String requestXML) throws Exception {
        try {

            System.out.println(requestXML);

            URL urll = new URL(mAddress);
            HttpsURLConnection conn = (HttpsURLConnection) urll.openConnection();

            // set required headers
            conn.setRequestProperty("Content-Type",
                    "text/xml;charset=UTF-8");
            conn.setRequestMethod("POST");

            conn.setDoOutput(true);

            byte[] body = requestXML.getBytes("UTF-8");
            conn.setFixedLengthStreamingMode(body.length);

            BufferedOutputStream out = new BufferedOutputStream(conn.getOutputStream());
            out.write(body);
            out.close();
            conn.connect();
            int statusCode = conn.getResponseCode();

            if (statusCode == HttpURLConnection.HTTP_OK) {
                InputStream content = new BufferedInputStream(conn.getInputStream());
                return content;
            } else {
                throw new Exception("HTTP Status: " + statusCode);
            }

        } catch (IOException e) {
            e.printStackTrace();
            throw e;

        } catch (Exception e) {
            e.printStackTrace();
            throw e;
        }

    }

    private void getCheckResultRequest(Payment payment) throws Exception {

        String requestXML
                = "\n"
                + "\n"
                + "	\n"
                + "	"
                + ""
                + "\n" + ""
                + "$POINT_ID\n"
                + "			$PASSWORD"
                + "" + ""
                + "\n"
                + "";

        // set the payment id
        requestXML = requestXML.replace("$PAYMENT_ID", payment.getId()).replace("$POINT_ID", mUsername).replace("$PASSWORD", mPassword);

        // send the request to the server
        InputStream responseXML = sendRequest(requestXML);

        String responseStr = convertStreamToString(responseXML);
        System.out.println(responseStr);

    }

    private void registerCheckRequest(Payment payment) throws Exception {
//        String requestXML = "\n"
//                + "\n"
//                + "	\n"
//                + "	"
//                + ""
//                + "\n"
//                + "\n" + "$PAYMENT_PARAMS"
//                + "\n" + "" + ""
//                + "$POINT_ID\n"
//                + "			$PASSWORD"
//                + "" + ""
//                + "\n"
//                + "";


        String requestXML = "<?xml version='1.0' encoding='utf-8'?>\n" +
                "<soap:Envelope xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xmlns:xsd='http://www.w3.org/2001/XMLSchema'>\n" +
                "  <soap:Body>\n" +
                "    <SendRequest xmlns='http://tempuri.org/'>\n" +
                "      <request xsi:type='RegisterCheckRequest' Id='7a646d45-ee2f-4b1c-8de8-780c416fbbd0' Service='42' xmlns='http://paygo24.com/v3/protocol'>\n" +
                "        <PaymentParameters xmlns=''>\n" +
                "          <Parameter Name='account' Value='08374829' />\n" +
                "        </PaymentParameters>\n" +
                "      </request>\n" +
                "      <pointId>46</pointId>\n" +
                "      <password>4QrcOUm6Wau+VuBX8g+IPg==</password>\n" +
                "    </SendRequest>\n" +
                "  </soap:Body>\n" +
                "</soap:Envelope>";


        // set the payment id
        requestXML = requestXML.replace("$PAYMENT_ID", payment.getId())
                .replace("$SERVICE_ID", payment.getServiceId())
                .replace("$POINT_ID", mUsername)
                .replace("$PASSWORD", mPassword);

        // serialize the payment parameters
        String paramsStr = "";
        List params = payment.getParams();

        for (int i = 0, c = params.size(); i < c; ++i) {
            Payment.PaymentParam param = (Payment.PaymentParam) params.get(i);

            paramsStr += "\n";
        }

        requestXML = requestXML.replace("$PAYMENT_PARAMS", paramsStr);

        // send the request to the server
        InputStream responseXML = sendRequest(requestXML);

        String responseStr = convertStreamToString(responseXML);
        System.out.println(responseStr);

    }

    private void sendPaymentRequest(Payment payment) throws Exception {

        String requestXML = "\n"
                + "\n"
                + "	\n"
                + "	"
                + ""
                + "\n"
                + "\n" + "$PAYMENT_PARAMS"
                + "\n" + ""
                + "$POINT_ID\n"
                + "			$PASSWORD"
                + "" + ""
                + "\n"
                + "";

        // set the payment id
        requestXML = requestXML.replace("$PAYMENT_ID", payment.getId())
                .replace("$SERVICE_ID", payment.getServiceId())
                .replace("$PAYMENT_TICKET", payment.getTicket())
                .replace("$PAYMENT_NUMBER", payment.getNumber())
                .replace("$PAYMENT_TIME", payment.getTime())
                .replace("$ENCASHMENT_NUMBER", payment.getEncashmentNumber())
                .replace("$PAYMENT_VALUE", payment.getValue())
                .replace("$PAYMENT_COMMISSION", payment.getCommission())
                .replace("$PAYMENT_CURRENCY", payment.getCurrency())
                .replace("$PAYMENT_TOOL", payment.getPaymentTool())
                .replace("$POINT_ID", mUsername)
                .replace("$PASSWORD", mPassword);

        // serialize the payment parameters
        String paramsStr = "";
        List params = payment.getParams();

        for (int i = 0, c = params.size(); i < c; ++i) {
            Payment.PaymentParam param = (Payment.PaymentParam) params.get(i);

            paramsStr += "\n";
        }

        requestXML = requestXML.replace("$PAYMENT_PARAMS", paramsStr);

        // send the request to the server
        InputStream responseXML = sendRequest(requestXML);

        String responseStr = convertStreamToString(responseXML);

        System.out.println(responseStr);

    }

    /*
     * Checks the server response for errors and throws an exception
     */
    private void checkError(Document dom) throws PaypointException {
        Element root = dom.getDocumentElement();
        NodeList errorItems = root.getElementsByTagName("Error");

        if (errorItems.getLength() != 0) {
            Node item = errorItems.item(0);
            NamedNodeMap attrs = item.getAttributes();
            String errorText = attrs.getNamedItem("Description")
                    .getTextContent();

            throw new PaypointException(errorText, attrs.getNamedItem("Number")
                    .getTextContent());
        }
    }

    private void checkStatusRequest(Payment payment) throws Exception {
        String requestXML
                = "\n"
                + "\n"
                + "	\n"
                + "	"
                + ""
                + "\n"
                + ""
                + "$POINT_ID\n"
                + "$PASSWORD"
                + "" + ""
                + "\n"
                + "";

        // set the payment id
        requestXML = requestXML.replace("$PAYMENT_ID", payment.getId())
                .replace("$POINT_ID", mUsername)
                .replace("$PASSWORD", mPassword);

        // send the request to the server
        InputStream responseXML = sendRequest(requestXML);

        String responseStr = convertStreamToString(responseXML);
        System.out.println(responseStr);

    }

    private String getAttrValue(NamedNodeMap attrs, String name) {
        Node value = attrs.getNamedItem(name);
        if (value != null) {
            return value.getTextContent();
        } else {
            return "";
        }
    }

    private static String convertStreamToString(InputStream is) {
        /*
         * To convert the InputStream to String we use the
         * BufferedReader.readLine() method. We iterate until the BufferedReader
         * return null which means there's no more data to read. Each line will
         * appended to a StringBuilder and returned as String.
         */
        BufferedReader reader = new BufferedReader(new InputStreamReader(is));
        StringBuilder sb = new StringBuilder();

        String line = null;
        try {
            while ((line = reader.readLine()) != null) {
                sb.append(line + "\n");
            }
        } catch (IOException e) {

        } finally {
            try {
                is.close();
            } catch (IOException e) {

            }
        }
        return sb.toString();
    }

    public static final class Payment implements Comparable {

        private String mId;
        private String mServiceId;
        private String mTicket;
        private String mNumber;
        private String mTime;
        private String mEncashmentNumber;
        private String mValue;
        private String mCommission;
        private String mBalance;
        private String mCurrency;
        private String mPaymentTool;
        private String mRejectPayment;
        private String mState;
        private String mStateComment;
        private String mStateTime;
        private String mServerPaymentId;

        private List<PaymentParam> mParams;

        // cached parameters
        private String mAccount;
        private int mStateAsInt;
        private long mTimeLong;
        private String mServiceName;

        public Payment() {
            mParams = new ArrayList();

        }

        public long getTimeLong() {
            return mTimeLong;
        }

        public String getId() {
            return mId;
        }

        public void setId(String id) {
            mId = id;
        }

        public String getServiceId() {
            return mServiceId;
        }

        public void setServiceId(String serviceId) {
            mServiceId = serviceId;
        }

        public List getParams() {
            return mParams;
        }

        public void setParams(List params) {
            mParams = params;
        }

        public String getTicket() {
            return mTicket;
        }

        public void setTicket(String ticket) {
            mTicket = ticket;
        }

        public String getNumber() {
            return mNumber;
        }

        public void setNumber(String number) {
            mNumber = number;
        }

        public String getTime() {
            return mTime;
        }

        public void setTime(String time) {
            mTime = time;

        }

        public String getEncashmentNumber() {
            return mEncashmentNumber;
        }

        public void setEncashmentNumber(String encashmentNumber) {
            mEncashmentNumber = encashmentNumber;
        }

        public String getValue() {
            return mValue;
        }

        public void setValue(String value) {
            mValue = value;
        }

        public String getCommission() {
            return mCommission;
        }

        public void setCommission(String commission) {
            mCommission = commission;
        }

        public String getBalance() {
            return mBalance;
        }

        public void setBalance(String balance) {
            mBalance = balance;
        }

        public String getCurrency() {
            return mCurrency;
        }

        public void setCurrency(String currency) {
            mCurrency = currency;
        }

        public String getPaymentTool() {
            return mPaymentTool;
        }

        public void setPaymentTool(String paymentTool) {
            mPaymentTool = paymentTool;
        }

        public String getRejectPayment() {
            return mRejectPayment;
        }

        public void setRejectPayment(String rejectPayment) {
            mRejectPayment = rejectPayment;
        }

        public String getState() {
            return mState;
        }

        public void setState(String state) {
            mState = state;

            try {
                mStateAsInt = Integer.parseInt(state);
            } catch (NumberFormatException e) {
                mStateAsInt = -1;
            }
        }

        public String getStateComment() {
            return mStateComment;
        }

        public void setStateComment(String stateComment) {
            mStateComment = stateComment;
        }

        public String getStateTime() {
            return mStateTime;
        }

        public void setStateTime(String stateTime) {
            mStateTime = stateTime;
        }

        public String getAccount() {
            return mAccount;
        }

        public void setAccount(String account) {
            mAccount = account;
        }

        public int getStateAsInt() {
            return this.mStateAsInt;
        }

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

//        @Override
//        public int compareTo(Payment another) {
        //            return Long.compare(another.getTimeLong(), mTimeLong);

//        }

        @Override
        public int compareTo(Object o) {
            Payment payment = (Payment) o;
            return Long.compare(payment.getTimeLong(), mTimeLong);

//            return 0;
        }


        public String getServiceName() {
            return mServiceName;
        }

        public void setServiceName(String serviceName) {
            mServiceName = serviceName;
        }

        public String getServerPaymentId() {
            return mServerPaymentId;
        }

        public void setServerPaymentId(String serverPaymentId) {
            mServerPaymentId = serverPaymentId;
        }

        public String getParamValue(String paramName) {
            try {
                for (PaymentParam param : mParams) {
                    if (param.getName().equalsIgnoreCase(paramName)) {
                        return param.getValue();
                    }
                }
            } catch (Exception e) {
                // TODO: handle exception
            }
            return null;
        }

        public static class PaymentParam {

            public static final int TYPE_INPUT = 1;
            public static final int TYPE_OUTPUT = 2;
            public static final int TYPE_INPUT_OUTPUT = 3;
            public static final int TYPE_INTERNAL_DEVICE = 4;
            private String mName;
            private String mValue;
            private int mType = TYPE_INPUT;

            public PaymentParam(String name, String value) {
                mName = name;
                mValue = value;
            }

            public PaymentParam(String name, String value, int type) {
                this(name, value);
                mType = type;

            }

            public int getType() {
                return mType;
            }

            public void setType(int type) {
                mType = type;
            }

            public String getName() {
                return mName;
            }

            public void setName(String name) {
                mName = name;
            }

            public String getValue() {
                return mValue;
            }

            public void setValue(String value) {
                mValue = value;
            }
        }
    }

    public static class PaypointException extends Exception {
        private String mErrorCode;

        public String getErrorCode() {
            return mErrorCode;
        }

        PaypointException(String message, String errorCode) {
            super(message);
            mErrorCode = errorCode;
        }
    }
}