using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Net;
using System.Net.Security;
using System.Text;
using System.Text.RegularExpressions;

namespace AllocsFixes.NetConnections.Servers.Web
{
	public static class OpenID {
		private const string STEAM_LOGIN = "https://steamcommunity.com/openid/login";
		private static Regex steamIdUrlMatcher = new Regex (@"^http:\/\/steamcommunity\.com\/openid\/id\/([0-9]{17,18})");

		static OpenID () {
			ServicePointManager.ServerCertificateValidationCallback = (srvPoint, certificate, chain, errors) => {
				if (errors == SslPolicyErrors.None)
					return true;

				Log.Out ("Steam certificate error: {0}", errors);

				return true;
			};

		}

		public static string GetOpenIdLoginUrl (string _returnHost, string _returnUrl) {
			Dictionary<string, string> queryParams = new Dictionary<string, string> ();

			queryParams.Add ("openid.ns", "http://specs.openid.net/auth/2.0");
			queryParams.Add ("openid.mode", "checkid_setup");
			queryParams.Add ("openid.return_to", _returnUrl);
			queryParams.Add ("openid.realm", _returnHost);
			queryParams.Add ("openid.identity", "http://specs.openid.net/auth/2.0/identifier_select");
			queryParams.Add ("openid.claimed_id", "http://specs.openid.net/auth/2.0/identifier_select");

			return STEAM_LOGIN + '?' + buildUrlParams (queryParams);
		}

		public static ulong Validate (HttpListenerRequest _req) {
			if (getValue (_req, "openid.mode") == "cancel") {
				Log.Warning ("Steam OpenID login canceled");
				return 0;
			}
			string steamIdString = getValue (_req, "openid.claimed_id");
			ulong steamId = 0;
			Match steamIdMatch = steamIdUrlMatcher.Match (steamIdString);
			if (steamIdMatch.Success) {
				steamId = ulong.Parse (steamIdMatch.Groups [1].Value);
			} else {
				Log.Warning ("Steam OpenID login result did not give a valid SteamID");
				return 0;
			}

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

			queryParams.Add ("openid.ns", "http://specs.openid.net/auth/2.0");

			queryParams.Add ("openid.assoc_handle", getValue (_req, "openid.assoc_handle"));
			queryParams.Add ("openid.signed", getValue (_req, "openid.signed"));
			queryParams.Add ("openid.sig", getValue (_req, "openid.sig"));
			queryParams.Add ("openid.identity", "http://specs.openid.net/auth/2.0/identifier_select");
			queryParams.Add ("openid.claimed_id", "http://specs.openid.net/auth/2.0/identifier_select");

			string[] signeds = getValue (_req, "openid.signed").Split (',');
			foreach (string s in signeds) {
				queryParams ["openid." + s] = getValue (_req, "openid." + s);
			}

			queryParams.Add ("openid.mode", "check_authentication");

			byte[] postData = Encoding.ASCII.GetBytes (buildUrlParams (queryParams));
			HttpWebRequest request = (HttpWebRequest)WebRequest.Create (STEAM_LOGIN);
			request.Method = "POST";
			request.ContentType = "application/x-www-form-urlencoded";
			request.ContentLength = postData.Length;
			request.Headers.Add (HttpRequestHeader.AcceptLanguage, "en");
			using (Stream st = request.GetRequestStream ()) {
				st.Write (postData, 0, postData.Length);
			}

			HttpWebResponse response = (HttpWebResponse)request.GetResponse ();
			string responseString = null;
			using (Stream st = response.GetResponseStream ()) {
				using (StreamReader str = new StreamReader (st)) {
					responseString = str.ReadToEnd ();
				}
			}

			if (responseString.ToLower ().Contains ("is_valid:true")) {
				return steamId;
			} else {
				Log.Warning ("Steam OpenID login failed: {0}", responseString);
				return 0;
			}
		}

		private static string buildUrlParams (Dictionary<string, string> _queryParams) {
			string[] paramsArr = new string[_queryParams.Count];
			int i = 0;
			foreach (KeyValuePair<string, string> kvp in _queryParams) {
				paramsArr [i++] = kvp.Key + "=" + Uri.EscapeDataString (kvp.Value);
			}
			return string.Join ("&", paramsArr);
		}

		private static string getValue (HttpListenerRequest _req, string _name) {
			NameValueCollection nvc = _req.QueryString;
			if (nvc [_name] == null) {
				throw new MissingMemberException ("OpenID parameter \"" + _name + "\" missing");
			}
			return nvc [_name];
		}

	}
}

