using System;
using System.Configuration;
using System.Linq;
using System.Security.Claims;
using System.Text.RegularExpressions;
using System.Threading;
using iiie.Authentication.DBO;
using Newtonsoft.Json;

namespace iiie.Authentication.Business
{
    public abstract class AuthProvider<TUserDbo>
        where TUserDbo : UserDboAuth
    {
        /// <summary>
        /// Application specific settings
        /// </summary>
        public static AuthProvider<TUserDbo> Instance { get; set; }

        /// <summary>
        /// The registered user dbo, or null if no user is logged
        /// </summary>
        public static TUserDbo UserDbo
        {
            get
            {
                var claim = ((ClaimsIdentity)Thread.CurrentPrincipal.Identity).Claims.FirstOrDefault(x => x != null && x.Type == "__userdbo__");
                if (claim == null)
                    return null;
                return JsonConvert.DeserializeObject<TUserDbo>(claim.Value);
            }
            set
            {
                var claim = new Claim("__userdbo__", JsonConvert.SerializeObject(value));
                ((ClaimsIdentity)Thread.CurrentPrincipal.Identity).AddClaim(claim);
            }
        }

        /// <summary>
        /// Check if this is a valid email address
        /// </summary>
        /// <param name="email">The email to test</param>
        /// <returns>True if the email is valid, false otherwise</returns>
        public static bool IsValidEmail(string email)
        {
            try
            {
                return Regex.IsMatch(email,
                      @"^(?("")("".+?(?<!\\)""@)|(([0-9a-z]((\.(?!\.))|[-!#\$%&'\*\+/=\?\^`\{\}\|~\w])*)(?<=[0-9a-z])@))" +
                      @"(?(\[)(\[(\d{1,3}\.){3}\d{1,3}\])|(([0-9a-z][-\w]*[0-9a-z]*\.)+[a-z0-9][\-a-z0-9]{0,22}[a-z0-9]))$",
                      RegexOptions.IgnoreCase, TimeSpan.FromMilliseconds(250));
            }
            catch (RegexMatchTimeoutException)
            {
                return false;
            }
        }

        /// <summary>
        /// Get a int from config file, or return the default value
        /// </summary>
        /// <param name="key">The config file key</param>
        /// <param name="def">The default value</param>
        /// <returns>The found int</returns>
        protected int GetInt(string key, int def)
        {
            var valueStr = ConfigurationManager.AppSettings[key];
            if (valueStr == null)
                return def;
            int value;
            if (int.TryParse(valueStr, out value))
                return value;
            return def;
        }

        /// <summary>
        /// Get a string from config file, or return the default value
        /// </summary>
        /// <param name="key">The config file key</param>
        /// <param name="def">The default value</param>
        /// <returns>The found string</returns>
        protected string GetString(string key, string def)
        {
            var value = ConfigurationManager.AppSettings[key];
            if (value == null)
                return def;
            return value;
        }
        /// <summary>
        /// Contructs a user dbo from the specified username and salt and register it into UserDbo
        /// </summary>
        /// <param name="username">The username of the verified token</param>
        /// <param name="salt">The salt in the token</param>
        /// <returns>The user dbo, or null if user is not valid</returns>
        public TUserDbo GetAndRegisterUserDbo(string username, string salt)
        {
            return UserDbo = GetUserDbo(username, salt);
        }

        /// <summary>
        /// Contructs a user dbo from the specified username and salt
        /// </summary>
        /// <param name="username">The username of the verified token</param>
        /// <param name="salt">The salt in the token</param>
        /// <returns>The user dbo, or null if user is not valid</returns>
        public abstract TUserDbo GetUserDbo(string username, string salt);

        /// <summary>
        /// Const value to compute password hash
        /// </summary>
        public virtual int GetPasswordSaltByteSize()
        {
            return GetInt("PasswordSaltByteSize", 42);
        }

        /// <summary>
        /// Const value to compute password hash
        /// </summary>
        public virtual int GetPasswordHashByteSize()
        {
            return GetInt("PasswordHashByteSize", 42);
        }

        /// <summary>
        /// Const value to compute password hash
        /// </summary>
        public virtual int GetPasswordIterations()
        {
            return GetInt("PasswordIterations", 2048);
        }

        /// <summary>
        /// Returns a string used to validate the token.
        /// Must be constant. Default is app setting 'ValidatorString'
        /// </summary>
        /// <returns>The string</returns>
        public virtual string GetValidatorString()
        {
            return GetString("ValidatorString", "__default__");
        }

        /// <summary>
        /// Returns a string used to sign the token
        /// Must be constant. Default is app setting 'CredentialKey'
        /// </summary>
        /// <returns>The string</returns>
        public virtual string GetCredentialKey()
        {
            return GetString("CredentialKey", "__default__");
        }
    }
}
