using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Security.Principal;
using System.Threading.Tasks;
using BO.AppServer.Business.Exceptions;
using BO.AppServer.Business.Mapper;
using BO.AppServer.Business.Security;
using BO.AppServer.Business.Services.Base;
using BO.AppServer.Data.Entity;
using BO.AppServer.Metadata.Configuration;
using BO.AppServer.Metadata.Dto;
using BO.AppServer.Metadata.Enums;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Quadarax.Foundation.Core.Business.Security;
using Quadarax.Foundation.Core.Data;
using Quadarax.Foundation.Core.Data.Interface;
using Quadarax.Foundation.Core.Data.Interface.Entity.Dto;
using Quadarax.Foundation.Core.Exceptions;

namespace BO.AppServer.Business.Services
{
    public class UserService : UserContextService
    {
        #region *** Private fields ***
        private UserManager<IdentityUser> _manUser;
        private RoleManager<IdentityRole> _manRole;

        protected WorkspaceRepo _repoWorkspace;
        #endregion

        #region *** Constructor & DI ***
        public UserService(
            WorkspaceRepo repoWorkspace,
            AccessService srvAccessService,
            RoleManager<IdentityRole> roleManager, 
            IOptions<Configuration> config,
            UserRepo repoUser,
            UserManager<IdentityUser> userManager,
            IPrincipal currentPrincipal,
            ILoggerFactory logger) : base(config, repoUser, userManager, currentPrincipal, logger)
        {
            _manUser = userManager ?? throw new ArgumentNullException(nameof(userManager));
            _manRole = roleManager ?? throw new ArgumentNullException(nameof(roleManager));

            _repoWorkspace = repoWorkspace ?? throw new ArgumentNullException(nameof(repoWorkspace));

        }
        #endregion

        #region *** Public Operations ***
        public async Task<ResultsValueDto<UserRDto>> GetAllUsersAsync(UserScopeEnums scope, PagingDto paging)
        {
            List<User> users = null;
            switch (scope)
            {
                case UserScopeEnums.All:
                    users = _repoUser.Query(paging).ToList();
                    break;
                case UserScopeEnums.Locked:
                    users = _repoUser.Query(paging).Where(x=>x.IsEnabled==false).ToList();
                    break;
                case UserScopeEnums.System:
                    users = _repoUser.Query(paging).Where(x=>x.IsSystem==true).ToList();
                    break;
            }
            return new ResultsValueDto<UserRDto>(users.MapList<User, UserRDto>());
        }


        public async Task<ResultValueDto<UserRDto>> GetUserAsync(IdentificationTypeEnum identificationType, string identificationValue)
        {
            var user = identificationType == IdentificationTypeEnum.Id ?
                _repoUser.Get(ArgumentAsLong(nameof(identificationValue), identificationValue))
                : _repoUser.GetByName(identificationValue);


            if (user == null)
                throw new NoDataException();
            
            // gather rest of data
            var result = new ResultValueDto<UserRDto>(user.Map<User, UserRDto>());
            return result;
        }

        public async Task<ResultValueDto<UserRDto>> CreateUserAsync(UserCDto newUser)
        {
            //TODO: role validation
            return await CreateUserAsync(newUser.Name, newUser.Password,newUser.Email,newUser.LangLcid,newUser.Roles.Select(Enum.Parse<RoleEnum>).ToArray());
        }

