package com.friday.custody.service.trustService.yinhe.utils;

import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.JSONPath;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.friday.custody.service.trustService.yinhe.common.YHContants;
import com.friday.custody.dto.TenantEscrowAuthorizationInfoDto;
import com.friday.custody.dto.YinheAppSecretDTO;
import com.friday.custody.entity.InvokeRecordEntity;
import com.friday.custody.service.basicService.impl.InvokeRecordServiceImpl;
import com.friday.custody.service.basicService.impl.PullDataFailureLogServiceImpl;
import com.friday.custody.utils.PullDataFailureUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import sun.security.jca.ProviderList;
import sun.security.jca.Providers;

import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
import java.util.Base64;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ThreadLocalRandom;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author vdi.zhoul
 * @description: java api调用案例 只利用jdk的相关类实现
 * @date 2021/9/13 17:58
 */
@Slf4j
@Service
public class FmpUtils {

    private static HashMap<String,HashMap<String,Object>> TOKEN_MAP = new HashMap();
    @Autowired
    private InvokeRecordServiceImpl invokeRecordService;
    @Autowired
    private PullDataFailureLogServiceImpl pullDataFailureLogService;

    private ObjectMapper objectMapper = new ObjectMapper();
    public  JSONObject sendMessage(TenantEscrowAuthorizationInfoDto tenant, String apiUrl, JSONObject params) {
        JSONObject result = null;
        String tenantAppSecret = tenant.getAppSecret();
        if (ObjectUtil.isEmpty(tenantAppSecret)){
            return result;
        }
        YinheAppSecretDTO yinheAppSecretDTO = new YinheAppSecretDTO();
        try {
            yinheAppSecretDTO = objectMapper.readValue(tenantAppSecret, YinheAppSecretDTO.class);
        } catch (IOException e) {
            log.error("解析托管账密{}失败!{}",yinheAppSecretDTO, e);
        }
        String appId = yinheAppSecretDTO.getAppId();
        String secretKey =yinheAppSecretDTO.getSecKey();
        //获取token
        getAccessToken(tenant);
        String signMsg = "";
        String path = "";
        String requestSn = randomString(YHContants.CHARS, 24);
        String timestamp = String.valueOf(System.currentTimeMillis());
        String body = params.toJSONString();
        String apiPath = YHContants.API_HOST + apiUrl;
        try {
            URL url = new URL(apiPath);
            path = url.getPath();
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
       /** 构成签名字符串 构成规则如下 （请求方法类型+换行+请求路径+换行+随机数+换行+时间戳+换行+请求体+换行）
         * 签名原始字符串 = HTTP Method + ‘\n’ + HTTP URI + ‘\n’ + requestSn + ‘\n’
         * timestamp + ‘\n’ + Request Body + ‘\n’ */

        signMsg = "POST" + "\n" + path + "\n" + requestSn + "\n" + timestamp + "\n" + body + "\n";
        Map<String, String> headers = new HashMap<>(16);
        //生成一个签名sign
        headers.put("X-HMAC-SIGNATURE", hmacSha256(signMsg, secretKey));
        headers.put("Content-Type", "application/json;charset=UTF-8");
        headers.put("X-HMAC-ALGORITHM", "hmac-sha256");
        headers.put("X-HMAC-ACCESS-KEY", "user-key");
        headers.put("Access-Token", String.valueOf(TOKEN_MAP.get(tenant.getTenantName()).get("accessToken")));
        headers.put("timestamp", timestamp);
        //headers.put("Host", "fmp-moneytransfer.tsa2.chinastock.com.cn");
        headers.put("Request-Sn", requestSn);
        headers.put("appId", appId);

        String resultStr = null;

        String errorMsg = null;
        try {
            resultStr = sendPost(apiPath, headers, body);
        } catch (IOException e) {
            log.error("银河获得连接失败!", e);
            errorMsg = e.getMessage();
        }catch (Exception e){
            log.error("调用银河上游接口失败! admin ={}, api={}, params ={}",tenant.getTenantName(), apiUrl, params, e);
            errorMsg = e.getMessage();
        }
        result = JSONObject.parseObject(resultStr);
        log.info("调用银河证券接口:{}, params:{}, result：{}", apiPath, params, result);
        if (!ObjectUtils.isEmpty(result) && JSONPath.contains(result, "$.meta.code")){
            String code = JSONPath.extract(JSONObject.toJSONString(result), "$.meta.code").toString();
            if (YHContants.SUCCESS_CODE .equals(code)) {
                saveInvokeRecord(tenant, tenant.getTrustNo(), apiUrl, JSONObject.toJSONString(headers), params, result);
                return result;
            }
        }
        if (StringUtils.isEmpty(errorMsg)){
            errorMsg = resultStr;
        }
        pullDataFailureLogService.saveErrorData(tenant.getTenantName(),
                PullDataFailureUtil.createErrorMap(tenant.getTrustNo(), apiPath, JSONObject.toJSONString(headers), JSONObject.toJSONString(params), errorMsg));
        return result;
    }

    /**
     * 获取token token有效期两个小时
     * @return
     */
    private void getAccessToken(TenantEscrowAuthorizationInfoDto tenant) {
        //先从缓存中获取token
        HashMap<String, Object> userToken = TOKEN_MAP.get(tenant.getTenantName());
        if (!ObjectUtils.isEmpty(userToken) && !ObjectUtils.isEmpty(userToken.get("accessToken"))) {
            Long currentTime = System.currentTimeMillis();
            //token持有时间
            Long holdingTime = (currentTime - Long.parseLong(userToken.get("tokenTime").toString()));
            if (holdingTime >= 3600 * 2 * 1000) {
                synchronized (this) {
                    //获取新token
                    login(tenant);
                }
            }
        }else {
            synchronized (this) {
                //获取新token
                login(tenant);
            }
        }
    }
    public void main(String[] args) {

        //获取accessToken信息
//        String accessToken = login(null);

//        System.out.println("[accessToken]:" + accessToken);
//
//        //调用业务接口
//        String serviceResult = doService(accessToken);
//
//        System.out.println("[serviceResult]:" + serviceResult);
    }
    /**
     * 调用登录接口获取 token
     *
     * @return
     */
    private Map<String, Object> login(TenantEscrowAuthorizationInfoDto tenant) {
        String tenantAppSecret = tenant.getAppSecret();
        if (ObjectUtil.isEmpty(tenantAppSecret)){
            return new HashMap<>();
        }
        YinheAppSecretDTO yinheAppSecretDTO = new YinheAppSecretDTO();
        try {
            yinheAppSecretDTO = objectMapper.readValue(tenantAppSecret, YinheAppSecretDTO.class);
        } catch (IOException e) {
            log.error("解析托管账密{}失败!{}",yinheAppSecretDTO, e);
        }
        String appId = yinheAppSecretDTO.getAppId();
        String appKey = yinheAppSecretDTO.getAppKey();
        String serviceName = yinheAppSecretDTO.getServiceName();
        Map<String, String> headers = new HashMap<>(8);
        headers.put("Content-Type", "application/json;charset=UTF-8");
        long tokenTime = System.currentTimeMillis();
        headers.put("timestamp", String.valueOf(tokenTime));
        String randomStr = randomString(YHContants.CHARS, 24);
        headers.put("Request-Sn", randomStr);
        String body = "{\"appId\":\"" + appId + "\",\"appKey\":\"" + appKey + "\",\"serviceName\":\"" + serviceName + "\" }";
        String accessToken = null;
        HashMap<String, Object> tokenMap = new HashMap<>();
        try {
            String result = sendPost(YHContants.API_HOST + YHContants.TOEKN_URL, headers, body);
            log.info("tenant = {}, 调用/login返回参数 = {}", tenant.getTenantName(), result);
            String regStr = "(\"accessToken\":\")([\\w.-]+)\"";
            Matcher matcher = Pattern.compile(regStr).matcher(result);
            while (matcher.find()) {
                accessToken = matcher.group(2);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        tokenMap.put("accessToken", accessToken);
        tokenMap.put("tokenTime", tokenTime);
        return TOKEN_MAP.put(tenant.getTenantName(), tokenMap);
    }
    public void saveInvokeRecord(TenantEscrowAuthorizationInfoDto tenant, String trustNo, String invokeType, String headers, Map<String, Object> params, Map<String, Object> invokeResult) {
        Date now = new Date();
        InvokeRecordEntity invokeRecordEntity = InvokeRecordEntity.builder()
                .trustNo(trustNo)
                .admin(tenant.getTenantName())
                .invokeType(invokeType)
                .reqHeader(JSON.toJSONString(headers))
                .reqParam(JSON.toJSONString(params))
                .invokeTime(now)
                .invokeResult(JSON.toJSONString(invokeResult))
                .queryKey(tenant.getTenantName() + "::" + trustNo + "::" + invokeType)
                .createTime(now)
                .updateTime(now)
                .build();
        invokeRecordService.save(invokeRecordEntity);
    }

    public static  final String SERVICE_URL = "";
    /**
     * 调用业务接口
     *
     * @param accessToken
     * @return
     */
    private static String doService(TenantEscrowAuthorizationInfoDto tenant, String accessToken) {
        Map<String,String> map = JSON.parseObject(tenant.getAppSecret(), Map.class);
        log.info("setPropertyFromJson设置银河证券托管信息为{}",JSON.toJSONString(map));
        String appId = tenant.getTenantName();
        String appKey = tenant.getAppKey();
        String secretKey = map.get("secKey");
        String serviceName = map.get("serviceName");
        String signMsg = "";
        String path = "";
        String requestSn = randomString(YHContants.CHARS, 24);
        String timestamp = String.valueOf(System.currentTimeMillis());

        //投资者份额余额查询
        String body = "{\r\n \"fundManagerRegNo\": \"P1000000\"" + "}";
        try {
            URL url = new URL(SERVICE_URL);
            path = url.getPath();
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
        /**
         * 构成签名字符串 构成规则如下 （请求方法类型+换行+请求路径+换行+随机数+换行+时间戳+换行+请求体+换行）
         * 签名原始字符串 = HTTP Method + ‘\n’ + HTTP URI + ‘\n’ + requestSn + ‘\n’
         * timestamp + ‘\n’ + Request Body + ‘\n’
         */
        signMsg = "POST" + "\n" + path + "\n" + requestSn + "\n" + timestamp + "\n" + body + "\n";
        Map<String, String> headers = new HashMap<>(16);
        //生成一个签名sign
        headers.put("X-HMAC-SIGNATURE", hmacSha256(signMsg, secretKey));
        headers.put("Content-Type", "application/json;charset=UTF-8");
        headers.put("X-HMAC-ALGORITHM", "hmac-sha256");
        headers.put("X-HMAC-ACCESS-KEY", "user-key");
        headers.put("Access-Token", accessToken);
        headers.put("timestamp", timestamp);
        //headers.put("Host", "fmp-moneytransfer.tsa2.chinastock.com.cn");
        headers.put("Request-Sn", requestSn);
        headers.put("appId", appId);

        String result = null;
        try {
            result = sendPost(SERVICE_URL, headers, body);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 发送post请求
     *
     * @param urlStr  url地址
     * @param headers 请求头
     * @param body    请求体
     * @return
     * @throws IOException
     */
    private static String sendPost(String urlStr, Map<String, String> headers, String body) throws IOException {
        StringBuffer stringBuffer;
        //封装路径信息
        URL url = new URL(urlStr);
        //建立连接
        URLConnection urlConnection = url.openConnection();
        //连接对象类型转换
        HttpURLConnection httpUrlConnection = (HttpURLConnection) urlConnection;
        try {
            if (headers != null && !headers.isEmpty()) {
                for (Map.Entry<String, String> entry : headers.entrySet()) {
                    httpUrlConnection.addRequestProperty(entry.getKey(), entry.getValue());
                }
            }
            //设定POST请求方法
            httpUrlConnection.setRequestMethod("POST");
            //开启post请求的输出功能
            httpUrlConnection.setDoOutput(true);
            //设置连接超时时间
            httpUrlConnection.setConnectTimeout(60 * 1000);
            //设置读取超时时间
            httpUrlConnection.setReadTimeout(30 * 1000);
            //写入body数据
            if (null != body) {
                httpUrlConnection.getOutputStream().write(body.getBytes());
            }
            int responseCode = httpUrlConnection.getResponseCode();
            System.out.println("[responseCode]：" + responseCode);
            //获取字节输入流信息
            InputStream inputStream;
            if (responseCode == 200) {
                inputStream = httpUrlConnection.getInputStream();
            } else {
                inputStream = httpUrlConnection.getErrorStream();
            }
            //字节流转换成字符流
            InputStreamReader inReader = new InputStreamReader(inputStream, YHContants.FILE_ENCODE);
            //把reader中的数据读取到缓冲的chars中
            char[] chars = new char[1 * 1024];
            int readCount;
            //用StringBuffer来接收char
            stringBuffer = new StringBuffer();
            while ((readCount = inReader.read(chars)) != -1) {
                stringBuffer.append(chars, 0, readCount);
            }
            return stringBuffer.toString();
        } catch (IOException e) {
            //异常时关闭连接
            if (null != httpUrlConnection) {
                httpUrlConnection.disconnect();
            }
            throw e;
        }finally {
            httpUrlConnection.disconnect();
        }
    }

    /**
     * 获得一个随机的字符串
     *
     * @param baseString 随机字符选取的样本
     * @param length     字符串的长度
     * @return 随机字符串
     */
    public static String randomString(String baseString, int length) {
        if (baseString == null || baseString.length() == 0) {
            return "";
        }
        final StringBuilder sb = new StringBuilder(length);

        if (length < 1) {
            length = 1;
        }
        int baseLength = baseString.length();
        while (sb.length() < length) {
            //利用ThreadLocalRandom提升多线程环境下的获取随机数的性能
            int number = ThreadLocalRandom.current().nextInt(baseLength);
            sb.append(baseString.charAt(number));
        }
        return sb.toString();
    }

    private static volatile Mac macInstance;

    /**
     * 按照 Hmac算法 生成一个SHA256加密字符串
     *
     * @param data 加密的字符串
     * @param key  加密的秘钥
     * @return 加密后的字符串
     * @throws Exception
     */
    public static String hmacSha256(String data, String key) {
        try {
            byte[] secKeyBytes = key.getBytes(YHContants.FILE_ENCODE);
            //根据给定的字节数组构造一个密钥,第二参数指定一个密钥算法的名称
            SecretKey secretKey = new SecretKeySpec(secKeyBytes, "HmacSHA256");
            //生成一个指定Mac算法的Mac对象
            macInstance = getMacInstance();
            //用给定密钥初始化Mac对象
            macInstance.init(secretKey);
            byte[] text = data.getBytes(YHContants.FILE_ENCODE);
            //完成 Mac操作 并用base64进行编码
            return Base64.getEncoder().encodeToString(macInstance.doFinal(text));
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        }
        return data;
    }

    /**
     * 获取一个单例的Mac（双端模式）
     *
     * @return
     */
    public static Mac getMacInstance() {
        if (macInstance == null) {
            synchronized (FmpUtils.class) {
                if (macInstance == null) {
                    try {
                        macInstance = Mac.getInstance("HmacSHA256");
                    } catch (NoSuchAlgorithmException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        return macInstance;
    }


    public void testProvider() {
        ProviderList list = Providers.getProviderList();
        for (Provider provider : list.providers()) {
            for (Object o : provider.keySet()) {
                if (o.toString().toUpperCase().indexOf("HMACSHA256") > -1) {
                    System.out.println(o);
                    System.out.println("[" + provider.getInfo() + "]");
                }
            }
        }
    }
}
