
using System;

using System.Collections.Generic;
using System.Collections.Specialized;
using System.Globalization;
using System.Linq;

using System.Security.Cryptography;
using System.Text;
using System.Web;
using Mono.Web;
using NeoSmart.Utils;

namespace AmazonPayHelpers
{
    public class AmazonPaySigningHelper
    {
        public const string Iso8601DateTimeFormat = "yyyyMMddTHHmmssZ";
        public const string Iso8601DateFormat = "yyyyMMdd";

        private readonly string _awsSecretKey;
        private readonly string _service;
        private readonly string _region;
        private readonly String _encryptionDateTime;

        public AmazonPaySigningHelper(string awsSecretKey, String encryptionDateTime, string service = null, string region = null)
        {
            _awsSecretKey = awsSecretKey;
            _service = service ?? "AmazonPay";
            _region = region ?? "eu-west-1";
            _encryptionDateTime = encryptionDateTime;
        }

        public string GetSignature(string httpMethod, string urlWithoutProtocol, Dictionary<string, string> body, Dictionary<string, string> headers)
        {
            if (httpMethod== "POST")
            {
                string canonicalRequest = GetCanonicalRequestPost(httpMethod, urlWithoutProtocol, body, headers);
                Console.WriteLine("Canonical Request - " + Environment.NewLine + canonicalRequest + Environment.NewLine);
                string stringToSign = GetStringToSign(canonicalRequest);
                Console.WriteLine("String to Sign :" + Environment.NewLine + stringToSign + Environment.NewLine);
                return GetSignature(stringToSign);
            }
            else
            {
                string canonicalRequest = GetCanonicalRequestGET(httpMethod, urlWithoutProtocol, body, headers);
                Console.WriteLine("Canonical Request - " + Environment.NewLine + canonicalRequest + Environment.NewLine);
                string stringToSign = GetStringToSign(canonicalRequest);
                Console.WriteLine("String to Sign :" + Environment.NewLine + stringToSign + Environment.NewLine);
                return GetSignature(stringToSign);
            }

            
        }




        private static string GetCanonicalRequestGET(string httpMethod, string urlWithoutProtocol, Dictionary<string, string> body, Dictionary<string, string> headers)
        {
            var canonicalRequest = new StringBuilder();
            canonicalRequest.AppendFormat("{0}\n", httpMethod);
            canonicalRequest.AppendFormat("{0}\n", urlWithoutProtocol);


            if (body != null && body.Count > 0)
            {
                IOrderedEnumerable<KeyValuePair<string, string>> orderedParams = body.OrderBy(key => key.Key);

                foreach (KeyValuePair<string, string> data in orderedParams)
                {
                    canonicalRequest.AppendFormat("{0}={1}&", HttpUtility.HtmlEncode(data.Key), HttpUtility.HtmlEncode(data.Value));
                }
            }


            if (canonicalRequest.Length > 0)
                canonicalRequest.Remove(canonicalRequest.Length - 1, 1);

            canonicalRequest.AppendFormat("\n");

            if (headers != null && headers.Count > 0)
            {
                IOrderedEnumerable<KeyValuePair<string, string>> orderedParams = headers.OrderBy(key => key.Key);

                foreach (KeyValuePair<string, string> data in orderedParams)
                {
                    canonicalRequest.AppendFormat("{0}={1}&", HttpUtility.HtmlEncode(data.Key), HttpUtility.HtmlEncode(data.Value));
                }
            }


            // remove trailing '&'
            if (canonicalRequest.Length > 0)
                canonicalRequest.Remove(canonicalRequest.Length - 1, 1);

            canonicalRequest.AppendFormat("\n");

            Console.WriteLine("Returning canonical request=", canonicalRequest.ToString());

            return canonicalRequest.ToString();
        }

        private static string GetCanonicalRequestPost(string httpMethod, string urlWithoutProtocol, Dictionary<string, string> body, Dictionary<string, string> headers)
        {
            var canonicalRequest = new StringBuilder();
            canonicalRequest.AppendFormat("{0}\n", httpMethod);
            canonicalRequest.AppendFormat("{0}\n", urlWithoutProtocol);

            canonicalRequest.AppendFormat("\n");
            if (headers != null && headers.Count > 0)
            {
                IOrderedEnumerable<KeyValuePair<string, string>> orderedParams = headers.OrderBy(key => key.Key);

                foreach (KeyValuePair<string, string> data in orderedParams)
                {
                    canonicalRequest.AppendFormat("{0}={1}&", HttpUtility.HtmlEncode(data.Key), HttpUtility.HtmlEncode(data.Value));
                }
            }


            if (canonicalRequest.Length > 0)
                canonicalRequest.Remove(canonicalRequest.Length - 1, 1);

            canonicalRequest.AppendFormat("\n");

            if (body != null && body.Count > 0)
            {
                IOrderedEnumerable<KeyValuePair<string, string>> orderedParams = body.OrderBy(key => key.Key);

                foreach (KeyValuePair<string, string> data in orderedParams)
                {
                    canonicalRequest.AppendFormat("{0}={1}&", HttpUtility.HtmlEncode(data.Key), HttpUtility.HtmlEncode(data.Value));
                }
            }

            // remove trailing '&'
            if (canonicalRequest.Length > 0)
                canonicalRequest.Remove(canonicalRequest.Length - 1, 1);

            Console.WriteLine("Returning canonical request=", canonicalRequest.ToString());

            return canonicalRequest.ToString();
        }

