/*
 * Copyright (c) 2012. HappyDroids LLC, All rights reserved.
 */

package net.kencochrane.sentry;

import org.apach3.commons.lang3.time.DateFormatUtils;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.security.SignatureException;
import java.util.Date;
import java.util.UUID;
import java.util.zip.CRC32;
import java.util.zip.Checksum;
import java.util.zip.GZIPOutputStream;

import static org.apach3.commons.codec.binary.Base64.encodeBase64String;

/**
 * User: ken Cochrane
 * Date: 2/10/12
 * Time: 10:43 AM
 * Utility class for the Raven client.
 */
public class RavenUtils {

  private static final String HMAC_SHA1_ALGORITHM = "HmacSHA1";

  /**
   * Computes RFC 2104-compliant HMAC signature.
   * Based off of the sample here. http://docs.amazonwebservices.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/AuthJavaSampleHMACSignature.html
   *
   * @param data The data to be signed.
   * @param key  The signing key.
   * @return The hex-encoded RFC 2104-compliant HMAC signature.
   * @throws java.security.SignatureException
   *          when signature generation fails
   */
  public static String calculateHMAC(String data, String key) throws java.security.SignatureException {
    String result;
    try {

      // get an hmac_sha1 key from the raw key bytes
      SecretKeySpec signingKey = new SecretKeySpec(key.getBytes(), HMAC_SHA1_ALGORITHM);

      // get an hmac_sha1 Mac instance and initialize with the signing key
      Mac mac = Mac.getInstance(HMAC_SHA1_ALGORITHM);
      mac.init(signingKey);

      // compute the hmac on input data bytes
      byte[] rawHmac = mac.doFinal(data.getBytes());

      result = hexEncode(rawHmac);

    } catch (Exception e) {
      throw new SignatureException("Failed to generate HMAC : " + e.getMessage());
    }
    return result;
  }

  /**
   * Get the hostname for the system throwing the error.
   *
   * @return The hostname for the server
   */
  public static String getHostname() {
    String hostname;
    try {
      hostname = InetAddress.getLocalHost().getCanonicalHostName();
    } catch (UnknownHostException e) {
      // can't get hostname
      hostname = "unavailable";
    }
    return hostname;
  }

  /**
   * Convert a java date into the ISO8601 format YYYYMMDDTHH:mm:ss
   * in timezone UTC
   *
   * @param date the date to convert
   * @return ISO8601 formatted date as a String
   */
  public static String getDateAsISO8601String(Date date) {
    return DateFormatUtils.formatUTC(date, DateFormatUtils.ISO_DATETIME_FORMAT.getPattern());
  }

  /**
   * Get the timestamp for right now as a long
   *
   * @return timestamp for now as long
   */
  public static long getTimestampLong() {
    return System.currentTimeMillis();
  }

  /**
   * Given a long timestamp convert to a ISO8601 formatted date string
   *
   * @param timestamp the timestamp to convert
   * @return ISO8601 formatted date string
   */
  public static String getTimestampString(long timestamp) {

    java.util.Date date = new java.util.Date(timestamp);
    return getDateAsISO8601String(date);
  }

  /**
   * Given the time right now return a ISO8601 formatted date string
   *
   * @return ISO8601 formatted date string
   */
  public static String getTimestampString() {

    java.util.Date date = new java.util.Date();
    return getDateAsISO8601String(date);
  }

  /**
   * build the HMAC sentry signature
   * <p/>
   * The header is composed of a SHA1-signed HMAC, the timestamp from when the message was generated,
   * and an arbitrary client version string.
   * <p/>
   * The client version should be something distinct to your client, and is simply for reporting purposes.
   * To generate the HMAC signature, take the following example (in Python):
   * <p/>
   * hmac.new(public_key, '%s %s' % (timestamp, message), hashlib.sha1).hexdigest()
   *
   * @param message   the error message to send to sentry
   * @param timestamp the timestamp for when the message was created
   * @param key       sentry public key
   * @return SHA1-signed HMAC string
   */
  public static String getSignature(String message, long timestamp, String key) {
    String full_message = timestamp + " " + message;
    String hmac = null;
    try {
      hmac = calculateHMAC(full_message, key);

    } catch (SignatureException e) {
      e.printStackTrace();
    }
    return hmac;
  }


  /**
   * The byte[] returned by MessageDigest does not have a nice
   * textual representation, so some form of encoding is usually performed.
   * <p/>
   * This implementation follows the example of David Flanagan's book
   * "Java In A Nutshell", and converts a byte array into a String
   * of hex characters.
   * <p/>
   * Another popular alternative is to use a "Base64" encoding.
   *
   * @param aInput what we are hex encoding
   * @return hex encoded string.
   */
  public static String hexEncode(byte[] aInput) {
    StringBuilder result = new StringBuilder();
    char[] digits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
    for (byte b : aInput) {
      result.append(digits[(b & 0xf0) >> 4]);
      result.append(digits[b & 0x0f]);
    }
    return result.toString();
  }

  /**
   * An almost-unique hash identifying the this event to improve aggregation.
   *
   * @param message The message we are sending to sentry
   * @return CRC32 Checksum string
   */
  public static String calculateChecksum(String message) {

    // get bytes from string
    byte bytes[] = message.getBytes();

    Checksum checksum = new CRC32();

    // update the current checksum with the specified array of bytes
    checksum.update(bytes, 0, bytes.length);

    // get the current checksum value
    long checksumValue = checksum.getValue();
    return String.valueOf(checksumValue);
  }

  /**
   * Hexadecimal string representing a uuid4 value.
   *
   * @return Hexadecimal UUID4 String
   */
  public static String getRandomUUID() {
    UUID uuid = UUID.randomUUID();
    String uuid_string = uuid.toString();
    // if we keep the -'s in the uuid, it is too long, remove them
    uuid_string = uuid_string.replaceAll("-", "");
    return uuid_string;
  }

  /**
   * Gzip then base64 encode the str value
   *
   * @param str the value we want to compress and encode.
   * @return Base64 encoded compressed version of the string passed in.
   */
  public static String compressAndEncode(String str) {
    if (str == null || str.length() == 0) {
      return str;
    }
    ByteArrayOutputStream out = new ByteArrayOutputStream();
    GZIPOutputStream gzip;
    try {
      gzip = new GZIPOutputStream(out);

      gzip.write(str.getBytes());
      gzip.close();
    } catch (IOException e) {
      e.printStackTrace();
      //todo do something here better then this.
    }
    return encodeBase64String(out.toByteArray());
  }
}
