package com.sdtool.common.tool;

import org.apache.commons.lang3.StringUtils;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringWriter;
import java.lang.reflect.Field;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.*;

/**
 * Created by jlutt on 2019-05-31.
 *
 * @author jlutt
 */
public class InvokeSignature {

  /**
   * 获取所有Fields,包含父类field
   *
   * @param clazz clazz
   * @return list
   */
  private static List<Field> getAllFields(Class<?> clazz) {
    if (!clazz.equals(Object.class)) {
      List<Field> fields = new ArrayList<>(Arrays.asList(clazz.getDeclaredFields()));
      List<Field> fields2 = getAllFields(clazz.getSuperclass());
      if (fields2 != null) {
        fields.addAll(fields2);
      }
      return fields;
    } else {
      return null;
    }
  }

  private static PublicKey getPublicKeyFromX509(String algorithm,
                                               InputStream ins) throws Exception {
    KeyFactory keyFactory = KeyFactory.getInstance(algorithm);

    StringWriter writer = new StringWriter();
    StreamUtil.io(new InputStreamReader(ins), writer);

    byte[] encodedKey = writer.toString().getBytes();

    encodedKey = Base64.decodeBase64(encodedKey);

    return keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey));
  }

  private static PrivateKey getPrivateKeyFromPKCS8(String algorithm,
                                                  InputStream ins) throws Exception {
    if (ins == null || StringUtils.isEmpty(algorithm)) {
      return null;
    }

    KeyFactory keyFactory = KeyFactory.getInstance(algorithm);

    byte[] encodedKey = StreamUtil.readText(ins).getBytes();

    encodedKey = Base64.decodeBase64(encodedKey);

    return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(encodedKey));
  }

  public static String getSignCheckContent(Map<String, String> params) {
    if (params == null) {
      return null;
    }

    params.remove("sign");
    params.remove("sign_type");

    StringBuffer content = new StringBuffer();
    List<String> keys = new ArrayList<>(params.keySet());
    Collections.sort(keys);

    for (int i = 0; i < keys.size(); i++) {
      String key = keys.get(i);
      String value = params.get(key);
      if (StringUtils.isNoneEmpty(key, value)) {
        content.append((i == 0 ? "" : "&") + key + "=" + value);
      }
    }

    return content.toString();
  }


  /**
   * 验证签名
   *
   * @param params
   * @param publicKey
   * @param charset
   * @return
   */
  public static boolean rsaCheck(Map<String, String> params,
                                 String publicKey,
                                 String charset) {
    String sign = params.get("sign");
    String content = getSignCheckContent(params);

    return rsa256CheckContent(content, sign, publicKey, charset);
  }

  public static String getSignContent(Map<String, String> sortedParams) {
    StringBuffer content = new StringBuffer();
    List<String> keys = new ArrayList<>(sortedParams.keySet());
    Collections.sort(keys);
    int index = 0;
    for (String key : keys) {
      String value = sortedParams.get(key);
      if (StringUtils.isNoneEmpty(key, value)) {
        content.append((index == 0 ? "" : "&") + key + "=" + value);
        index++;
      }
    }
    return content.toString();
  }

  /**
   * rsa内容签名
   *
   * @param content
   * @param privateKey
   * @param charset
   * @return
   */
  public static String rsaSign(String content,
                               String privateKey,
                               String charset) {
    return rsa256Sign(content, privateKey, charset);
  }

  /**
   * 转换对象为map
   *
   * @param object object
   * @param ignore ignore
   * @return map
   */
  public static Map<String, String> objectToMap(Object object, String... ignore) {
    Map<String, String> tempMap = new LinkedHashMap<>();
    for (Field f : getAllFields(object.getClass())) {
      if (!f.isAccessible()) {
        f.setAccessible(true);
      }
      boolean ig = false;
      if (ignore != null && ignore.length > 0) {
        for (String i : ignore) {
          if (i.equals(f.getName())) {
            ig = true;
            break;
          }
        }
      }
      if (ig) {
        continue;
      } else {
        Object o = null;
        try {
          o = f.get(object);
        } catch (IllegalArgumentException | IllegalAccessException e) {
          e.printStackTrace();
        }
        tempMap.put(f.getName(), o == null ? "" : o.toString());
      }
    }
    return tempMap;
  }

  private static boolean rsa256CheckContent(String content,
                                            String sign,
                                            String publicKey,
                                            String charset) {
    try {
      PublicKey pubKey = getPublicKeyFromX509("RSA",
          new ByteArrayInputStream(publicKey.getBytes()));

      java.security.Signature signature = java.security.Signature
          .getInstance("SHA256WithRSA");

      signature.initVerify(pubKey);

      if (StringUtils.isEmpty(charset)) {
        signature.update(content.getBytes());
      } else {
        signature.update(content.getBytes(charset));
      }

      return signature.verify(Base64.decodeBase64(sign.getBytes()));
    } catch (Exception e) {
      throw new RuntimeException(
          "RSAcontent = " + content + ",sign=" + sign + ",charset = " + charset, e);
    }
  }

  private static String rsa256Sign(String content,
                                   String privateKey,
                                   String charset) {

    try {
      PrivateKey priKey = getPrivateKeyFromPKCS8("RSA",
          new ByteArrayInputStream(privateKey.getBytes()));

      java.security.Signature signature = java.security.Signature
          .getInstance("SHA256WithRSA");

      signature.initSign(priKey);

      if (StringUtils.isEmpty(charset)) {
        signature.update(content.getBytes());
      } else {
        signature.update(content.getBytes(charset));
      }

      byte[] signed = signature.sign();

      return new String(Base64.encodeBase64(signed));
    } catch (Exception e) {
      throw new RuntimeException("RSAcontent = " + content + "; charset = " + charset, e);
    }

  }
}
