using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Web;

namespace SFAPITest.Services
{
    public class MafengwoHelper
    {
        //  马蜂窝API调用:
        //1. 加密请求数据(data)
        //2. 拼接请求基本字段内容
        //3. 生成签名
        //4. 发送HTTP请求
        //5. 得到HTTP响应
        //6. 解密数据
        //7. 解析JSON结果
        public static string clientId = "";
        public static string clientSecret = "";
        public static string key = "";

        /// <summary>
        /// 马蜂窝api请求封装
        /// </summary>
        /// <param name="actionName">API接口的动作名称</param>
        /// <param name="requesParams">业务接口请求数据</param>
        /// <returns></returns>
        private static string MFWApiRequest(string actionName, object requesParams)
        {
            //商家ID
            int partnerId = int.Parse(clientId);
            //API接口的动作名称
            string action = actionName;
            //请求发起的时间戳
            string timestamp = Convert.ToInt32((DateTime.Now - new DateTime(1970, 1, 1, 0, 0, 0)).TotalSeconds).ToString();
            //随机串
            string nonce = GetRandomStr();
            //业务请求数据
            object obj = requesParams;
            string reqData = Newtonsoft.Json.JsonConvert.SerializeObject(obj);
            //加密请求数据data
            string data = encrypt(reqData, key);

            //根据基础参数生成的数据签名
            //对 partnerId、action、timestamp、key、nonce 和 data 字段进行合并
            string paramsValueStr = partnerId + action + timestamp + key + nonce + data;
            string sign = SignRequest(paramsValueStr);
            //OAuth token
            string access_token = GetAccessToekn(clientId, clientSecret, "client_credentials");
            //加密后的字符串有可能会包含“+”或者“=”字符,Java 或 C# 在某些处理字符串的步骤上会做特殊的处理【对 data 加密后的数据进行 urlencode】
            //data 字段只有在请求的最后一步之前再做 urlencode,请不要在生成签名之前做,这样会导致加密的数据无法被反解成正常的数据
            data = HttpUtility.UrlEncode(data);
            //http请求
            var str = string.Format("partnerId={0}&action={1}&timestamp={2}&nonce={3}&data={4}&sign={5}&access_token={6}", partnerId, action, timestamp, nonce, data, sign, access_token);
            var responseData = Post("https://openapi.mafengwo.cn/deals/rest", str);
            var response = decrypt(responseData, key);
            return DecodeString(response);
        }

        /// <summary>
        /// 生成签名
        /// 在数据已经加密完成之后,需要对 partnerId、action、timestamp、key、nonce 和 data 字段进行合并,并对汇总后的字符串进行 MD5 加密。
        /// 加密后的结果为本次请求的签名。
        ///  md5 加密后的英文字符均应为小写字符,否则验证会不通过
        ///  MD5(partnerId + action + timestamp + key + nonce + data)
        /// </summary>
        /// <returns></returns>
        private static string SignRequest(string str)
        {
            MD5CryptoServiceProvider md5Hasher = new MD5CryptoServiceProvider();
            byte[] hashedDataBytes;
            hashedDataBytes = md5Hasher.ComputeHash(Encoding.GetEncoding("gb2312").GetBytes(str));
            //hashedDataBytes = md5Hasher.ComputeHash(Encoding.GetEncoding("utf-8").GetBytes(str));
            StringBuilder tmp = new StringBuilder();
            foreach (byte i in hashedDataBytes)
            {
                tmp.Append(i.ToString("x2"));
            }
            return tmp.ToString().ToLower();
        }

