using Diligent.WebAPI.Business.Services;
using Microsoft.AspNetCore.SignalR;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Net;
using System.Text;
using System.Web.Http;

namespace Diligent.WebAPI.Host.Middlewares
{
    [AttributeUsage(AttributeTargets.Method)]
    public class AuthorizationHubFilter : Attribute, IHubFilter
    {
        public string Roles;

        public async ValueTask<object> InvokeMethodAsync(
            HubInvocationContext invocationContext, Func<HubInvocationContext, ValueTask<object>> next)
        {
            Type type = typeof(AuthorizationHubFilter);
            var arguments = Attribute.GetCustomAttributes(invocationContext.HubMethod).Where(k => k.TypeId.ToString() == type.ToString()).ToList();

            // there is no custom attributes, so the filter does not need to perform authorization
            if (!arguments.Any())
                return await next(invocationContext);

            // get filter attribute
            var roles = Attribute.GetCustomAttribute(invocationContext.HubMethod, arguments[0].GetType());

            if (roles is null)
                throw new NullReferenceException();

            // get roles from filter attribute and split roles into an array
            var arrayOfRoles = ((AuthorizationHubFilter)roles).Roles.Split(",");

            var context = invocationContext.Context.GetHttpContext();

            if (context is null)
                throw new NullReferenceException();

            var token = context.Request.Query["access_token"];

            await AttachUserToContext(context, token, arrayOfRoles);

            return await next(invocationContext);
        }

        private static async Task AttachUserToContext(HttpContext context, string token, string[] roles)
        {
            bool contain = false;
            try
            {
                var authorizationService = context.RequestServices.GetService(typeof(AuthorizationService));
                var configuration = context.RequestServices.GetService(typeof(IConfiguration));

                if (authorizationService is null || configuration is null)
                    throw new NullReferenceException();

                var tokenHandler = new JwtSecurityTokenHandler();
                var jwtSettings = ((IConfiguration)configuration).GetSection("JwtSettings");
                var issuer = jwtSettings["validIssuer"];
                var audience = jwtSettings["validAudience"];
                var key = Encoding.UTF8.GetBytes(jwtSettings["jwtSecret"]);

                tokenHandler.ValidateToken(token, new TokenValidationParameters
                {
                    ValidateIssuerSigningKey = true,
                    IssuerSigningKey = new SymmetricSecurityKey(key),
                    ValidIssuer = issuer,
                    ValidAudience = audience,
                    ValidateIssuer = true,
                    ValidateAudience = true,
                    ValidateLifetime = true,
                }, out SecurityToken validatedToken);

                var jwtToken = (JwtSecurityToken)validatedToken;
                var userName = jwtToken.Payload["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"].ToString();
                var rl = (jwtToken.Payload["http://schemas.microsoft.com/ws/2008/06/identity/claims/role"]).ToString();

                if (rl is null)
                    throw new Exception();

                var rlArray = rl.Split(",");

                //check is there any role from token same as role from attribute of authorization filter
                foreach (var t in rlArray)
                {
                    if (roles.Contains(t))
                    {
                        contain = true;
                        break;
                    }
                }

                if (!contain)
                {
                    throw new Exception();
                }

                // attach user to context on successful jwt validation
                if (userName != null)
                {
                    context.Items["User"] = await ((AuthorizationService)authorizationService).GetByUserName(userName);
                }

            }
            catch
            {
                // user don't have appropriate role to access the hub method
                if (!contain)
                {
                    throw new HttpResponseException(HttpStatusCode.Forbidden);
                }
                throw new UnauthorizedAccessException();
            }
        }
    }
}
