using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

namespace HRD.LDAPService.JWT
{
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
    public class JWTAuthorizeVendorId : Attribute, IAuthorizationFilter
    {
        public void OnAuthorization(AuthorizationFilterContext context)
        {
            if (JwtTokenConfig.AktivateAuthorizationFilter)
            {
                //allow access with logn & pwd and without Authorization token
                var path = context?.HttpContext.Request.Path.Value;
                if (!string.IsNullOrEmpty(path))
                {
                    if (JwtTokenConfig.IsInBlackList(path))
                    {
                        context.Result = new JsonResult(new { message = $"Unauthorized access. Path is in a blacklist: '${path}'" }) { StatusCode = StatusCodes.Status403Forbidden };
                        return;
                    }

                    if (JwtTokenConfig.IsInWhiteList(path))
                    {
                        return;
                    }
                    else //need jwt check
                    {
                        var check = (string)context.HttpContext.Items[JwtGlobals.HttpContextItem_IsValidHenselToken];
                        if (check == null)
                        {
                            context.Result = new JsonResult(new { message = $"Unauthorized access. Path: '${path}'" }) { StatusCode = StatusCodes.Status401Unauthorized };
                            return;
                        }
                    }

                    var headerAuthorization = context.HttpContext.Request.Headers["Authorization"];

                    var authorizationType = headerAuthorization.FirstOrDefault()?.Split(" ").First();
                    if (authorizationType == null)
                    {
                        context.Result = new JsonResult(new { message = $"Eror. Path: '${path}'" }) { StatusCode = StatusCodes.Status401Unauthorized };
                        return;
                    }
                    var jwt = headerAuthorization.FirstOrDefault();

                    if (!JwtManager.IsValidatJwtTokenSubject(jwt))
                    {
                        throw new UnauthorizedAccessException($"Not valid JWT");
                    }

                    LdapUser ldapUser = JwtManager.DecryptTokenAsLdapUser(jwt);
                    string ldapUserVendorId = ldapUser.GetExtendedAttributeValue("VendorId");
                    if (string.IsNullOrEmpty(ldapUserVendorId))
                    {
                        context.Result = new JsonResult(new { message = $"Vendor Id is empty. Path: '${path}'" }) { StatusCode = StatusCodes.Status401Unauthorized };
                        return;
                    }
                    var syncIOFeature = context.HttpContext.Features.Get<IHttpBodyControlFeature>();
                    if (syncIOFeature != null)
                    {
                        syncIOFeature.AllowSynchronousIO = true;

                        var req = context.HttpContext.Request;

                        req.EnableBuffering();

                        // read the body here as a workarond for the JSON parser disposing the stream
                        if (req.Body.CanSeek)
                        {
                            req.Body.Seek(0, SeekOrigin.Begin);

                            // if body (stream) can seek, we can read the body to a string for logging purposes
                            using (var reader = new StreamReader(
                                 req.Body,
                                 encoding: Encoding.UTF8,
                                 detectEncodingFromByteOrderMarks: false,
                                 bufferSize: 8192,
                                 leaveOpen: true))
                            {
                                var jsonString = reader.ReadToEnd();
                                var content = JsonConvert.DeserializeObject<dynamic>(jsonString);
                                if (content == null)
                                {
                                    context.Result = new JsonResult(new { message = $"Unauthorized access. Can not deserialize the the  Request: '${path}'" }) { StatusCode = StatusCodes.Status403Forbidden };
                                }

                                if (content?.Root != null && string.Equals(content?.Root?.Type.ToString(), "Array", StringComparison.InvariantCulture))
                                {
                                    List<dynamic> contentList = JsonConvert.DeserializeObject<List<dynamic>>(jsonString);
                                    string jwtVendorId = contentList?.FirstOrDefault()?.vendorId;
                                    if (string.IsNullOrEmpty(jwtVendorId) || ldapUserVendorId != jwtVendorId)
                                    {
                                        context.Result = new JsonResult(new { message = $"Vendor {ldapUserVendorId} is not equal to Vendor from Token {jwtVendorId} List. Path: '${path}'" }) { StatusCode = StatusCodes.Status401Unauthorized };
                                        return;
                                    }
                                }
                                else
                                {
                                    string jwtVendorId = content.vendorId;
                                    if (string.IsNullOrEmpty(jwtVendorId) || ldapUserVendorId != jwtVendorId)
                                    {
                                        context.Result = new JsonResult(new { message = $"Vendor {ldapUserVendorId} is not equal to Vendor from Token {jwtVendorId}. Path: '${path}'" }) { StatusCode = StatusCodes.Status401Unauthorized };
                                        return;
                                    }
                                }
                            }

                            // Important! go back to beginning so json reader get's the whole thing
                            req.Body.Seek(0, SeekOrigin.Begin);
                        }
                    }
                }
            }
        }
    }
}