        /// <summary>
        /// 获取授权令牌   (默认有效期为120分钟(7200秒))
        /// </summary>
        /// <param name="client_id">商户ID</param>
        /// <param name="client_secret">商户Secret</param>
        /// <param name="grant_type">固定值client_credentials</param>
        /// <returns>token</returns>
        private static string GetAccessToekn(string client_id, string client_secret, string grant_type = "client_credentials")
        {
            string url = "https://openapi.mafengwo.cn/oauth2/token?client_id={0}&client_secret={1}&grant_type={2}";
            string reqUrl = string.Format(url, client_id, client_secret, grant_type);
            var resStr = Get(reqUrl);  //"{\"access_token\":\"aab19fa3942ed9456945fc23b88a6baa\",\"token_type\":\"GET\",\"expires_in\":7200,\"scope\":null}"
            var str = "";
            JObject resultObj = JObject.Parse(resStr);
            if (!string.IsNullOrEmpty(resultObj["expires_in"].ToString()) && int.Parse(resultObj["expires_in"].ToString()) > 0)
            {
                str = resultObj["access_token"].ToString();
            }
            return str;
        }

        /// <summary>
        /// 生成随机串
        /// 随机串的生成规则为:
        /// 1. 长度为16为字符串
        /// 2. 字符串仅能包括 大小写英文字符、数字
        /// </summary>
        /// <param name="maxLength">随机串长度</param>
        /// <returns>生成的随机串</returns>
        private static string GetRandomStr(int maxLength = 16)
        {
            string str = "";
            string strPool = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz";
            Random rand = new Random();
            for (var i = 0; i < maxLength; i++)
            {
                str += strPool[rand.Next(0, 61)];
            }
            return str;
        }

        public static string Post(string url, string postData)
        {
            //请求
            WebRequest request = (HttpWebRequest)WebRequest.Create(url);
            request.Method = "POST";
            request.ContentType = "application/x-www-form-urlencoded;charset=utf-8";
            //request.ContentType = "multipart/form-data;charset=utf-8";
            request.ContentLength = Encoding.UTF8.GetByteCount(postData);
            byte[] postByte = Encoding.UTF8.GetBytes(postData);
            Stream reqStream = request.GetRequestStream();
            reqStream.Write(postByte, 0, postByte.Length);
            reqStream.Close();

            //响应
            HttpWebResponse response = (HttpWebResponse)request.GetResponse();
            Stream myResponseStream = response.GetResponseStream();
            StreamReader myStreamReader = new StreamReader(myResponseStream, Encoding.GetEncoding("utf-8"));
            string retString = myStreamReader.ReadToEnd();
            myStreamReader.Close();
            myResponseStream.Close();
            return retString;
        }

        public static string Get(string url)
        {
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
            request.Method = "GET";
            request.ContentType = "text/html;charset=UTF-8";
            HttpWebResponse response = (HttpWebResponse)request.GetResponse();
            Stream myResponseStream = response.GetResponseStream();
            StreamReader myStreamReader = new StreamReader(myResponseStream, Encoding.UTF8);
            string retString = myStreamReader.ReadToEnd();
            myStreamReader.Close();
            myResponseStream.Close();
            return retString;
        }

        /// <summary>
        /// unicode转中文
        /// </summary>
        /// <param name="unicode"></param>
        /// <returns></returns>
        public static string DecodeString(string unicode)
        {
            if (string.IsNullOrEmpty(unicode))
            {
                return string.Empty;
            }
            return System.Text.RegularExpressions.Regex.Unescape(unicode);
        }

        #region 加密解密
        static int BLOCK_SIZE = 32;
        /**
         * 获得对明文进行补位填充的字节.
         *
         * @param count 需要进行填充补位操作的明文字节个数
         * @return 补齐用的字节数组
         */
        public static byte[] fillByte(int count)
        {
            // 计算需要填充的位数
            int amountToPad = BLOCK_SIZE - (count % BLOCK_SIZE);
            if (amountToPad == 0)
            {
                amountToPad = BLOCK_SIZE;
            }
            // 获得补位所用的字符
            char padChr = chr(amountToPad);
            String tmp = string.Empty;
            for (int index = 0; index < amountToPad; index++)
            {
                tmp += padChr;
            }
            return Encoding.UTF8.GetBytes(tmp);
        }


