using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using System.Threading;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;

using Newtonsoft.Json;

using C4IT.Security;
using C4IT.Logging;
using C4IT.XML;
using C4IT.FASD.Base;
using C4IT.DataHistoryProvider;
using static C4IT.Logging.cLogManager;
using System.Web.Http.Controllers;
using System.Reflection;
using Microsoft.Ajax.Utilities;

namespace F4SDwebService
{
    public class cAuthentication : DelegatingHandler
    {
        private const string WwwAuthenticateHeader = "WWW-Authenticate";
        private const string Basic = "Basic";

        public cDataHistoryCollector Collector = null;
        public Dictionary<string, cOneTimePW> OTPWs = null;

        protected override async Task<HttpResponseMessage> SendAsync(
            HttpRequestMessage request, CancellationToken cancellationToken)
        {
            if (Collector == null || OTPWs == null)
                return await base.SendAsync(request, cancellationToken);

            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
            try
            {

                var Path = request.RequestUri.LocalPath.ToLowerInvariant();

                var UserInfo = ValidateBearer(request);

                if (UserInfo != null)
                {
                    request.Properties.Add("F4SD_UserInfo", UserInfo);
                    return await base.SendAsync(request, cancellationToken);
                }

                if (!cLogManager.DefaultLogger.IsDebug
                    && !Path.EndsWith("checkconnection")
                    && !Path.EndsWith("logon/logon")
                    )
                {
                    var response = ValidateOTP(request);
                    if (response != null)
                    {
                        var tsc = new TaskCompletionSource<HttpResponseMessage>();
                        tsc.SetResult(response);
                        return await tsc.Task;
                    }
                }
                var _res = await base.SendAsync(request, cancellationToken);
                return _res;
            }
            catch (System.OperationCanceledException EC)
            {
                LogEntry($"The operation of the request '{request.RequestUri} was canceled.'", LogLevels.Debug);
                LogException(EC, LogLevels.Debug);
            }
            catch (Exception E)
            {
                LogException(E);
            }
            finally
            {
                if (CM != null) LogMethodEnd(CM);
            }

            var responseErr = new HttpResponseMessage(HttpStatusCode.InternalServerError);
            var tsc2 = new TaskCompletionSource<HttpResponseMessage>();
            tsc2.SetResult(responseErr);
            return await tsc2.Task;
        }

