using EVCB_OCPP.WEBAPI.Models.WebAPI;
using EVCB_OCPP.WEBAPI.Services;
using Microsoft.AspNetCore.Http;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Http.Extensions;
using System.IO;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

namespace EVCB_OCPP.WEBAPI.Handlers
{
    public class CPOAuthentication : Attribute, IAsyncAuthorizationFilter
    {
        private readonly IServiceProvider serviceProvider;
        private readonly ICustomerService customerService;
        private readonly ILogger logger;

        public CPOAuthentication(
            IServiceProvider serviceProvider,
            ICustomerService customerService,
            ILogger<CPOAuthentication> logger)
        {
            this.serviceProvider = serviceProvider;
            this.customerService = customerService;
            this.logger = logger;
        }

        public void OnAuthorization(AuthorizationFilterContext context)
        {
            OnAuthorizationAsync(context).RunSynchronously();
        }

        public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
        {
            HttpContext actionContext = context.HttpContext;
            int _status = StatusCodes.Status401Unauthorized;// HttpStatusCode.Unauthorized;
            CPOOuterResponse _errorResponse = new CPOOuterResponse() { StatusMessage = CPO_StatusMessage.ERROR_MSG_INVALIDHEADER };

            try
            {
                string formatMsg = "";
                if (CheckHeaders(actionContext, out formatMsg))
                {
                    string timestamp = actionContext.Request.Headers[EVCBConfiguration.Header_Timestamp].FirstOrDefault();

                    if (IsExpiryTime(timestamp))
                    {
                        _errorResponse.StatusMessage = CPO_StatusMessage.ERROR_MSG_INVALIDTIMESTAMP;
                    }
                    else
                    {
                        string partnerId = actionContext.Request.Headers[EVCBConfiguration.Header_PartnerId].FirstOrDefault();
                        string key = await customerService.GetAPIKeyAsync(Guid.Parse(partnerId));

                        if (IsPass(actionContext, key))
                        {
                            return;
                            //return continuation().ContinueWith<HttpResponseMessage>((tsk) =>
                            //{
                            //    HttpResponseMessage response = tsk.Result;

                            //    return response;

                            //}, cancellationToken);
                        }
                        else
                        {
                            _errorResponse.StatusMessage = CPO_StatusMessage.ERROR_MSG_INVALIDSIGNATURE;
                        }
                    }

                }
                else
                {
                    _errorResponse.StatusMessage = formatMsg;
                }

            }
            catch (Exception ex)
            {
                _errorResponse.StatusMessage = CPO_StatusMessage.ERROR_MSG_UNEXPECTEDERROR;
                logger.LogError(ex, null, null);
            }


            //context.Result = FromResult(new HttpResponseMessage(_status) { Content = new StringContent(JsonConvert.SerializeObject(_errorResponse), System.Text.Encoding.UTF8, "application/json") });
            context.Result = new ContentResult()
            {
                StatusCode = _status,
                ContentType = "application/json",
                Content = JsonConvert.SerializeObject(_errorResponse)
            };
        }

        public Task<HttpResponseMessage> ExecuteAuthorizationFilterAsync(HttpContext actionContext, CancellationToken cancellationToken, Func<Task<HttpResponseMessage>> continuation)
        {
            HttpStatusCode _status = HttpStatusCode.Unauthorized;
            CPOOuterResponse _errorResponse = new CPOOuterResponse() { StatusMessage = CPO_StatusMessage.ERROR_MSG_INVALIDHEADER };

            try
            {
                string formatMsg = "";
                if (CheckHeaders(actionContext, out formatMsg))
                {
                    string timestamp = actionContext.Request.Headers[EVCBConfiguration.Header_Timestamp].FirstOrDefault();

                    if (IsExpiryTime(timestamp))
                    {
                        _errorResponse.StatusMessage = CPO_StatusMessage.ERROR_MSG_INVALIDTIMESTAMP;
                    }
                    else
                    {
                        string partnerId = actionContext.Request.Headers[EVCBConfiguration.Header_PartnerId].FirstOrDefault();
                        string key = serviceProvider.GetRequiredService<ICustomerService>().GetAPIKey(new Guid(partnerId));

                        if (IsPass(actionContext, key))
                        {
                            return continuation().ContinueWith<HttpResponseMessage>((tsk) =>
                            {
                                HttpResponseMessage response = tsk.Result;

                                return response;

                            }, cancellationToken);
                        }
                        else
                        {
                            _errorResponse.StatusMessage = CPO_StatusMessage.ERROR_MSG_INVALIDSIGNATURE;
                        }
                    }

                }
                else
                {
                    _errorResponse.StatusMessage = formatMsg;
                }

            }
            catch (Exception ex)
            {
                _errorResponse.StatusMessage = CPO_StatusMessage.ERROR_MSG_UNEXPECTEDERROR;
            }


            return FromResult(new HttpResponseMessage(_status) { Content = new StringContent(JsonConvert.SerializeObject(_errorResponse), System.Text.Encoding.UTF8, "application/json") });
        }

