using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Kyoo.Authentication.Models;
using Kyoo.Models.Permissions;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Options;
namespace Kyoo.Authentication
{
	/// 
	/// A permission validator to validate permission with user Permission array
	/// or the default array from the configurations if the user is not logged. 
	/// 
	public class PermissionValidatorFactory : IPermissionValidator
	{
		/// 
		/// The permissions options to retrieve default permissions.
		/// 
		private readonly IOptionsMonitor _options;
		/// 
		/// Create a new factory with the given options
		/// 
		/// The option containing default values.
		public PermissionValidatorFactory(IOptionsMonitor options)
		{
			_options = options;
		}
		/// 
		public IFilterMetadata Create(PermissionAttribute attribute)
		{
			return new PermissionValidator(attribute.Type, attribute.Kind, attribute.Group, _options);
		}
		
		/// 
		public IFilterMetadata Create(PartialPermissionAttribute attribute)
		{
			return new PermissionValidator((object)attribute.Type ?? attribute.Kind, _options);
		}
		/// 
		/// The authorization filter used by 
		/// 
		private class PermissionValidator : IAsyncAuthorizationFilter
		{
			/// 
			/// The permission to validate
			/// 
			private readonly string _permission;
			/// 
			/// The kind of permission needed
			/// 
			private readonly Kind? _kind;
			/// 
			/// The group of he permission
			/// 
			private readonly Group _group = Group.Overall;
			/// 
			/// The permissions options to retrieve default permissions.
			/// 
			private readonly IOptionsMonitor _options;
			/// 
			/// Create a new permission validator with the given options
			/// 
			/// The permission to validate
			/// The kind of permission needed
			/// The group of the permission
			/// The option containing default values.
			public PermissionValidator(string permission, Kind kind, Group group, IOptionsMonitor options)
			{
				_permission = permission;
				_kind = kind;
				_group = group;
				_options = options;
			}
			/// 
			/// Create a new permission validator with the given options
			/// 
			/// The partial permission to validate
			/// The option containing default values.
			public PermissionValidator(object partialInfo, IOptionsMonitor options)
			{
				if (partialInfo is Kind kind)
					_kind = kind;
				else if (partialInfo is string perm)
					_permission = perm;
				else
					throw new ArgumentException($"{nameof(partialInfo)} can only be a permission string or a kind.");
				_options = options;
			}
			/// 
			public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
			{
				string permission = _permission;
				Kind? kind = _kind;
				if (permission == null || kind == null)
				{
					switch (context.HttpContext.Items["PermissionType"])
					{
						case string perm:
							permission = perm;
							break;
						case Kind kin:
							kind = kin;
							break;
						case null when kind != null:
							context.HttpContext.Items["PermissionType"] = kind;
							return;
						case null when permission != null:
							context.HttpContext.Items["PermissionType"] = permission;
							return;
						default:
							throw new ArgumentException("Multiple non-matching partial permission attribute " +
							                            "are not supported.");
					}
					if (permission == null || kind == null)
						throw new ArgumentException("The permission type or kind is still missing after two partial " +
						                            "permission attributes, this is unsupported.");
				}
				string permStr = $"{permission.ToLower()}.{kind.ToString()!.ToLower()}";
				string overallStr = $"{_group.ToString().ToLower()}.{kind.ToString()!.ToLower()}";
				AuthenticateResult res = await context.HttpContext.AuthenticateAsync(JwtBearerDefaults.AuthenticationScheme);
				if (res.Succeeded)
				{
					ICollection permissions = res.Principal.GetPermissions();
					if (permissions.All(x => x != permStr && x != overallStr))
						context.Result = new StatusCodeResult(StatusCodes.Status403Forbidden);
				}
				else
				{
					ICollection permissions = _options.CurrentValue.Default ?? Array.Empty();
					if (res.Failure != null || permissions.All(x => x != permStr && x != overallStr))
						context.Result = new StatusCodeResult(StatusCodes.Status401Unauthorized);
				}
			}
		}
	}
}