        /**
         * 删除解密后明文的补位字符
         *
         * @param decrypted 解密后的明文
         * @return 删除补位字符后的明文
         */
        public static byte[] removeByte(byte[] decrypted)
        {
            int pad = (int)decrypted[decrypted.Length - 1];
            if (pad < 1 || pad > 32)
            {
                pad = 0;
            }
            byte[] res = new byte[decrypted.Length - pad];
            Array.Copy(decrypted, 0, res, 0, decrypted.Length - pad);
            return res;
        }

        /**
         * 将数字转化成ASCII码对应的字符,用于对明文进行补码
         *
         * @param a 需要转化的数字
         * @return 转化得到的字符
         */
        static char chr(int a)
        {
            byte target = (byte)(a & 0xFF);
            return (char)target;
        }

        /// <summary>
        /// 解密
        /// </summary>
        /// <param name="text"></param>
        /// <param name="sKey"></param>
        /// <returns></returns>
        public static string decrypt(string text, string sKey)
        {
            byte[] Key = Encoding.UTF8.GetBytes(sKey);
            byte[] Iv = new byte[16];
            Array.Copy(Key, Iv, 16);
            byte[] Text = Encoding.UTF8.GetBytes(text);
            return AES_decrypt(text, Iv, Key);
        }

        private static string AES_decrypt(String Input, byte[] Iv, byte[] Key)
        {
            RijndaelManaged aes = new RijndaelManaged();
            aes.KeySize = 256;
            aes.BlockSize = 128;
            aes.Mode = CipherMode.CBC;
            aes.Padding = PaddingMode.None;
            //aes.Padding = PaddingMode.PKCS7;
            aes.Key = Key;
            aes.IV = Iv;
            var decrypt = aes.CreateDecryptor(aes.Key, aes.IV);
            byte[] xBuff = null;
            using (var ms = new MemoryStream())
            {
                using (var cs = new CryptoStream(ms, decrypt, CryptoStreamMode.Write))
                {
                    byte[] xXml = Convert.FromBase64String(Input);
                    byte[] msg = new byte[xXml.Length + 32 - xXml.Length % 32];
                    Array.Copy(xXml, msg, xXml.Length);
                    cs.Write(xXml, 0, xXml.Length);
                }
                xBuff = removeByte(ms.ToArray());
            }
            return System.Text.Encoding.Default.GetString(xBuff);
        }


        /// <summary>
        /// 加密
        /// </summary>
        /// <param name="Input"></param>
        /// <param name="Iv"></param>
        /// <param name="Key"></param>
        /// <returns></returns>
        public static string encrypt(string text, string sKey)
        {
            byte[] Key = Encoding.UTF8.GetBytes(sKey);
            byte[] Iv = new byte[16];
            Array.Copy(Key, Iv, 16);
            byte[] Text = Encoding.UTF8.GetBytes(text);
            return AES_encrypt(Text, Iv, Key);
        }

        private static String AES_encrypt(byte[] Input, byte[] Iv, byte[] Key)
        {
            var aes = new System.Security.Cryptography.RijndaelManaged();
            //秘钥的大小,以位为单位
            aes.KeySize = 256;
            //支持的块大小
            aes.BlockSize = 128;
            //填充模式
            //aes.Padding = PaddingMode.PKCS7;
            aes.Padding = System.Security.Cryptography.PaddingMode.None;
            aes.Mode = System.Security.Cryptography.CipherMode.CBC;
            aes.Key = Key;
            aes.IV = Iv;
            var encrypt = aes.CreateEncryptor(aes.Key, aes.IV);
            byte[] xBuff = null;

            #region 自己进行PKCS7补位,用系统自己带的不行
            byte[] msg = new byte[Input.Length + 32 - Input.Length % 32];
            Array.Copy(Input, msg, Input.Length);
            byte[] pad = fillByte(Input.Length);
            Array.Copy(pad, 0, msg, Input.Length, pad.Length);
            #endregion

            #region 注释的也是一种方法,效果一样
            //ICryptoTransform transform = aes.CreateEncryptor();
            //byte[] xBuff = transform.TransformFinalBlock(msg, 0, msg.Length);
            #endregion

            using (var ms = new MemoryStream())
            {
                using (var cs = new System.Security.Cryptography.CryptoStream(ms, encrypt, System.Security.Cryptography.CryptoStreamMode.Write))
                {
                    cs.Write(msg, 0, msg.Length);
                }
                xBuff = ms.ToArray();
            }

            String Output = Convert.ToBase64String(xBuff);
            return Output;
        }