        private Task<HttpResponseMessage> FromResult(HttpResponseMessage result)
        {
            var source = new TaskCompletionSource<HttpResponseMessage>();
            source.SetResult(result);


            return source.Task;
        }

        private bool IsPass(HttpContext actionContext, string key)
        {
            bool authenticated = false;
            string timestamp = actionContext.Request.Headers[EVCBConfiguration.Header_Timestamp].FirstOrDefault();
            string partnerId = actionContext.Request.Headers[EVCBConfiguration.Header_PartnerId].FirstOrDefault();
            string signature = actionContext.Request.Headers[EVCBConfiguration.Header_Signature].FirstOrDefault();

            //Task<string> content = actionContext.Request.Body.Content.ReadAsStringAsync();
            //string body = content.Result;
            //actionContext.Request.Body.Position = 0;
            //string body = new StreamReader(actionContext.Request.Body).ReadToEnd();
            //actionContext.Request.Body.Position = 0;
            string body = actionContext.GetPreLoadBody();
            string diplayUrl = actionContext.Request.GetDisplayUrl().Replace("%20", " ").Replace(@"\\", @"\");
            string tempText = diplayUrl.Substring(diplayUrl.IndexOf('?') + 1).ToLower();
            tempText = tempText.StartsWith("http") ? string.Empty : tempText;
            body = tempText + body;

            string unencodeText = string.Format("{0}{1}{2}{3}", body, timestamp, partnerId, key).ToLower();
            string signVerification = GenerateSignature(unencodeText);
            if (signVerification == signature)
            {
                authenticated = true;
            }

            return authenticated;
        }

        private bool CheckHeaders(HttpContext actionContext, out string formatMessage)
        {
            formatMessage = string.Empty;

            if (actionContext.Request.Headers.ContainsKey(EVCBConfiguration.Header_PartnerId)
               && actionContext.Request.Headers.ContainsKey(EVCBConfiguration.Header_Signature)
               && actionContext.Request.Headers.ContainsKey(EVCBConfiguration.Header_Timestamp))
            {
                string timestamp = actionContext.Request.Headers[EVCBConfiguration.Header_Timestamp].FirstOrDefault();
                string PartnerId = actionContext.Request.Headers[EVCBConfiguration.Header_PartnerId].FirstOrDefault();

                long _timestamp = 0;
                if (!long.TryParse(timestamp, out _timestamp))
                {
                    formatMessage = string.Format("{0} ", "Timestamp's Format is incorrect.");
                    return false;
                }
                Guid _PartnerId = Guid.Empty;
                if (!Guid.TryParse(PartnerId, out _PartnerId))
                {
                    formatMessage = string.Format("{0} ", "PartnerId''s Format is incorrect.it must be Guid type");
                    return false;
                }

                return true;
            }
            else
            {
                return false;
            }
        }

        private static bool IsExpiryTime(string timestamp)
        {
            bool result = true;
            long minTime = DateTimeOffset.UtcNow.AddSeconds(-300).ToUnixTimeSeconds();
            long maxTime = DateTimeOffset.UtcNow.AddSeconds(300).ToUnixTimeSeconds();
            long requestTime = 0;

            if (long.TryParse(timestamp, out requestTime))
            {
                if (minTime < requestTime && maxTime > requestTime)
                {
                    result = false;
                }
            }

            return result;
        }


        private static string GenerateSignature(string unencodeText)
        {
            if ((unencodeText == null) || (unencodeText.Length == 0))
            {
                return String.Empty;
            }
            unencodeText = unencodeText.ToLower();

            MD5 md5 = new MD5CryptoServiceProvider();
            byte[] textToHash = Encoding.UTF8.GetBytes(unencodeText);
            byte[] result = md5.ComputeHash(textToHash);
            return BitConverter.ToString(result).Replace("-", "").ToLower();

        }
    }
}