using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization.Json;
using System.IO;
using System.Web;
//using Newtonsoft.Json;
using Com.Bocom.OpenApi.Security.Digest;
using Com.Bocom.OpenApi.Utils;
using Kivii.Text;

namespace Com.Bocom.OpenApi
{
    public class DefaultBocomClient
    {
        protected string appId;
        protected string privateKey;
        protected string charset = BocomConstants.CHARSET_UTF8;
        protected string format = BocomConstants.FORMAT_JSON;
        protected string bocomPublicKey;
        protected string password;
        protected string encryptType = BocomConstants.ENCRYPT_TYPE_RSA_AND_AES;

        public DefaultBocomClient(string appId, string privateKey, string charset, string format,
            string bocomPublicKey, string passWord)
        {
            this.appId = appId;
            this.privateKey = privateKey;
            this.charset = charset;
            this.bocomPublicKey = bocomPublicKey;
            this.format = format;
            this.password = passWord;
        }
        
        public DefaultBocomClient(string appId, string privateKey, string charset, string format,
            string bocomPublicKey, string encryptType, string passWord)
        {
            this.appId = appId;
            this.privateKey = privateKey;
            this.charset = charset;
            this.bocomPublicKey = bocomPublicKey;
            this.format = format;
            this.encryptType = encryptType;
            this.password = passWord;
        }

        public DefaultBocomClient(string appId, string privateKey, string bocomPulicKey)
            : this(appId, privateKey, BocomConstants.CHARSET_UTF8, BocomConstants.FORMAT_JSON,
                bocomPulicKey, null)
        {
        }
        
        public DefaultBocomClient(string appId, string privateKey, string bocomPulicKey, string encryptType)
            : this(appId, privateKey, BocomConstants.CHARSET_UTF8, BocomConstants.FORMAT_JSON,
                bocomPulicKey, encryptType, null)
        {
        }


        public BocomResponse Execute<T>(BocomRequest<T> request) where T : BocomResponse
        {
            string uuid = Guid.NewGuid().ToString();
            string msgId = uuid.Replace("-", "");
            return Execute(request, msgId);
        }

        public BocomResponse Execute<T>(BocomRequest<T> request, string msgId) where T : BocomResponse
        {
            return Execute(request, msgId, "");
        }

