using Knoks.Core.Data.Interfaces;
using Knoks.Core.Entities;
using Knoks.Core.Entities.Args;
using Knoks.Core.Logic.Interfaces;
using Knoks.Framework.Cryptography;
using Knoks.Framework.Security;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.Extensions.Logging;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Linq;
using System;
using Knoks.Framework.Services;
using System.Security.Cryptography;
using System.Net;

namespace Knoks.Core.Logic.Managers
{
    public class UserManager : IUserManager
    {
        #region Fields
        private readonly ILogger<UserManager> _logger;
        private readonly IUserDao _userDao;
        private readonly IPasswordStrength _passwordStrength;
        private readonly IPasswordProcess _passwordProcess;
        private readonly IEventManager _eventManager;
        private readonly IUserImageManager _userImageManager;
        private readonly IAccountDao _accountDao;
        private readonly IAccountTransactionsSettings _settings;
        private readonly IEmailService _emailService;
        private readonly IGeneralManager _generalManager;

        public User PlatformUser { get; private set; }
        public Account PlatformUserAccount { get; private set; }
        #endregion

        /// <summary>
        /// ctor
        /// </summary>
        /// <param name="logger"></param>
        /// <param name="userDao"></param>
        /// <param name="passwordStrength"></param>
        /// <param name="passwordProcess"></param>
        /// <param name="eventManager"></param>
        /// <param name="userImageManager"></param>
        /// <param name="accountDao"></param>
        /// <param name="settings"></param>
        public UserManager(
            ILogger<UserManager> logger,
            IUserDao userDao,
            IPasswordStrength passwordStrength,
            IPasswordProcess passwordProcess,
            IEventManager eventManager,
            IUserImageManager userImageManager,
            IAccountDao accountDao,
            IGeneralManager generalManager,
            IAccountTransactionsSettings settings)
        {
            _logger = logger;
            _userDao = userDao;
            _passwordStrength = passwordStrength;
            _passwordProcess = passwordProcess;
            _eventManager = eventManager;
            this._userImageManager = userImageManager;
            this._generalManager = generalManager;
            _accountDao = accountDao ?? throw new ArgumentNullException(nameof(accountDao));

            if (settings != null) _settings = settings;
        }

        public async Task<User> CreateUser(CreateUserArgs args)
        {
            var userInfo = await _userDao.CreateUser(args, _passwordProcess.Generate(args.Password).ToFlatString(), _settings == null ? 0 : _settings.NewUserFreeCreditAmount);
            await _eventManager.FireЕvent(this, EventAction.UserCreated, userInfo.user);
            return userInfo.user;
        }

        public async Task<bool> UserExists(string userName)
        {
            return await _userDao.UserNameExists(userName, null);
        }
        public async Task<User> UpdateUserSucsess(long userId)
        {
            return await _userDao.UpdateUserSucsess(userId);
        }

        public async Task<bool> UserEmailExists(string email)
        {
            return await _userDao.UserEmailExists(email);
        }

        public async Task<bool> UserNameExists(string userName, long? userId)
        {
            return await _userDao.UserNameExists(userName, userId);
        }

        public async Task<IEnumerable<User>> GetUsers(long? userId = null, UserType? userType = null)
        {
            return await _userDao.GetUsers(userId, (byte?)userType);
        }

        public async Task<User> GetUser(long userId)
        {
            return await _userDao.GetUser(userId);
        }

        public async Task<User> GetUserByEmail(string email)
        {
            return await _userDao.GetUserByEmail(email);
        }

        public async Task<User> AuthenticateUser(int apiConsumerId, string email, string password)
        {
            _logger.LogDebug($"AuthenticateUser: email='{email}'");

            var obj = await _userDao.GetUserCredential(apiConsumerId, email);
            if (obj == null) return null; //User is not exists

            var savedPasswordResult = new PasswordResult(obj.Password);
            var userPasswordResult = _passwordProcess.Generate(password, savedPasswordResult.Salt);

            return userPasswordResult.IsIdentical(savedPasswordResult) ? obj.User : null;
        }

        public async Task<User> UpdateUser(UpdateUserArgs args)
        {
            _logger.LogDebug($"Update user: apiConsumerId={args.ApiConsumerId}, email='{args.Email}'");
            //if (await AuthenticateUser(args.ApiConsumerId, args.Email, args.Password) == null)
            //{
            //    throw new InvalidOperationException("Invalid password for confirm");
            //}

            System.IO.Stream stream = args.Content != null && args.Content.Length > 0 ? new System.IO.MemoryStream(args.Content) : 
                !string.IsNullOrEmpty(args.ContentBase64) ? Framework.Extentions.FileInfoExtentions.ParseImage(args.ContentBase64) : null;
            args.HasAvatar = args.HasAvatar || stream != null;
            var user = await _userDao.UpdateUser(args);
            if (stream != null)
            {
                try
                {
                    await _userImageManager.SaveAvatar(user.UserId, stream);
                }
                catch {
                    args.HasAvatar = false;
                    user = await _userDao.UpdateUser(args);
                }
            }
            if (user.HasAvatar.HasValue && user.HasAvatar.Value)
            {
                user.AvatarUrl = _generalManager.GetAvatarUrl(user.UserId);
            }
            return user;
        }

        public FileResponse GetAvatarFile(int userId)
        {
            DateTimeOffset modified;
            var stream = _userImageManager.LoadAvatar((long)userId, out modified);
            return new FileResponse() { File = stream, FileDownloadName = "avatar.png", LastModified = modified };
        }

        public async Task<UserAccounts> GetUserInfo(string accountWalletAddress)
        {
            _logger.LogDebug($"Get User Info for wallet address: {accountWalletAddress}");

            if (accountWalletAddress == PlatformUserAccount.WalletAddress)
            {
                return new UserAccounts
                {
                    Accounts = new List<Account>() {PlatformUserAccount},
                    User = PlatformUser
                };
            }

            return await _userDao.GetUserInfo(accountWalletAddress);
        }

        public async Task Initialize()
        {
            var platformUserList = await _userDao.GetUsers(userType: (byte)UserType.Platform);

            if (platformUserList.Count() != 1)
                throw new InvalidOperationException($"Wrong number of platform users: {platformUserList.Count()}");

            PlatformUser = platformUserList.First();

            var platformUserAccount = await _accountDao.GetUserAccounts(PlatformUser.UserId);
            if (!platformUserAccount.Accounts.Any())
                throw new InvalidOperationException("Platform user must have at least one account");

            PlatformUserAccount = platformUserAccount.Accounts.First();
        }

        public async Task<bool> CheckPasswordResetToken(string token)
        {
            return await _userDao.CheckPasswordResetToken(token);
        }

        public async Task SendPasswordResetLink(string email, string link)
        {
            using (RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider())
            {
                byte[] bytes = new byte[8];
                rng.GetBytes(bytes);

                string token = Convert.ToBase64String(bytes);
                await _userDao.SendPasswordResetLink(email, token);
                await _eventManager.FireЕvent(this, EventAction.ResetPassword, new UserPasswordReset() { Email = email, Link = $"{link}/{WebUtility.UrlEncode(token)}"});
                await Task.CompletedTask;
            }
        }

        public async Task<bool> PasswordReset(string token, string newPassword)
        {
            return await _userDao.PasswordReset(token, _passwordProcess.Generate(newPassword).ToFlatString());
        }
    }
}
