using System;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using System.Web.Configuration;
using System.Net;
using System.Collections.Specialized;
using System.Text;
using minimalist_oauth_rp.Models;
using System.IO;
using System.Runtime.Serialization.Json;

namespace minimalist_oauth_rp.Controllers
{
    [HandleError]
    public class AuthenticationController : Controller
    {
        Uri hostname;
        String sesion;

        protected override void Initialize(RequestContext requestContext)
        {
            if (requestContext.HttpContext.Session.Keys.Count > 0)
            {
                sesion = (String)requestContext.HttpContext.Session["previousState"];
            }
            hostname = requestContext.HttpContext.Request.Url;
            base.Initialize(requestContext);
        }

        public ActionResult OAuthStart()
        {
            String authServerBaseUri = WebConfigurationManager.AppSettings["trustedxAuthServerBaseUri"];
            String appOAuthRedirectUri = hostname.Scheme + "://" + hostname.Authority + WebConfigurationManager.AppSettings["appOAuthRedirectUri"];
            String appOAuthClientId = WebConfigurationManager.AppSettings["appOAuthClientId"];
            String appOAuthScopes = WebConfigurationManager.AppSettings["appOAuthScopes"];
            String appOAuthFlowSelection = WebConfigurationManager.AppSettings["appOAuthFlowSelection"];

            String state = this.generateRandom();

            UriBuilder uriBuilder = new UriBuilder(authServerBaseUri);
            var query = HttpUtility.ParseQueryString(uriBuilder.Query);
            query["redirect_uri"] = appOAuthRedirectUri;
            query["client_id"] = appOAuthClientId;
            query["response_type"] = "code";
            query["state"] = state;
            query["scope"] = appOAuthScopes;
            query["acr_values"] = appOAuthFlowSelection;
            query["prompt"] = "login";
            query["ui_locales"] = "es";

            Session["previousState"] = state;

            uriBuilder.Query = query.ToString();
            String url = uriBuilder.ToString();

            return Redirect(url);
        }

        public ActionResult OauthCallback(String code, String state, String error, String error_description)
        {
            if (sesion != state)
            {
                return RedirectToAction("Http400", "Errors");
            }
            if (!String.IsNullOrEmpty(error))
            {
                if ("access_denied".Equals(error) && String.IsNullOrEmpty(error_description))
                {
                    return RedirectToAction("Index", "Home");
                }
                else
                {
                    return RedirectToAction("Http500", "Errors", new RouteValueDictionary(new { error = error, error_description = error_description }));
                }
            }
            // Base URL of TrustedX Authentication Server
            String authServerBaseUri = WebConfigurationManager.AppSettings["trustedxAuthServerBaseUri"];
            // Base URL of TrustedX Resource Server
            String resourcesBaseUri = WebConfigurationManager.AppSettings["trustedxResourcesBaseUri"];
            // As specified by OAuth, for security reasons you need to again send the redirection URL to
            // TrustedX. In this example, the URL is simply re-read from the property file.
            String appOAuthRedirectUri = hostname.Scheme + "://" + hostname.Authority + WebConfigurationManager.AppSettings["appOAuthRedirectUri"];
            // The application home page displayed to the user after successful login.
            String appHomeRedirectUri = WebConfigurationManager.AppSettings["appHomeRedirectUri"];
            String appReauthenticateUri = WebConfigurationManager.AppSettings["appReauthenticateUri"];
            // To authenticate the request, use your API_KEY as an HTTP header
            String apiKey = WebConfigurationManager.AppSettings["apiKey"];

            WebClient client = new WebClient();
            client.Encoding = Encoding.UTF8;
            client.Headers.Add("Authorization", "Basic " + apiKey);
            NameValueCollection values = new NameValueCollection();
            values["redirect_uri"] = appOAuthRedirectUri;
            values["code"] = code;
            values["grant_type"] = "authorization_code";
            byte[] responsePost;
            String responsePostString;
            try
            {
                responsePost = client.UploadValues(authServerBaseUri + "/token", values);
                responsePostString = Encoding.UTF8.GetString(responsePost);
            }
            catch (WebException we)
            {
                return RedirectToAction("Http400", "Errors", new RouteValueDictionary(new { error = we.Message, error_description = we.InnerException }));
            }
            MemoryStream msPost = new MemoryStream(responsePost);
            DataContractJsonSerializer serOuathAccessToken = new DataContractJsonSerializer(typeof(OauthAccessToken));
            OauthAccessToken oauthAccessToken = (OauthAccessToken)serOuathAccessToken.ReadObject(msPost);

            client.Headers.Set("Authorization", "Bearer " + oauthAccessToken.access_token);
            String responseGet = "";
            try
            {
                responseGet = client.DownloadString(resourcesBaseUri + "/openid/v1/users/me");
            }
            catch (WebException we)
            {
                return RedirectToAction("Http400", "Errors", new RouteValueDictionary(new { error = we.Message, error_description = we.InnerException }));
            }
            MemoryStream msGet = new MemoryStream(Encoding.UTF8.GetBytes(responseGet));
            DataContractJsonSerializer serLoginInfo = new DataContractJsonSerializer(typeof(UserInfo));
            UserInfo userInfo = (UserInfo)serLoginInfo.ReadObject(msGet);
            ViewData["accessToken"] = oauthAccessToken.access_token;
            ViewData["jsonTokenResponsePrettyPrint"] = responsePostString;
            ViewData["jsonUserInfoResponsePrettyPrint"] = responseGet;

            return View("Login",userInfo);
        }

        public ActionResult Logout() {
            // For the logout operation, you have two choices:
            //
            // - Logout from the application only: Under this strategy, only the session in the
            // application is terminated, and no logout request is sent to TrustedX. This is usually the
            // best option as the user can still use single sign-on until the session expires or the
            // browser is closed.
            //
            // - Logout from both TrustedX and the application: Under this strategy, the user is logged
            // out from the application and the identity provider (TrustedX). This may be appropriate
            // for some scenarios, especially if this is the only (or the "main") application the user
            // connects to.
            //
            // This example shows you how to log out from TrustedX and return to the application where a
            // local logout would be performed (this last part is omitted for the sake of simplicity).
            // To do this, you need to generate a logout request to be sent to TrustedX. In this
            // request, you include a "redirect_uri" parameter, which makes TrustedX return the control
            // back to the application after the logout from the IdP.

            // URL of the application where the browser is redirected after the TrustedX logout.
            String appLogoutRedirectUri = hostname.Scheme + "://" + hostname.Authority + WebConfigurationManager.AppSettings["appLogoutRedirectUri"];
            String idpPortalUri = WebConfigurationManager.AppSettings["trustedxIdpServerBaseUri"];
            // Logout request sent to TrustedX Authentication Server. The previous redirection URL is
            // included.
            String logoutUri = idpPortalUri + "/logout?redirect_uri=" + appLogoutRedirectUri;
            return Redirect(logoutUri);
        }

        private string generateRandom()
        {
            Random random = new Random();
            byte[] bytes = new byte[16];
            random.NextBytes(bytes);
            return Convert.ToBase64String(bytes);
        }

    }
}