        #endregion

        #region 接口调用
        /// <summary>
        /// 获取马蜂窝订单详情
        /// </summary>
        /// <param name="orderId"></param>
        /// <returns></returns>
        public static MFWResponse<MFWOrderDetail> GetMFWOrderDetail(string orderId)
        {
            //API接口的动作名称
            string action = "sales.order.detail.get";
            //业务请求数据
            object obj = new
            {
                order_id = orderId
            };
            var response = MFWApiRequest(action, obj);
            var result = Newtonsoft.Json.JsonConvert.DeserializeObject<MFWResponse<MFWOrderDetail>>(response);
            return result;
        }


        /// <summary>
        /// 获取订单备注
        /// </summary>
        /// <param name="orderId"></param>
        /// <returns></returns>
        public static List<MFWOrderMemo> GetMFWOrderMemo(string orderId)
        {
            string action = "sales.order.memo.get";
            object obj = new
            {
                order_id = orderId
            };
            var response = MFWApiRequest(action, obj);
            var result = Newtonsoft.Json.JsonConvert.DeserializeObject<MFWResponse<List<MFWOrderMemo>>>(response);
            return result.data;
        }

        public static string GetMFWOrderMemoStr(string orderId)
        {
            var memoList = GetMFWOrderMemo(orderId);
            var memo = string.Join("|", memoList.Select(x => x.content));
            return memo;
        }

        /// <summary>
        /// 获取出行人信息
        /// </summary>
        /// <returns></returns>
        public static MFWTravelInfo GetTraveler(string orderId)
        {
            string action = "sales.order.traveler.get";
            object obj = new
            {
                order_id = orderId
            };
            var response = MFWApiRequest(action, obj);
            var result = Newtonsoft.Json.JsonConvert.DeserializeObject<MFWResponse<MFWTravelInfo>>(response);
            return result.data;
        }

        /// <summary>
        /// 获取补款单列表
        /// </summary>
        /// <param name="orderId"></param>
        /// <returns></returns>
        public static MFWReplenishResponse GetReplenishList(string orderId)
        {
            string action = "sales.replenish.list.get";
            object obj = new
            {
                order_id = orderId
            };
            var response = MFWApiRequest(action, obj);
            var result = Newtonsoft.Json.JsonConvert.DeserializeObject<MFWReplenishResponse>(response);
            return result;
        }


        #endregion
    }

    public class MFWResponse<T>
    {
        public int errno { get; set; }
        public string message { get; set; }
        public T data { get; set; }
    }

    #region 订单详情实体
    /// <summary>
    /// 订单详情
    /// </summary>
    public class MFWOrderDetail
    {
        //旅行商城业务订单号
        public string orderId { get; set; }
        public Status status { get; set; }
        //旅行出行时间
        public string goDate { get; set; }
        //旅行结束日期
        public string endDate { get; set; }
        //订单支付时间
        public string paytime { get; set; }
        //订单创建时间
        public string ctime { get; set; }
        //预订人信息
        public BookingPeople bookingPeople { get; set; }
        //马蜂窝产品id,产品唯一标识
        public string salesId { get; set; }
        //产品名称
        public string salesName { get; set; }
        //商家设置的产品外部编码
        public string otaSalesName { get; set; }
        //订单关联产品品类
        public int salesType { get; set; }
        //目的地
        public string mdd { get; set; }
        //订单关联产品出发地
        public string from { get; set; }
        //马蜂窝SKU ID,SKU唯一标识
        public string skuId { get; set; }
        //商家设置的SKU外部编码
        public string otaSkuId { get; set; }
        //SKU名称
        public string skuName { get; set; }
        //订单原始金额
        public decimal totalPrice { get; set; }
        //用户实际支付金额
        public decimal paymentFee { get; set; }
        //订单购买项详细信息
        public List<Items> items { get; set; }
        //订单优惠信息
        public PromotionDetail promotionDetail { get; set; }
        //库存信息
        public List<Skus> skus { get; set; }
    }