        public BocomResponse Execute<T>(BocomRequest<T> request, string msgId, string appAuthToken)
            where T : BocomResponse
        {
            string encryptKey = null;
            string encryptType = this.encryptType;
            string encryptPolicy = null;

            if (request.IsNeedEncrypt())
            {
                if (request is BocomEncryptRequest<T>)
                {
                    encryptKey = ((BocomEncryptRequest<T>) request).GetEncryptKey();
                    encryptPolicy = ((BocomEncryptRequest<T>) request).GetEncryptPolicy();
                }
                else if (request is BocomUploadEncryptRequest<T>)
                {
                    encryptKey = ((BocomUploadEncryptRequest<T>) request).GetEncryptKey();
                    encryptPolicy = ((BocomUploadEncryptRequest<T>) request).GetEncryptPolicy();
                }
                else if (request is BocomDownloadEncryptRequest<T>)
                {
                    encryptKey = ((BocomDownloadEncryptRequest<T>) request).getEncryptKey();
                    encryptPolicy = ((BocomDownloadEncryptRequest<T>) request).getEncryptPolicy();
                }
            }


            Dictionary<string, string> param =
                PrepareParams(request, msgId, appAuthToken, encryptType, encryptKey, encryptPolicy);
            string respStr;
            if (request.GetMethod().Equals("POST"))
            {
                try
                {
                    if (request is BocomUploadRequest<T>)
                    {
                        Dictionary<string, FileItem> map = new Dictionary<string, FileItem>();
                        map.Add(BocomConstants.FILE_CONTENT, ((BocomUploadRequest<T>) request).GetFileItem());

                        respStr = WebUtils.GetResponseJson(request.GetServiceUrl(), param, charset,
                            ((BocomUploadRequest<T>) request).GetFileItem());
                    }
                    else if (request is BocomUploadEncryptRequest<T>)
                    {
                        Dictionary<string, FileItem> map = new Dictionary<string, FileItem>();
                        map.Add(BocomConstants.FILE_CONTENT, ((BocomUploadEncryptRequest<T>) request).GetFileItem());

                        respStr = WebUtils.GetResponseJson(request.GetServiceUrl(), param, charset,
                            ((BocomUploadEncryptRequest<T>) request).GetFileItem());
                    }
                    else if (request is BocomDownloadRequest<T>)
                    {
                        respStr = WebUtils.GetResponseJson(request.GetServiceUrl(), param, charset,
                            ((BocomDownloadRequest<T>) request).GetDownloadPath());
                    }
                    else if (request is BocomDownloadEncryptRequest<T>)
                    {
                        respStr = WebUtils.GetResponseJson(request.GetServiceUrl(), param, charset,
                            ((BocomDownloadEncryptRequest<T>) request).GetDownloadPath(), encryptKey, encryptType);
                    }
                    else
                    {
                        respStr = WebUtils.GetResponseJson(request.GetServiceUrl(), param, charset);
                    }
                }
                catch (IOException e)
                {
                    throw new BocomApiException(500, "api connect host err", e);
                }
            }
            else
            {
                throw new BocomApiException("only support POST, method: " + request.GetMethod());
            }

            BocomResponse response = ParseJsonWithBocomSign(request, respStr, encryptType, encryptKey);

            if (response == null)
            {
                throw new Exception("response is null.");
            }

            if (response is BocomDecryptResponse)
            {
                ((BocomDecryptResponse) response).setDecryptKey(((BocomEncryptRequest<T>) request).GetEncryptKey());
            }

            return response;
        }

