using HRD.LDAPService.JWT;
using HRD.LDAPService.Ldap;
using Microsoft.Extensions.Options;
using System;
using System.DirectoryServices.AccountManagement;
using System.Linq;

namespace HRD.LDAPService
{
    public class LdapAuthenticationService
    {
        private readonly string LDAP_DOMAIN;

        public LdapAuthenticationService(IOptions<LdapOptions> options)
        {
            LDAP_DOMAIN = options.Value.LDAP_DOMAIN;
        }

        private UserPrincipal GetUserPrincipal(string loginName, PrincipalContext principalContext)
        {
            try
            {
                UserPrincipal userPrincipal = UserPrincipal.FindByIdentity(principalContext, IdentityType.SamAccountName, loginName);
                if (userPrincipal == null)
                {
                    userPrincipal = UserPrincipal.FindByIdentity(principalContext, loginName);
                    if (userPrincipal == null)
                    {
                        throw new Exception($"Can't find an user by name: '{loginName}'");
                    }
                }

                return userPrincipal;
            }
            catch (Exception ex)
            {
                throw new Exception($"Login failed wrong user credentials '{loginName}'", ex);
            }
        }

        /// <summary>
        /// Returns a User without LDAP user authentication
        /// </summary>
        /// <param name="ldapUser"></param>
        /// <returns></returns>
        public LdapUser RenewIdentity(string token)
        {
            if (string.IsNullOrEmpty(token)) { throw new ArgumentNullException("Token is empty!"); }

            try
            {
                LdapUser ldapUserFromToken = JwtManager.DecryptTokenAsLdapUser(token);
                if (ldapUserFromToken == default)
                {
                    throw new Exception($"Wrong token");
                }

                using PrincipalContext principalContext = new PrincipalContext(ContextType.Domain, LDAP_DOMAIN);
                ldapUserFromToken.IsValidatCredentials = true;
                UpdateLdapUserFromPrincipalContext(ref ldapUserFromToken, principalContext);
                return ldapUserFromToken;
            }
            catch (Exception ex)
            {
                throw new Exception($"Renew failed", ex);
            }
        }

        /// <summary>
        /// Returns a User without LDAP user authentication
        /// </summary>
        /// <param name="ldapUser"></param>
        /// <returns></returns>
        public LdapUser RenewIdentity(LdapUser ldapUser)
        {
            if (ldapUser == default) { return default; }
            try
            {
                if (String.IsNullOrEmpty(ldapUser.LoginName))
                {
                    throw new Exception($"Renew Login failed empty user Loginname");
                }

                LdapUser ldapUserFromToken = JwtManager.DecryptTokenAsLdapUser(ldapUser.Token);
                if (ldapUserFromToken == default)
                {
                    throw new Exception($"Wrong token");
                }

                if (!string.Equals(ldapUserFromToken.LoginName, ldapUser.LoginName, StringComparison.OrdinalIgnoreCase))
                {
                    throw new Exception($"Loginname and Token-Loginname are not the same");
                }

                if (ldapUser.IsRealLDAPUser)
                {
                    ldapUserFromToken.IsRealLDAPUser = ldapUser.IsRealLDAPUser;
                    using PrincipalContext principalContext = new PrincipalContext(ContextType.Domain, LDAP_DOMAIN);
                    ldapUser.IsValidatCredentials = true;
                    UpdateLdapUserFromPrincipalContext(ref ldapUserFromToken, principalContext);
                }
                else
                {
                    ldapUserFromToken.IsRealLDAPUser = false;
                    ldapUserFromToken.AddPasswordHash(ldapUser.PasswordHash);

                    if (!string.Equals(ldapUserFromToken.PasswordHashShort, ldapUser.PasswordHashShort, StringComparison.OrdinalIgnoreCase))
                    {
                        throw new Exception($"PasswordHashShort and Token-PasswordHashShortare not the same");
                    }

                    ldapUserFromToken.IsValidatCredentials = !string.IsNullOrEmpty(ldapUserFromToken.PasswordHash);
                    ldapUserFromToken.Enabled = ldapUserFromToken.IsValidatCredentials;

                    ldapUserFromToken.BadLogonCount = ldapUserFromToken.IsValidatCredentials ? 0 : ldapUserFromToken.BadLogonCount + 1;

                    //ldapUserFromToken.IsAccountLockedOut = ;
                    //ldapUserFromToken.LdapName = ;
                    //ldapUserFromToken.LdapSurname = ;
                    //ldapUserFromToken.LdapGuid = ;
                    //ldapUserFromToken.Email = ;
                    //ldapUserFromToken.AccountLockoutTime = ;
                }
                return ldapUserFromToken;
            }
            catch (Exception ex)
            {
                throw new Exception($"Login failed wrong user credentials '{ldapUser.LoginName}'", ex);
            }
        }