    /// <summary>
    /// 库存信息
    /// </summary>
    public class Skus
    {
        //库存名称
        public string stockName { get; set; }
        //商家设置的SKU外部编码
        public string otaSkuId { get; set; }
        //库存ID
        public int skuId { get; set; }
    }

    /// <summary>
    /// 订单优惠信息
    /// </summary>
    public class PromotionDetail
    {
        //马蜂窝补贴金额
        public decimal reduce_mfw { get; set; }
        //商家补贴金额
        public decimal reduce_ota { get; set; }
    }

    /// <summary>
    /// 订单购买项详情信息
    /// </summary>
    public class Items
    {
        //剩余可退金额
        public decimal remain_payment_fee { get; set; }
        //剩余可退数量
        public int remain_num { get; set; }
        //库存ID
        public int skuId { get; set; }
        //费用项
        public int price_type { get; set; }
        //本项总金额
        public decimal total_price { get; set; }
        //本项应支付金额
        public decimal payment_fee { get; set; }
        //购买项描述
        public string name { get; set; }
        //本项单价金额
        public decimal price { get; set; }
        //本项购买个数
        public int num { get; set; }
        //购买项ID
        public int id { get; set; }
    }

    /// <summary>
    /// 预订人信息
    /// </summary>
    public class BookingPeople
    {
        //预定人马蜂窝UID
        public int uid { get; set; }
        public string name { get; set; }
        public string email { get; set; }
        public string phone { get; set; }
        public string phone_area { get; set; }
        public string wechat { get; set; }
        public string remark { get; set; }
    }

    /// <summary>
    /// 状态信息
    /// </summary>
    public class Status
    {
        //订单状态
        public int orderStatus { get; set; }
        //全退标识
        public int allRefundFlag { get; set; }
        //退款状态
        public int refundStatus { get; set; }
    }

    /// <summary>
    /// 订单备注实体
    /// </summary>
    public class MFWOrderMemo
    {
        public int id { get; set; }
        public string order_id { get; set; }
        public int admin_uid { get; set; }
        public string content { get; set; }
        public DateTime dateTime { get; set; }
    }

    #region 出行人信息实体
    public class MFWTravelInfo
    {
        public string order_id { get; set; }
        public MFWTravelPeople travel_people { get; set; }
    }

    public class MFWTravelPeople
    {
        public MFWTraveler[] traveler { get; set; }
        public MFWTrip trip { get; set; }
        public MFWTsAddress ts_address { get; set; }
        public MFWAddress address { get; set; }
    }

    /// <summary>
    /// 出行人信息
    /// </summary>
    public class MFWTraveler
    {
        public string name { get; set; }
        public string id_type { get; set; }
        public string birthday { get; set; }
        public string gender { get; set; }
        public string nationality { get; set; }
        public string height { get; set; }
        public string weight { get; set; }
        public string shoe_size { get; set; }
        public string left_eye_sight { get; set; }
        public string right_eye_sight { get; set; }
        public string date_of_expiry { get; set; }
        public string cellphone { get; set; }
        public string family_name { get; set; }
        public string mainland_phone { get; set; }
        public string traveler_id { get; set; }
        public string laissez_passer_tw { get; set; }
        public string laissez_passer { get; set; }
        public string passport { get; set; }
        public string id_card { get; set; }
        public string first_name { get; set; }
    }

