package com.zbkj.service.wangshang.api;

import com.zbkj.service.wangshang.api.internal.mapping.SignChecker;
import com.zbkj.service.wangshang.api.internal.mapping.Signer;
import com.zbkj.service.wangshang.api.internal.parser.xml.ObjectXmlParser;
import com.zbkj.service.wangshang.api.internal.util.*;
import com.zbkj.service.wangshang.api.service.base.MerchantprodMerchantUploadphotoRequest;
import com.zbkj.service.wangshang.api.service.base.bkcloudfunds.BkcloudfundsOssFileUploadRequest;
import org.apache.http.HttpEntity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.Security;
import java.util.HashMap;
import java.util.Map;

public abstract class AbstractMybankClient implements MybankClient {
    private static final Logger logger = LoggerFactory.getLogger(AbstractMybankClient.class);
    private String serverUrl;

    private String format   = MybankConstants.FORMAT_XML;
    private String signType = MybankConstants.SIGN_TYPE_RSA;
    private String charset = MybankConstants.CHARSET_UTF8;

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

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

    public AbstractMybankClient(String serverUrl) {
        this.serverUrl = serverUrl;
    }

    public AbstractMybankClient(String serverUrl, String charset) {
        this.serverUrl = serverUrl;
        this.charset = charset;
    }

    public AbstractMybankClient(String serverUrl, String charset, String signType) {
        this(serverUrl, charset);
        if (!StringUtils.isEmpty(signType)) {
            this.signType = signType;
        }
    }

    public AbstractMybankClient(String serverUrl, String format, String charset, String signType) {
        this(serverUrl, charset);
        if (!StringUtils.isEmpty(format)) {
            this.format = format;
        }
        if (!StringUtils.isEmpty(signType)) {
            this.signType = signType;
        }
    }

    @Override
    public <T extends MybankResponse> T execute(MybankRequest<T> request) throws MybankApiException {
        MybankParser<T> parser = null;
        if (MybankConstants.FORMAT_XML.equals(this.format)) {
            parser = new ObjectXmlParser<T>(request.getResponseClass());
        } else {
            throw new MybankApiException(MybankApiExceptionEnum.NOT_XML_TYPE);
        }
        return execute(request, parser);
    }

    @Override
    public <T extends MybankResponse> T execute(MybankUploadRequest<T> request) throws MybankApiException {
        MybankParser<T> parser = new ObjectXmlParser<T>(request.getResponseClass());
        return execute(request, parser);
    }

    private <T extends MybankResponse> T execute(DefaultRequest<T> request, MybankParser<T> parser) throws MybankApiException {
        long beginTime = System.currentTimeMillis();
        Map<String, Object> rt = new HashMap<String, Object>();
        if (request instanceof MybankRequest){
            rt = doPost((MybankRequest<T>) request);
        }else {
            rt = doPost((MybankUploadRequest<T>) request);
        }

        Map<String, Long> costTimeMap = new HashMap<String, Long>();
        if (rt.containsKey(MybankConstants.PREPARE_TIME)) {
            costTimeMap.put("prepareCostTime", (Long)(rt.get(MybankConstants.PREPARE_TIME)) - beginTime);
            if (rt.containsKey(MybankConstants.REQUEST_TIME)) {
                costTimeMap.put("requestCostTime", (Long)(rt.get(MybankConstants.REQUEST_TIME)) - (Long)(rt.get(MybankConstants.PREPARE_TIME)));
            }
        }
        T tRsp = null;
        try {
            tRsp = parser.parse((String)rt.get(MybankConstants.RSP_CONTENT));
            tRsp.setXmlContent((String)rt.get(MybankConstants.RSP_CONTENT));
            tRsp.setRequestContent((String)rt.get("requestContent"));

            checkResponseSign((String)rt.get(MybankConstants.RSP_CONTENT));

            if (costTimeMap.containsKey(MybankConstants.REQUEST_COST_TIME)) {
                costTimeMap.put("postCostTime", System.currentTimeMillis() - (Long)(rt.get(MybankConstants.REQUEST_TIME)));
            }
        } catch (RuntimeException e) {
            MybankLogger.logBizError((String)rt.get(MybankConstants.RSP_CONTENT), costTimeMap);
            throw new MybankApiException(e);
        } catch (MybankApiException e) {
            MybankLogger.logBizError((String)rt.get(MybankConstants.RSP_CONTENT), costTimeMap);
            throw e;
        }
        MybankLogger.logBizSummary(rt, costTimeMap);
        return tRsp;
    }

