using DAL._Shared.SharedModels;
using DAL._Shared.SharedRepositories;
using HRD.LDAPService;
using HRD.LDAPService.JWT;
using Microsoft.Extensions.Primitives;
using System;
using System.Threading.Tasks;

namespace StaffDBServer.SharedControllers
{
    public class WebAppUserHelper
    {
        private const int GLB_LOGIN_LOCK_TIME_IN_MIN = 5;

        public int GlbWebApiIdStaffDB { get; private set; } = 2;

        WebAppUserRepository webAppUserRepository;

        WebAppEmployeeInfoRepository webAppEmployeeInfoRepository;

        private readonly JwtManager _jwtManager;

        public WebAppUserHelper(WebAppUserRepository webAppUserRepository, WebAppEmployeeInfoRepository webAppEmployeeInfoRepository, JwtManager jwtManager)
        {
            this.webAppUserRepository = webAppUserRepository;
            this.webAppEmployeeInfoRepository = webAppEmployeeInfoRepository;
            _jwtManager = jwtManager;
        }

        public async Task<WebAppUser> CheckLoginWithJWTAsync(StringValues accessToken, string clientVersion)
        {            
            if (!JwtManager.IsValidatJwtTokenSubject(accessToken))
            {
                throw new UnauthorizedAccessException($"Not valid JWT");
            }

            var ldapUser = JwtManager.DecryptTokenAsLdapUser(accessToken);
            if (ldapUser == null)
            {
                throw new UnauthorizedAccessException($"Unable to decrypt JWT");
            }
                        
            WebAppEmployeeInfo webAppEmployeeInfo = await webAppEmployeeInfoRepository.GetByAsync(x => x.LoginName == ldapUser.LoginName);
            if (webAppEmployeeInfo == default)
            {
                throw new UnauthorizedAccessException($"User '{ldapUser.LoginName}' cannot be found in StaffDB.");
            }

            var userFromDB = await webAppUserRepository.GetByAsync(u => u.LoginName == ldapUser.LoginName, false);
            if (userFromDB == default)
            {
                throw new UnauthorizedAccessException($"Unable to load WebApiUser:{ldapUser.LoginName}");
            }

            if (userFromDB.JwtExpiredOn == null)
            {
                throw new UnauthorizedAccessException($"User have to relogin; LoginName: {ldapUser.LoginName}");
            }

            userFromDB.RoleList = ldapUser.RoleListAsString(EN_LdapRoleListFilter.OnlyRoleList);
            userFromDB.WebAppRoleList = ldapUser.RoleListAsString(EN_LdapRoleListFilter.OnlyWebAppRoleList);
            userFromDB.Token = ldapUser.Token;
            userFromDB.JwtExpiredOn = ldapUser.JwtExpiredOn;
            userFromDB.LastLogin = DateTime.Now;
            userFromDB.ClientVersion = clientVersion;

            if (await webAppUserRepository.SaveChangesAsync())
            {
                return userFromDB;
            }
            return (default);
        }