        protected Dictionary<string, string> PrepareParams<T>(BocomRequest<T> request, string msgId,
            string appAuthToken, string encryptType, string encryptKey, string encryptPolicy) where T : BocomResponse
        {
            Console.WriteLine("PrepareParams---" + encryptKey);   
            string bizContentStr = BuildBizContentStr(request);
            string path = string.Empty;
            try
            {
                path = new Uri(request.GetServiceUrl()).LocalPath;
                path = path.Replace("/uat/", "/");
            }
            catch (UriFormatException e)
            {
                Console.WriteLine(e.Message);
            }

            Dictionary<string, string> param = new Dictionary<string, string>();

            Dictionary<string, string> extraParams = request.GetExtraParams();

            if (extraParams != null)
            {
                param = param.Union(extraParams).ToDictionary(k => k.Key, v => v.Value);
            }

            param.Add(BocomConstants.APP_ID, appId);
            Console.WriteLine("appId:" + appId);
            param.Add(BocomConstants.CHARSET, charset);
            param.Add(BocomConstants.FMT_TYPE, BocomConstants.FORMAT_JSON);
            param.Add(BocomConstants.APP_AUTH_TOKEN, appAuthToken);
            param.Add(BocomConstants.MSG_ID, msgId);
            param.Add(BocomConstants.TIMESTAMP, DateTime.Now.ToString(BocomConstants.DATE_TIME_FORMAT));
            Console.WriteLine("请求是否要加密:" + request.IsNeedEncrypt());
            if (request.IsNeedEncrypt())
            {
                if (encryptType == null || encryptKey == null || encryptPolicy == null)
                {
                    throw new ArgumentNullException(
                        "request need be encrypted, encrypt type, encrypt policy and encrypt key can not be null.");
                }

                string is_Encrypt = "true";
                /* if (request.isNeedEncrypt())
                 {
                     is_Encrypt = "true";
                 }*/
                param.Add(BocomConstants.IS_ENCRYPT, is_Encrypt);
            }
            else
            {
                param.Add(BocomConstants.BIZ_CONTENT_KEY, bizContentStr);
            }


            if (request is BocomEncryptRequest<T>)
            {
                //这里分报文和字段加密

                /*if(BocomConstants.ENCRYPT_POLICY_TYPE_CONTENT.Equals(encryptPolicy)&&bizContentStr != null) { //报文加密
                    Console.WriteLine("报文加密bizcontent：" + bizContentStr);
                    param.Add(BocomConstants.BIZ_CONTENT,
                        BocomEncrypt.encryptContent(bizContentStr, encryptType, encryptKey, charset));
                }*/
                if (BocomConstants.ENCRYPT_POLICY_TYPE_FIELD.Equals(encryptPolicy) && bizContentStr != null)
                {
                    //字段加密
                    param.Add(BocomConstants.BIZ_CONTENT, bizContentStr);
                }

                param.Add(BocomConstants.ENCRYPT_POLICY, encryptPolicy);
                // 用公钥对对称密钥进行加密
                param.Add(BocomConstants.ENCRYPT_KEY,
                    BocomEncrypt.AsymmetricalEncrypt(encryptKey, this.encryptType, bocomPublicKey, charset));
            }


            if (request is BocomUploadRequest<T>)
            {
                //如果是上传文件请求但不需要加密的话，就只需提取文件的哈希码，不需要进行其他操作
                FileItem f = ((BocomUploadRequest<T>) request).GetFileItem();
                Console.WriteLine("文件没有加密");
                string fileName = f.GetFileName();
                param.Add(BocomConstants.FILE_HASHCODE,
                    SHA256Digest.Digest(fileName));
            }

            if (request is BocomUploadEncryptRequest<T>)
            {
                //如果是上传文件请求 并且需要加密的话，先提取文件哈希码 存储到params，然后将文件内容加密，然后用密文替换fileItem的content
                FileItem f = ((BocomUploadEncryptRequest<T>) request).GetFileItem();
                string fileName = f.GetFileName();
                //params.put(BocomConstants.ENCRYPT_TYPE, encryptType);
                param.Add(BocomConstants.BIZ_CONTENT, bizContentStr);
                param.Add(BocomConstants.FILE_HASHCODE,
                    SHA256Digest.Digest(fileName));
                param.Add(BocomConstants.ENCRYPT_POLICY, encryptPolicy);
                // 用公钥对对称密钥进行加密
                param.Add(BocomConstants.ENCRYPT_KEY,
                    BocomEncrypt.AsymmetricalEncrypt(encryptKey, BocomConstants.ENCRYPT_TYPE_RSA_AND_AES, bocomPublicKey, charset));

                try
                {
                    //提取fileItem的file 然后转为InputStream,然后用encryptFile加密，然后将加密的流转为字节数组保存
                    FileStream i = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
                    byte[] encryptContent = BocomEncrypt.EncryptFile(i, encryptKey, encryptType);
                    f.SetContent(encryptContent);
                }
                catch (IOException e)
                {
                    throw new BocomApiException("上传文件加密失败", e);
                }
            }

            if (request is BocomDownloadEncryptRequest<T>)
            {
                param.Add(BocomConstants.BIZ_CONTENT, bizContentStr);
                param.Add(BocomConstants.ENCRYPT_POLICY, encryptPolicy);
                // 用公钥对对称密钥进行加密
                param.Add(BocomConstants.ENCRYPT_KEY,
                    BocomEncrypt.AsymmetricalEncrypt(encryptKey, BocomConstants.ENCRYPT_TYPE_RSA_AND_AES, bocomPublicKey, charset));
            }


            string strToSign = WebUtils.BuildOrderedSignStr(path, param);

            Console.WriteLine("strToSign:" + strToSign);

           string signedStr = BocomSignature.Sign(encryptType, strToSign, privateKey, charset);
            if (signedStr.Length < 3)
                throw new Exception("sign Exception!");
            param.Add(BocomConstants.SIGN, signedStr);
            return param;
        }