        public async Task<ResultValueDto<UserRDto>> CreateUserAsync(string userName,string userPassword, string userEmail, int prefferedLCID, RoleEnum[] roles)
        {

            try
            {
                // Validations
                var exists = await ExistsUserByNameAsync(userName);
                if (exists.Value)
                    throw new CodeException(1100, $"Cannot create user '{userName}', user name or email already exists.");

                // Create Identity User
                var idtUser = await _manUser.FindByNameAsync(userName);
                if (idtUser == null)
                {
                    idtUser = new IdentityUser(userName)
                    {
                        NormalizedUserName = userName.ToLower(CultureInfo.InvariantCulture),
                        Email = userEmail,
                        NormalizedEmail = userEmail.ToLower(CultureInfo.InvariantCulture),
                        LockoutEnabled = true,
                        PasswordHash = PasswordHelper.HashPassword(userPassword)
                    };

                    await _manUser.CreateAsync(idtUser);
                    Log.Log(LogLevel.Debug, $"User '{userName}' created in IF.");
                }
                else
                    Log.Log(LogLevel.Debug, $"User '{userName}' already exists in IF.");


                // Append roles
                var currentRoles = await _manUser.GetRolesAsync(idtUser);
                await _manUser.RemoveFromRolesAsync(idtUser, currentRoles.ToArray());

                foreach (var role in roles)
                {
                    await _manUser.AddToRoleAsync(idtUser, role.ToString().ToUpper());
                    Log.Log(LogLevel.Debug, $"User '{userName}' add to role '{role}' in IF.");
                }


                // Create BO user
                var user = _repoUser.GetByIfReference(idtUser.Id);
                if (user == null)
                {
                    user = _repoUser.New();
                    user.Ifreference = idtUser.Id;
                    user.Name = userName;
                    user.LangLcid = prefferedLCID;
                    user.Created = DateTime.Now;
                    user.IsEnabled = false;
                    user.LangLcid = prefferedLCID;
                    user.IsSystem = roles.Contains(RoleEnum.System);
                    user.IsEnabled = true;
                    _repoUser.Set(user);
                    Log.Log(LogLevel.Debug,
                        $"User '{userName}' [{user.Id}] created as enabled={user.IsEnabled} in BO.");
                    user = _repoUser.Get(user.Id);
                }
                else
                {
                    user.Name = idtUser.UserName;
                    user.LangLcid = prefferedLCID;
                    user.Modified = DateTime.Now;
                    user.IsSystem = roles.Contains(RoleEnum.System);
                    _repoUser.Set(user);
                    Log.Log(LogLevel.Debug,
                        $"User '{userName}' [{user.Id}] was recovered in BO.");
                }

                _repoUser.Commit();
                return new ResultValueDto<UserRDto>(user.Map<User, UserRDto>());
            }
            catch (Exception e)
            {
                return new ResultValueDto<UserRDto>(e);
            }
        }

        public async Task<ResultBoolDto> ExistsUserByNameAsync(string userName)
        {
            var user = await _manUser.FindByNameAsync(userName);
            if (user == null)
                    return new ResultBoolDto(false);

            var exists = _repoUser.Query(new Paging()).Any(x => x.Name.ToLower() == userName.ToLower(CultureInfo.InvariantCulture) && x.Ifreference == user.Id);
            return new ResultBoolDto(exists);
        }

        public async Task<ResultBoolDto> EnusreUserRolesAsync(string userName, RoleEnum[] roles)
        {
            var idtUser = await _manUser.FindByNameAsync(userName);
            if (idtUser == null)
                return new ResultBoolDto(false);

            var currentRoles = await _manUser.GetRolesAsync(idtUser);
            await _manUser.RemoveFromRolesAsync(idtUser, currentRoles.ToArray());

            foreach (var role in roles)
            {
                await _manUser.AddToRoleAsync(idtUser, role.ToString().ToUpper());
                Log.Log(LogLevel.Debug, $"User '{userName}' add to role '{role}' in IF.");
            }

            return new ResultBoolDto(true);
        }

        public ResultBoolDto ExistsUserByIfReference(string ifReference)
        {
            var exists = _manUser.FindByIdAsync(ifReference).Result;
            return new ResultBoolDto(exists!=null);
        }

        public async Task EnsureRolesAsync()
        {
            var roles = RoleUtils.GetAllRoles();
            foreach (var roleName in roles)
            {
                if (!await _manRole.RoleExistsAsync(roleName))
                {
                    var role = new IdentityRole(roleName);
                    role.NormalizedName = roleName.ToUpper(CultureInfo.InvariantCulture);
                    await _manRole.CreateAsync(role);
                    Log.Log(LogLevel.Information, $"Role '{roleName}' [{role.Id}] created.");
                }
            }
        }

        /*
        public ResultValueDto<UserRDto> SetUser(long userId, UserUDto user)
        {

        }

        public ResultValueDto<UserRDto> SetUserLock(long userId, bool flagEnabledDisabled)
        {

        }

        public ResultValueDto<UserRDto> SetUserLogAudit(long userId)
        {

        }
        */
        #endregion
    }
}
