using System;
using System.Collections.Specialized;
using System.IO;
using System.Net;
using System.Net.Cache;
using System.Security.Cryptography;
using System.Text;
using System.Web;
using System.Xml.Linq;

namespace ynhtm.API
{
    using Common;

    public class OAuthQzone : BaseHandler
    {
        string AppID = ""; // APP ID
        string AppKey = ""; // APP KEY
        const string SECRETNAME = "oauth_token_secret";

        public const string SCRIPT = "<script type=\"text/javascript\">try{opener.OAuthQzoneLogin(location.href);}catch(e){}finally{self.close();}</script>";

        static readonly Encoding ENCODING;
        static readonly DateTime TIMESTAMP; // 时间戳起点

        // UrlEncode 要处理的特殊字符
        const string UNRESERVED_CHARS = @"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~";
        const string RESERVED_CHARS = @"`!@#$%^&*()_-+=.~,:;'?/|\[] ";

        static OAuthQzone()
        {
            ENCODING = Encoding.UTF8;
            TIMESTAMP = new DateTime(1970, 1, 1, 0, 0, 0, 0); // 时间戳起点
        }

        public OAuthQzone()
        {
            var setting = new BLL.Setting();
            var settingItem = setting.Get();
            if (!settingItem.OAuthQzoneEnabled) throw new Exception("无法使用QQ账号登录！");
            this.AppID = settingItem.OAuthQzoneAppID;
            this.AppKey = settingItem.OAuthQzoneAppKey;
        }

        public override void Process()
        {
            switch (this.Request["Action"])
            {
                case "Login":
                    {
                        this.CheckSafe(this.Request["oauth_signature"], this.Request["openid"], this.Request["timestamp"]);
                        var url = "http://openapi.qzone.qq.com/oauth/qzoneoauth_access_token";
                        var pars = new NameValueCollection();
                        pars.Add("oauth_token", this.Request["oauth_token"]);
                        pars.Add("oauth_vericode", this.Request["oauth_vericode"]);
                        var s = this.GetOAuth(url, pars);
                        var result = HttpUtility.ParseQueryString(s, ENCODING);
                        this.CheckSafe(result["oauth_signature"], result["openid"], result["timestamp"]);
                        var openID = result["openid"];
                        this.Session[SECRETNAME] = result["oauth_token_secret"];
                        url = "http://openapi.qzone.qq.com/user/get_user_info";
                        pars.Clear();
                        pars.Add("oauth_token", result["oauth_token"]);
                        pars.Add("openid", openID);
                        pars.Add("format", "xml");
                        s = this.GetOAuth(url, pars);
                        var xml = XElement.Parse(s, LoadOptions.PreserveWhitespace);
                        if (xml.Element("ret").Value != "0") throw new Exception(xml.Element("msg").Value);
                        var userName = xml.Element("nickname").Value;
                        var user = new BLL.User();
                        var item = user.GetByOAuthQzoneID(openID);
                        if (item == null)
                        {
                            this.Session["UserName"] = userName;
                            this.Session["OpenID"] = openID;
                            this.Response.Redirect("/User/OAuthQzoneGuide.aspx");
                        }
                        else
                        {
                            user.Login(item, this.Request, this.Response);
                            this.Response.Write(SCRIPT);
                        }
                    }
                    break;
                default:
                    {
                        this.Session[SECRETNAME] = "";
                        var url = "http://openapi.qzone.qq.com/oauth/qzoneoauth_request_token";
                        var s = this.GetOAuth(url);
                        var result = HttpUtility.ParseQueryString(s, ENCODING);
                        this.Session[SECRETNAME] = result["oauth_token_secret"];
                        this.Response.Redirect("http://openapi.qzone.qq.com/oauth/qzoneoauth_authorize?oauth_consumer_key=" + AppID + "&oauth_token=" + result["oauth_token"] + "&oauth_callback=" + this.UrlEncode(this.Request.Url.AbsolutePath + "?Action=Login"));
                    }
                    break;
            }
        }

        /// <summary>
        /// HMAC-SHA1 加密方法。
        /// </summary>
        /// <param name="s">源</param>
        /// <param name="key">密匙</param>
        string GetHmac(string s, string key)
        {
            using (var hmacsha1 = new HMACSHA1(ENCODING.GetBytes(key), true))
                return Convert.ToBase64String(hmacsha1.ComputeHash(ENCODING.GetBytes(s)));
        }

        /// <summary>
        /// 重写 URL 编码，注意 .NET 自带编码无法兼容。
        /// </summary>
        string UrlEncode(string value)
        {
            if (value.IsNullOrWhiteSpace()) return value;
            var result = new StringBuilder();
            foreach (var i in value)
            {
                if (UNRESERVED_CHARS.IndexOf(i) != -1)
                    result.Append(i);
                else if (RESERVED_CHARS.IndexOf(i) != -1)
                    result.AppendFormat("%{0:X2}", (int)i);
                else
                {
                    var s = HttpUtility.UrlEncode(i.ToString(), ENCODING);
                    if (!s.IsNullOrWhiteSpace()) result.Append(s);
                }
            }
            return result.ToString();
        }

        void CheckSafe(string signature, string openid, string timestamp)
        {
            if (signature != this.GetHmac(openid + timestamp, AppKey)) throw new Exception("效验失败！");
        }

        string GetOAuth(string url, NameValueCollection pars = null)
        {
            if (pars == null) pars = new NameValueCollection();
            pars["oauth_consumer_key"] = AppID;
            pars["oauth_nonce"] = DateTime.Now.Ticks.ToString();
            pars["oauth_signature_method"] = "HMAC-SHA1";
            pars["oauth_timestamp"] = DateTime.Now.ToUniversalTime().Subtract(TIMESTAMP).TotalSeconds.ToString("0");
            pars["oauth_version"] = "1.0";
            pars.Remove("oauth_signature");
            var query = "";
            var keys = pars.AllKeys;
            Array.Sort(keys);
            foreach (var i in keys)
                query += this.UrlEncode(i) + "=" + this.UrlEncode(pars[i]) + "&";
            query = query.Remove(query.Length - 1, 1);
            var signature = "GET&" + this.UrlEncode(url) + "&" + this.UrlEncode(query);
            var secret = (string)this.Session[SECRETNAME];
            url += "?" + query + "&oauth_signature=" + this.UrlEncode(this.GetHmac(signature, AppKey + "&" + secret));
            var request = (HttpWebRequest)WebRequest.Create(url);
            request.CachePolicy = new RequestCachePolicy(RequestCacheLevel.NoCacheNoStore);
            request.Method = "GET";
            using (var stream = new StreamReader(request.GetResponse().GetResponseStream(), ENCODING))
            {
                var s = stream.ReadToEnd();
                if (s.StartsWith("error_code=", StringComparison.OrdinalIgnoreCase)) throw new Exception("授权失败：" + s);
                return s;
            }
        }
    }
}