        protected string BuildBizContentStr<T>(BocomRequest<T> request) where T : BocomResponse
        {
            if (request.GetBizContent() == null)
            {
                return null;
            }

            if (string.Equals(BocomConstants.FORMAT_JSON, format))
            {
                string json;

                //JsonSerializerSettings jsonSetting = new JsonSerializerSettings
                //{ NullValueHandling = NullValueHandling.Ignore };
                //json = JsonConvert.SerializeObject(request.GetBizContent(), Formatting.None, jsonSetting);
                json = Kivii.Text.JsonSerializer.SerializeToString(request.GetBizContent());
                return json;
            }

            throw new Exception("only support json format, current format is not supported. format: " + format);
        }

        public BocomResponse ParseJsonWithBocomSign<T>(BocomRequest<T> request, string respStr, string encryptType,
            string encryptKey) where T : BocomResponse
        {
            if (request is BocomDownloadRequest<T> || request is BocomDownloadEncryptRequest<T>)
            {
                respStr = HttpUtility.UrlDecode(respStr);
            }

            string respBizContentStr = string.Empty;
            string sign = string.Empty;


            int indexOfRootStart = respStr.IndexOf(BocomConstants.RESPONSE_BIZ_CONTENT)
                                   + BocomConstants.RESPONSE_BIZ_CONTENT.Length + 2;

            int indexOfRootEnd = respStr.LastIndexOf(",");
            int indexOfSignStart = respStr.LastIndexOf(BocomConstants.SIGN + "\"") + BocomConstants.SIGN.Length + 3;
            int indexOfSignEnd = respStr.LastIndexOf("\"");

            respBizContentStr = respStr.Substring(indexOfRootStart, indexOfRootEnd - indexOfRootStart);

            sign = respStr.Substring(indexOfSignStart, indexOfSignEnd - indexOfSignStart);


            if (request is BocomDownloadEncryptRequest<T>)
            {
                int indexOfHashCodeStart = respBizContentStr.IndexOf(BocomConstants.FILE_HASHCODE) +
                                           BocomConstants.FILE_HASHCODE.Length + 3;

                int indexOfHashCodeEnd = respBizContentStr.LastIndexOf("\"");

                string hashCode =
                    respBizContentStr.Substring(indexOfHashCodeStart, indexOfHashCodeEnd - indexOfHashCodeStart);

                ((BocomDownloadEncryptRequest<T>) request).SetHashCode(hashCode);
            }


            bool passed = BocomSignature.Verify(encryptType, respBizContentStr, bocomPublicKey, charset, sign);
            if (!passed)
            {
                throw new Exception("bocom sign verify not passed.");
            }

            if (request.IsNeedEncrypt() && request is BocomEncryptRequest<BocomDecryptResponse>)
            {
                string encryptPolicy = ((BocomEncryptRequest<T>) request).GetEncryptPolicy();
                // 全报文加密需要使用对称密钥解密response报文
                if (BocomConstants.ENCRYPT_POLICY_TYPE_CONTENT.Equals(encryptPolicy) && respBizContentStr != null)
                {
                    try
                    {
                        // body = "{\"rsp_biz_content\":" + body + ",\"sign\":\"" + signContent + "\"}";
                        respBizContentStr = BocomEncrypt.SymmetricalDecrypt(
                            respBizContentStr.Substring(1, respBizContentStr.Length - 1), encryptType, encryptKey,
                            charset);
                    }
                    catch (Exception e)
                    {
                        throw new Exception("response can not decrypt. response: " + respStr, e);
                    }
                }
            }

            //反序列化并返回
            BocomResponse response;

            using (MemoryStream ms = new MemoryStream(Encoding.GetEncoding(charset).GetBytes(respBizContentStr)))
            {
                try
                {
                    DataContractJsonSerializer deseralizer = new DataContractJsonSerializer(request.GetResponseClass());

                    response = deseralizer.ReadObject(ms) as BocomResponse;
                }
                catch (Exception e)
                {
                    throw new Exception("---------- respBizContentStr: " + respBizContentStr, e);
                }
            }


            return response;
        }

        public void IgnoreSslHostnameVerifier()
        {
        }
    }
}