    private <T extends MybankResponse> Map<String, Object> doPost(MybankRequest<T> request) throws MybankApiException {
        Map<String, Object> result = new HashMap<String, Object>();
        String xmlContent = null;
        try {
            xmlContent = request.xmlBuild();
        }catch (MybankApiException e){
            MybankLogger.logBizError(e);
            throw new MybankApiException(e);
        }
        if (request.isNeedSign()){
            try {
                xmlContent = getSigner().sign(xmlContent, this.charset, this.signType);
            }catch (MybankApiException e){
                MybankLogger.logBizError(e);
                throw new MybankApiException(e);
            }
        }
        result.put("requestContent", xmlContent);
        result.put("prepareTime", System.currentTimeMillis());

        String rsp = null;
        try {
//            System.out.println("======> 请求地址："+this.serverUrl);
//            System.out.println("======> xml请求报文："+xmlContent);
            logger.info(">>>>>>>>> 网商接口 请求地址："+this.serverUrl);
            logger.info(">>>>>>>>> 网商接口 请求报文："+xmlContent);
            rsp = WebUtils.doPost(this.serverUrl, xmlContent, request.getCookieValue(), this.charset, this.connectTimeout,
            this.readTimeout, request.getApiFunction(), request.getApiVersion());
//            System.out.println("======> xml返回报文："+rsp);
            logger.info(">>>>>>>>> 网商接口 返回报文："+rsp);
        } catch (MybankApiException e) {
            logger.error(">>>>>>>>> 网商接口 请求地址："+this.serverUrl);
            logger.error(">>>>>>>>> 网商接口 请求报文："+xmlContent);
            logger.error(">>>>>>>>> 网商接口 返回报文 异常："+e.getMessage());
            throw new MybankApiException(e);
        }
        result.put("requestTime", System.currentTimeMillis());
        result.put("rspContent", rsp);
        result.put("url", this.serverUrl);
        return result;
    }

    private <T extends MybankResponse> Map<String, Object> doPost(MybankUploadRequest<T> request) throws MybankApiException {
        Map<String, Object> result = new HashMap<String, Object>();
        Map<String, Object> map = null;
        try {
            map = request.getMapByModel();
        }catch (MybankApiException e){
            MybankLogger.logBizError(e);
            throw new MybankApiException(MybankApiExceptionEnum.SERVER_SYSTEM_EXCEPTION,e);
        }

        String signString = request.uploadRequestSignString(map);

        try {
            signString = getSigner().webSign(signString, this.charset, this.signType);
        }catch (MybankApiException e){
            MybankLogger.logBizError(e);
            throw new MybankApiException(MybankApiExceptionEnum.SERVER_SYSTEM_EXCEPTION,e);
        }

        if(request instanceof BkcloudfundsOssFileUploadRequest){
            BkcloudfundsOssFileUploadRequest fileUploadRequest = (BkcloudfundsOssFileUploadRequest) request;
            fileUploadRequest.getRequestModel().setSignature(signString);
        }
        if (request instanceof MerchantprodMerchantUploadphotoRequest){
            MerchantprodMerchantUploadphotoRequest uploadphotoRequest = (MerchantprodMerchantUploadphotoRequest) request;
            uploadphotoRequest.getUploadphotoRequestModel().setSignature(signString);
        }

        map.put("Signature", signString);
        HttpEntity httpEntity = request.entityBuilder(map);
        ByteArrayOutputStream out = new ByteArrayOutputStream((int) httpEntity.getContentLength());
        try {
            httpEntity.writeTo(out);
            result.put("requestContent", out.toString());
        }catch (IOException e){
            MybankLogger.logBizError(e);
            throw new MybankApiException(MybankApiExceptionEnum.SERVER_SYSTEM_EXCEPTION,e);
        }finally{
            try {
                out.close();
            }catch (IOException e){
                MybankLogger.logBizError(e);
                throw new MybankApiException(MybankApiExceptionEnum.SERVER_SYSTEM_EXCEPTION,e);
            }
        }
        result.put("prepareTime", System.currentTimeMillis());

        String rsp = null;
        try {
            rsp = HttpsUtil.httpPost(this.serverUrl, httpEntity, this.charset, request.getApiFunction(), request.getApiVersion());
        } catch (MybankApiException e) {
            throw new MybankApiException(MybankApiExceptionEnum.REQUEST_REMOTE_SERVER_ERROR, e);
        }
        result.put("requestTime", System.currentTimeMillis());
        result.put("rspContent", rsp);
        result.put("url", this.serverUrl);
        return result;
    }

    private void checkResponseSign(String rspContent) throws MybankApiException {
        boolean checkResult = getSignChecker().check(rspContent, this.charset, this.signType);
        if (!checkResult) {
            throw new MybankApiException(MybankApiExceptionEnum.VERIFY_FAIL);
        }
    }

    void setServerUrl(String serverUrl) { this.serverUrl = serverUrl; }

    void setFormat(String format) { this.format = format; }

    void setSignType(String signType) { this.signType = signType; }

    void setCharset(String charset) { this.charset = charset; }

    void setConnectTimeout(int connectTimeout) { this.connectTimeout = connectTimeout; }

    void setReadTimeout(int readTimeout) { this.readTimeout = readTimeout; }

    abstract Signer getSigner();

    abstract SignChecker getSignChecker();
}