        /// <summary>
        /// Returns a User after LDAP user authentication
        /// </summary>
        /// <param name="ldapUser"></param>
        /// <returns></returns>
        public bool CheckAndUpdateIdentityWithPassword(LdapUser ldapUser)
        {
            if (ldapUser == default) { return false; }
            try
            {
                if (String.IsNullOrEmpty(ldapUser.LoginName))
                {
                    throw new Exception($"Login failed wrong user Loginname");
                }

                if (!JwtTokenConfig.DeaktivateLDAP)
                {
                    ldapUser.IsRealLDAPUser = true;
                    using var principalContext = new PrincipalContext(ContextType.Domain, LDAP_DOMAIN);
                    //Check PWD
                    ldapUser.IsValidatCredentials = principalContext.ValidateCredentials(ldapUser.LoginName, ldapUser.Password);

                    UpdateLdapUserFromPrincipalContext(ref ldapUser, principalContext);
                }
                else
                {
                    ldapUser.IsRealLDAPUser = false;

                    //ldapUser.AddPasswordHash(JWTCrypt.GenerateHashPassword(ldapUser.Password));
                    var hash = JWTCrypt.SHA512(ldapUser.Password);
                    ldapUser.AddPasswordHash(hash);
                    ldapUser.IsValidatCredentials = !string.IsNullOrEmpty(ldapUser.PasswordHash);
                    if (ldapUser.IsValidatCredentials)
                    {
                        ldapUser.Enabled = true;
                        ldapUser.BadLogonCount = 0;
                        ldapUser.LastBadPasswordAttempt = null;
                    }
                    else
                    {
                        ldapUser.Enabled = false;
                        ldapUser.BadLogonCount = +1;
                        ldapUser.LastBadPasswordAttempt = DateTime.UtcNow;
                    }

                    //ldapUser.IsAccountLockedOut = ;
                    //ldapUser.LdapName = ;
                    //ldapUser.LdapSurname = ;
                    //ldapUser.LdapGuid = ;
                    //ldapUser.Email = ;
                    //ldapUser.AccountLockoutTime = ;
                }

                return true;
            }
            catch (Exception ex)
            {
                ldapUser.IsValidatCredentials = false;
                throw new Exception($"Login failed wrong user credentials '{ldapUser.LoginName}'", ex);
            }
        }

        private void UpdateLdapUserFromPrincipalContext(ref LdapUser ldapUser, PrincipalContext principalContext)
        {
            UserPrincipal userPrincipal = GetUserPrincipal(ldapUser.LoginName, principalContext);
            if (userPrincipal == default)
            {
                throw new Exception($"Renew Login failed wrong user credentials '{ldapUser.LoginName}'");
            }

            ldapUser.IsAccountLockedOut = userPrincipal.IsAccountLockedOut();
            ldapUser.BadLogonCount = userPrincipal.BadLogonCount;
            ldapUser.Enabled = userPrincipal.Enabled ?? false;
            ldapUser.LastBadPasswordAttempt = userPrincipal.LastBadPasswordAttempt;
            ldapUser.LdapName = userPrincipal.Name;
            ldapUser.LdapSurname = userPrincipal.Surname;
            ldapUser.LdapGuid = userPrincipal.Guid;
            ldapUser.Email = userPrincipal.EmailAddress;
            ldapUser.AccountLockoutTime = userPrincipal.AccountLockoutTime;

            ldapUser.RoleList = ldapUser.RoleList.Union(JWT.JwtTokenConfig.JwtRoleList).ToList();

            if (ldapUser.RoleList?.Count > 0)
            {
                ldapUser = userPrincipal.Context.CheckAndAddGroupMembers(userPrincipal, ldapUser);
            }
        }
    }
}