        private string GetStringToSign(string canonicalRequest)
        {
            String dateStamp = _encryptionDateTime.Split('T')[0];
            Console.WriteLine("This one"+dateStamp);

            var scope = string.Format("{0}/{1}/{2}/{3}", dateStamp, _region, _service, "aws4_request");

            var stringToSign = new StringBuilder();

            stringToSign.AppendFormat("AWS4-HMAC-SHA384\n{0}\n{1}\n",
                                      _encryptionDateTime,
                                      scope);

            //stringToSign.Append(Utils.ToHex(Utils.Hash(canonicalRequest)));
            //stringToSign.Append(getHashedCanonicalRequests((canonicalRequest)));
            stringToSign.Append(getHashedCanonicalRequest(canonicalRequest));

            return stringToSign.ToString();
        }

        public string getHashedCanonicalRequest(string canonicalRequest)
        {
            string hasedcanoincalRequest = HexEncode(Hash(ToBytes(canonicalRequest)));
            return hasedcanoincalRequest;
        }


        private static byte[] ToBytes(string str)
        {
            return Encoding.UTF8.GetBytes(str.ToCharArray());
        }

        private static string HexEncode(byte[] bytes)
        {
            return BitConverter.ToString(bytes).Replace("-", string.Empty).ToLowerInvariant();
        }


        private static byte[] Hash(byte[] bytes)
        {
            return SHA384.Create().ComputeHash(bytes);
        } 


        

        private string GetSignature(string stringToSign)
        {
            var kSigning = GetSigningKey();
            return UrlBase64.Encode(Utils.GetKeyedHash(kSigning, stringToSign));
        }

        private byte[] GetSigningKey()
        {
            String dateStamp = _encryptionDateTime.Split('T')[0];
            var kDate = Utils.GetKeyedHash("AWS4" + _awsSecretKey, dateStamp);
            var kRegion = Utils.GetKeyedHash(kDate, _region);
            var kService = Utils.GetKeyedHash(kRegion, _service);
            return Utils.GetKeyedHash(kService, "aws4_request");
        }

        private static string getHashedCanonicalRequests(string canonicalRequest)
        {
            // Create a SHA384   

            StringBuilder Sb = new StringBuilder();
            using (SHA384 hash = SHA384Managed.Create())
            {
                Encoding enc = Encoding.UTF8;
                Byte[] result = hash.ComputeHash(enc.GetBytes(canonicalRequest));
                string str = BitConverter.ToString(result).Replace("-", String.Empty);
                foreach (Byte b in result)
                    Sb.Append(b.ToString("x2"));
            }
            return Sb.ToString();

        } 

        private static class Utils
        {
            private const string ValidUrlCharacters =
                "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~";

            public static string UrlEncode(string data)
            {
                StringBuilder encoded = new StringBuilder();
                foreach (char symbol in Encoding.UTF8.GetBytes(data))
                {
                    if (ValidUrlCharacters.IndexOf(symbol) != -1)
                    {
                        encoded.Append(symbol);
                    }
                    else
                    {
                        encoded.Append("%").Append(string.Format(CultureInfo.InvariantCulture, "{0:X2}", (int)symbol));
                    }
                }
                return encoded.ToString();
            }

            public static byte[] Sha384Hash(string value)
            {
                using (SHA384 sha384 = SHA384.Create())
                {
                    byte[] bytes = sha384.ComputeHash(Encoding.UTF8.GetBytes(value));
                    return bytes;
                }
            }

            public static byte[] Hash(string value)
            {
                return new SHA384CryptoServiceProvider().ComputeHash(Encoding.UTF8.GetBytes(value));
            }

            public static byte[] GetKeyedHash(string key, string value)
            {
                return GetKeyedHash(Encoding.UTF8.GetBytes(key), value);
            }

            public static byte[] GetKeyedHash(byte[] key, string value)
            {
                KeyedHashAlgorithm mac = new HMACSHA384(key);
                mac.Initialize();
                return mac.ComputeHash(Encoding.UTF8.GetBytes(value));
            }

            public static string ToHex(byte[] data)
            {
                StringBuilder sb = new StringBuilder();
                for (int i = 0; i < data.Length; i++)
                {
                    sb.Append(data[i].ToString("x2", CultureInfo.InvariantCulture));
                }
                return sb.ToString();
            }
        }
    }
}