        public async Task<WebAppUser> CheckLoginWithNameAndPasswordAsync(WebAppUser userFromClient, int webAppId)
        {
            try
            {                
                WebAppEmployeeInfo webAppEmployeeInfo = await webAppEmployeeInfoRepository.GetByAsync(x => x.LoginName == userFromClient.LoginName && x.WebAppId == webAppId);
            
                return await DoCheckLoginWithNameAndPasswordAsync(userFromClient, webAppUserRepository, webAppEmployeeInfoRepository);
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        public async Task<WebAppUser> CheckLoginWithNameAndPasswordAsync(WebAppUser userFromClient)
        {
            try
            {
                WebAppEmployeeInfo webAppEmployeeInfo = await webAppEmployeeInfoRepository.GetByAsync(x => x.LoginName == userFromClient.LoginName && x.WebAppId == GlbWebApiIdStaffDB);
            
                return await DoCheckLoginWithNameAndPasswordAsync(userFromClient, webAppUserRepository, webAppEmployeeInfoRepository);
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        private async Task<WebAppUser> DoCheckLoginWithNameAndPasswordAsync(WebAppUser userFromClient, WebAppUserRepository webAppUserRepository, WebAppEmployeeInfoRepository webAppEmployeeInfoRepository)
        {
            try
            {
                WebAppEmployeeInfo webAppEmployeeInfo = await webAppEmployeeInfoRepository.GetByAsync(x => x.LoginName == userFromClient.LoginName);
                if (webAppEmployeeInfo == default)
                {
                    if (userFromClient.IsGermanCulture())
                    {
                        throw new UnauthorizedAccessException($"Benutzer '{userFromClient.LoginName}' wurde in der StaffDB nicht freigeschaltet.");
                    }
                    else
                    {
                        throw new UnauthorizedAccessException($"User '{userFromClient.LoginName}' was not set in der StaffDB.");
                    }
                }

                LdapUser ldapUser = new LdapUser(userFromClient.LoginName, webAppEmployeeInfo.EmployeeId, userFromClient.Password, webAppEmployeeInfo.DepartmentId, webAppEmployeeInfo.ExtendedDepartmentIdList);
                if (!_jwtManager.GenerateLdapUserWithJwtToken(ldapUser))
                {
                    if (ldapUser == default)
                    {
                        if (userFromClient.IsGermanCulture())
                        {
                            throw new UnauthorizedAccessException($"Mit den Anmeldeinformationen (Loginname: '{userFromClient.LoginName}') konnte keine Verbindung hergestellt werden");
                        }
                        else
                        {
                            throw new UnauthorizedAccessException($"With login data (Loginname: '{userFromClient.LoginName}') cannot be logged");
                        }
                    }

                    if (ldapUser.IsAccountLockedOut)
                    {
                        System.Globalization.CultureInfo cultureinfo = new System.Globalization.CultureInfo(userFromClient.Culture);
                        var lastBadPasswordAttemptLocalTime = ldapUser.AccountLockoutTime - TimeSpan.FromMinutes(userFromClient.TimeZoneOffsetInMin);
                        var waitTillTime = lastBadPasswordAttemptLocalTime?.AddMinutes(GLB_LOGIN_LOCK_TIME_IN_MIN + 1);

                        if (userFromClient.IsGermanCulture())
                        {
                            throw new UnauthorizedAccessException($"Der Benutzer mit Loginnamen '{ldapUser.LoginName}' wurde am {lastBadPasswordAttemptLocalTime?.ToString(cultureinfo)} gesperrt!\nVersuchen Sie sich um {waitTillTime?.ToString(cultureinfo.DateTimeFormat.ShortTimePattern)} neuanzumelden.");
                        }
                        else
                        {
                            throw new UnauthorizedAccessException($"The user with the loginname '{ldapUser.LoginName}' was locked on {lastBadPasswordAttemptLocalTime?.ToString(cultureinfo)}!\nTry to log again at {waitTillTime?.ToString(cultureinfo.DateTimeFormat.ShortTimePattern)}.");
                        }
                    }

                    if (!ldapUser.Enabled)
                    {
                        if (userFromClient.IsGermanCulture())
                        {
                            throw new UnauthorizedAccessException($"Der Benutzer mit Loginnamen '{userFromClient.LoginName}' ist deaktiviert!");
                        }
                        else
                        {
                            throw new UnauthorizedAccessException($"The user with loginname '{userFromClient.LoginName}' is inactive!");
                        }
                    }

                    if (!ldapUser.IsValidatCredentials)
                    {
                        if (userFromClient.IsGermanCulture())
                        {
                            throw new UnauthorizedAccessException($"Sie haben Ihren Loginnamen '{userFromClient.LoginName}' oder Ihr Passwort falsch eingegeben.\nAchtung, das Passwort wurde {ldapUser.BadLogonCount} Mal falsch eingegeben!");
                        }
                        else
                        {
                            throw new UnauthorizedAccessException($"Your loginname '{userFromClient.LoginName}' or password is wrong.\nAttention, you have input wrong password {ldapUser.BadLogonCount} times!");
                        }
                    }
                }

                WebAppUser userFromDB = await webAppUserRepository.GetByAsync(u => u.LoginName == userFromClient.LoginName, false);
                if (userFromDB == default) //get the WebAppUser data from LDAP & StaffDB
                {
                    userFromDB = new WebAppUser(ldapUser.LoginName, webAppEmployeeInfo.ShortName, ldapUser.RoleListAsString(), $"{webAppEmployeeInfo.FirstName} {webAppEmployeeInfo.LastName}");
                    userFromDB.Language = userFromClient.Language;
                    userFromDB.Culture = userFromClient.Culture;
                    if (!await webAppUserRepository.AddAsync(userFromDB))
                    {
                        if (userFromClient.IsGermanCulture())
                        {
                            throw new UnauthorizedAccessException($"Benutzer '{userFromClient.LoginName}' konnte nicht automatisch erstellt werden.");
                        }
                        else
                        {
                            throw new UnauthorizedAccessException($"User '{userFromClient.LoginName}' cannot be created automatically.");
                        }
                    }
                }

                userFromDB.RoleList = ldapUser.RoleListAsString(EN_LdapRoleListFilter.OnlyRoleList);
                userFromDB.WebAppRoleList = ldapUser.RoleListAsString(EN_LdapRoleListFilter.OnlyWebAppRoleList);
                userFromDB.Token = ldapUser.Token;
                userFromDB.JwtExpiredOn = ldapUser.JwtExpiredOn;
                userFromDB.LastLogin = DateTime.Now;
                userFromDB.ClientVersion = userFromClient.ClientVersion;

                if (await webAppUserRepository.SaveChangesAsync())
                {
                    return userFromDB;
                }
                return (default);
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
    }
}