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

namespace BO.AppServer.Business.Services
{
    public class AccessService : AbstractService
    {
        private const string CS_SESSION_MAGIC = "magic";
        private const string CS_SESSION_TOKEN = "token";

        private SignInManager<IdentityUser> _manSignIn;
        private readonly UserManager<IdentityUser> _manUsers;
        private readonly UserRepo _repoUsers;
        private readonly IHttpContextAccessor _context;
        private readonly Configuration _configuration;
        public AccessService(IOptions<Configuration> config,
            SignInManager<IdentityUser> manSignIn,
            UserManager<IdentityUser> manUsers,
            UserRepo repoUsers,
            IPrincipal currentPrincipal, ILoggerFactory logger, 
            IHttpContextAccessor context) : base(currentPrincipal, logger)
        {
            _configuration = config.Value;
            _manUsers = manUsers ?? throw new ArgumentNullException(nameof(manUsers));
            _repoUsers = repoUsers ?? throw new ArgumentNullException(nameof(repoUsers));
            _context = context ?? throw new ArgumentNullException(nameof(context));
            _manSignIn = manSignIn ?? throw new ArgumentNullException(nameof(manSignIn));
        }

        public void OpenMagic(string magic)
        {
            _context.HttpContext.Session.SetString(CS_SESSION_MAGIC , magic);
            CheckMagic();
            _context.HttpContext.Session.SetString(CS_SESSION_TOKEN , "TKN" + Guid.NewGuid().ToString("N"));
        }

        public string GetToken()
        {
            return _context.HttpContext.Session.GetString(CS_SESSION_TOKEN);
        }

        public void CheckMagic()
        {
            if (!_configuration.Api.Console.MagicKeys.Contains(_context.HttpContext.Session.GetString(CS_SESSION_MAGIC)))
                throw new UnauthorizedAccessException("Wrong magic key to access to BO.AppServer api!");
        }
        public void CheckToken(string token)
        {
            if (string.IsNullOrEmpty(token) || _context.HttpContext.Session.GetString(CS_SESSION_TOKEN) != token)
                throw new UnauthorizedAccessException("Wrong token to access to BO.AppServer api!");
        }

        public async Task<ResultValueDto<UserRDto>> Login(string userName, string userPassword, RoleEnum loginForRole)
        {
            var idtUser = await _manUsers.FindByNameAsync(userName);
            if (idtUser == null)
            {
                return new ResultValueDto<UserRDto>(new CodeException(1201,
                    $"User '{userName}' doesn't exists!"));
            }
            var isApproved = await _manUsers.IsInRoleAsync(idtUser, loginForRole.ToString());
            if (!isApproved)
            {
                return new ResultValueDto<UserRDto>(new CodeException(1200,
                    $"User '{userName}' has no permission to login in. Access denied!"));
            }

            var user = _repoUsers.GetByIfReference(idtUser.Id);
            if (!user.IsEnabled)
            {
                Log.LogWarning($"User '{userName}' is disabled for log-in!");
                return new ResultValueDto<UserRDto>(new CodeException(1202,
                    $"User '{userName}' is disabled for log-in!"));
            }


            var result = await _manSignIn.PasswordSignInAsync(userName, userPassword, false, lockoutOnFailure: false);
            if (result.Succeeded)
            {
               
                user.LastLogged = DateTime.Now;
                _repoUsers.Set(user);
                _repoUsers.Commit();
                Log.LogInformation($"User '{userName}' [{user.Id}/{idtUser.Id}] logged in.");
                return new ResultValueDto<UserRDto>(user.Map<User, UserRDto>());
            }
            if (result.RequiresTwoFactor)
            {
                Log.LogWarning($"Two-factor sign for '{userName}' is set but not supported. Contact administrator.");
                return new ResultValueDto<UserRDto>(new Exception(
                    $"Two-factor sign for '{userName}' is set but not supported. Contact administrator."));
            }
            if (result.IsLockedOut)
            {
                Log.LogWarning($"User '{userName}' is locked out.");
                return new ResultValueDto<UserRDto>(new CodeException(1202, $"User '{userName}' is locked out!"));
            }
            
            Log.LogWarning($"Invalid login attempt for user '{userName}'.");
            return new ResultValueDto<UserRDto>(new CodeException(1203, $"Invalid login attempt for user '{userName}'."));
        }
    }
}