    public class MFWTrip
    {
        public string pick_up_time { get; set; }
        public string pick_up_place { get; set; }
        public string send_to { get; set; }
        public string pick_up_place_en { get; set; }
        public string send_to_en { get; set; }
        public string hotel_name_pick_up { get; set; }
        public string hotel_address_pick_up { get; set; }
        public string hotel_name_en_pick_up { get; set; }
        public string hotel_address_en_pick_up { get; set; }
        public string hotel_name_send_to { get; set; }
        public string hotel_address_send_to { get; set; }
        public string hotel_name_en_send_to { get; set; }
        public string hotel_address_en_send_to { get; set; }
        public string hotel_name_over_night { get; set; }
        public string flight_no_arrival { get; set; }
        public string flight_time_arrival { get; set; }
        public string flight_no_departure { get; set; }
        public string flight_time_departure { get; set; }
        public string luggage { get; set; }
        public string hotel_name_en { get; set; }
        public string hotel_address_en { get; set; }
        public string hotel_phone { get; set; }
        public string hotel_telephone { get; set; }
        public string using_time { get; set; }
        public string using_place { get; set; }
        public string flight_number { get; set; }
        public string flight_time { get; set; }
        public string place { get; set; }
        public string return_hotel { get; set; }
        public string hotel_adress { get; set; }
        public string return_hotel_phone { get; set; }
        public string pick_and_send_hotel_phone { get; set; }
        public string schedule { get; set; }
        public string back_hotel { get; set; }
        public string back_adress { get; set; }
        public string back_phone { get; set; }
        public string hotel_name { get; set; }
        public string hotel_address { get; set; }
        public string hotel_phone_number { get; set; }
        public string check_in_date { get; set; }
        public string check_out_date { get; set; }
        public string return_flight_number { get; set; }
        public string return_flight_time { get; set; }
        public string arrival_date { get; set; }
        public string departure_date { get; set; }
        public string departure_hotel_name { get; set; }
        public string departure_hotel_adress { get; set; }
        public string departure_hotel_number { get; set; }
        public string back_date { get; set; }
        public string over_night_hotel_address { get; set; }
        public string get_device_adress { get; set; }
        public string departure_hotel_name_cn { get; set; }
        public string time { get; set; }
        public string number { get; set; }
        public string phone { get; set; }
        public string wechat { get; set; }
        public string estimated_travel_date { get; set; }
        public string train_number { get; set; }
        public string train_station { get; set; }
        public string departure_time { get; set; }
        public string departure_frequency { get; set; }
        public string departure_hotel_area { get; set; }
        public string meal_time { get; set; }
        //出行人数
        public string tourists_number { get; set; }
    }

    public class MFWTsAddress
    {
        public string pick_up_address { get; set; }
        public string return_address { get; set; }
    }

    public class MFWAddress
    {
        public string adress { get; set; }
        public string receiver_name { get; set; }
        public string receiver_phone { get; set; }
    }
    #endregion

    #region 补款单
    public class MFWReplenishResponse
    {
        public int total { get; set; }
        public MFWReplenish[] list { get; set; }
    }

    public class MFWReplenish
    {
        public string order_id { get; set; }
        //补款单号
        public string replenish_id { get; set; }
        //补款单状态
        public int status { get; set; }
        //补款单创建时间
        public string ctime { get; set; }
        //创建补款单原因
        public int reason { get; set; }
        // 补款单具体金额
        public decimal fee { get; set; }
        // 补款单备注
        public string remark { get; set; }
    }

    /// <summary>
    /// 补款单状态
    /// </summary>
    public enum ReplenishStatus
    {
        待支付 = 0,
        已支付 = 1,
        申请退款中 = 2,
        部分退款成功 = 3,
        全部退款成功 = 4,
        已关闭 = 5
    }
    #endregion

    #endregion

}