        private cF4sdUserInfo ValidateBearer(HttpRequestMessage request)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
            try
            {
                if (Collector == null)
                    return null;

                var authenticationHeaderValue = request.Headers.Authorization;
                if (authenticationHeaderValue == null || string.IsNullOrEmpty(authenticationHeaderValue.Scheme) || !authenticationHeaderValue.Scheme.Equals("Bearer") || string.IsNullOrEmpty(authenticationHeaderValue.Parameter))
                {
                    LogEntry($"no valid bearer authentication header", LogLevels.Debug);
                    return null;
                }

                var _res = Collector.JwtTokenHandler.ValidateToken(authenticationHeaderValue.Parameter, Collector.JwtTokenValidationParameters, out var _res2);

                if (!(_res?.Identity?.IsAuthenticated == true))
                {
                    LogEntry($"bearer token could not validated", LogLevels.Debug);
                    return null;
                }

                if (!(_res2 is JwtSecurityToken _sec))
                {
                    LogEntry($"bearer token is not a JwtSecurityToken", LogLevels.Debug);
                    return null;
                }

                var _now = DateTime.UtcNow;
                if (_sec.ValidFrom > _now || _sec.ValidTo < _now)
                {
                    LogEntry($"bearer token could is not a JwtSecurityToken", LogLevels.Debug);
                    return null;
                }

                var strNameId = _sec.Claims.First(c => c.Type == JwtRegisteredClaimNames.NameId)?.Value;
                if (!Guid.TryParse(strNameId, out var _nameId))
                {
                    LogEntry($"bearer token has no valid NameId property", LogLevels.Debug);
                    return null;
                }

                var _retVal = new cF4sdUserInfo() { Id = _nameId, ValidUntil = _sec.ValidTo, LogonTime = _sec.ValidFrom };

                _retVal.Name = _sec.Claims.First(c => c.Type == JwtRegisteredClaimNames.UniqueName).Value;
                var strAuthTime = _sec.Claims.First(c => c.Type == JwtRegisteredClaimNames.AuthTime).Value;
                if (DateTime.TryParseExact(strAuthTime, "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fff'z'", null, System.Globalization.DateTimeStyles.AssumeUniversal, out var _AuthTime))
                    _retVal.LogonTime = _AuthTime;

                var _strLoginMethod = _sec.Claims.First(c => c.Type == "login_method").Value;
                _retVal.AccountType = cXmlParser.GetEnumFromString<cF4sdUserInfo.enumAccountType>(_strLoginMethod, cF4sdUserInfo.enumAccountType.unknown);

                _retVal.AdAccount = _sec.Claims.First(c => c.Type == "login_account").Value;
                _retVal.AdSid = _sec.Claims.First(c => c.Type == "account_sid").Value;
                _retVal.Language = _sec.Claims.First(c => c.Type == "language").Value;

                var _roles = _sec.Claims.Where(c => c.Type == "roles")?.Select(c => c.Value)?.ToList();
                if (_roles?.Count() > 0)
                    _retVal.Roles = _roles;

                const string cockpitUserRole = "Cockpit.User";
                if (!_roles.Contains(cockpitUserRole))
                {
                    LogEntry($"bearer roles does not contain the role \"{cockpitUserRole}\"", LogLevels.Debug);
                    return null;
                }

                if (cLogManager.DefaultLogger.IsDebug)
                {
                    var _str = JsonConvert.SerializeObject(_retVal, Formatting.Indented);
                    var _lst = new List<string>(2) { "Bearer user info:", _str };
                    cLogManager.DefaultLogger.LogList(LogLevels.Debug, _lst);
                }
                return _retVal;
            }
            catch (Exception E)
            {
                LogException(E);
            }
            finally
            {
                if (CM != null) LogMethodEnd(CM);
            }

            return null;
        }

        private HttpResponseMessage ValidateOTP(HttpRequestMessage request)
        {
            bool auth = false;
            try
            {
                var authenticationHeaderValue = request.Headers.Authorization;

                if (authenticationHeaderValue == null || string.IsNullOrEmpty(authenticationHeaderValue.Scheme) || !authenticationHeaderValue.Scheme.Equals(Basic) || string.IsNullOrEmpty(authenticationHeaderValue.Parameter))
                {
                    var response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
                    response.Headers.Add(WwwAuthenticateHeader, Basic);
                    return response;
                }

                string authValue = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(authenticationHeaderValue.Parameter));

                string[] authParts = authValue.Split(':');

                if (authParts.Length == 2)
                {
                    if (OTPWs == null)
                        cLogManager.DefaultLogger.LogEntry(LogLevels.Debug, "no OTP providers configured.");
                    cOneTimePW OTP;
                    if ((OTPWs != null) && OTPWs.TryGetValue(authParts[0], out OTP))
                    {
                        UInt32 Id;
                        if (UInt32.TryParse(authParts[1], System.Globalization.NumberStyles.Integer, null, out Id))
                        {
                            auth = OTP.Check(Id);
                        }
                    }
                }
                else
                    cLogManager.DefaultLogger.LogEntry(LogLevels.Debug, string.Format("Key contains {0} items!", authParts.Length));
            }
            catch { }

            if (!auth)
            {
                var response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
                response.Headers.Add(WwwAuthenticateHeader, Basic);
                return response;
            }
            return null;
        }

        static public cF4sdUserInfo GetUserInfo(HttpActionContext context)
        {
            try
            {
                var _props = context?.Request?.Properties;
                if (_props == null)
                    return null;

                if (!_props.TryGetValue("F4SD_UserInfo", out var _objUI))
                    return null;

                if (_objUI is cF4sdUserInfo userInfo)
                    return userInfo;
            }
            catch (Exception E)
            {
                LogException(E);
            }

            return null;
        }
    }

}