using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System;
using System.Threading.Tasks;
using Timeline.Configs;
using Timeline.Entities;
using Timeline.Helpers;
using Timeline.Services.User;
namespace Timeline.Services.Token
{
    public class UserTokenCreateResult
    {
        public string Token { get; set; } = default!;
        public UserEntity User { get; set; } = default!;
    }
    public interface IUserTokenManager
    {
        /// 
        /// Try to create a token for given username and password.
        /// 
        /// The username.
        /// The password.
        /// The expire time of the token.
        /// The created token and the user info.
        /// Thrown when  or  is null.
        /// Thrown when  is of bad format.
        /// Thrown when the user with  does not exist.
        /// Thrown when  is wrong.
        public Task CreateToken(string username, string password, DateTime? expireAt = null);
        /// 
        /// Verify a token and get the saved user info. This also check the database for existence of the user.
        /// 
        /// The token.
        /// The user stored in token.
        /// Thrown when  is null.
        /// Thrown when the token is expired.
        /// Thrown when the token is of bad version.
        /// Thrown when the token is of bad format.
        /// Thrown when the user specified by the token does not exist. Usually the user had been deleted after the token was issued.
        public Task VerifyToken(string token);
    }
    public class UserTokenManager : IUserTokenManager
    {
        private readonly ILogger _logger;
        private readonly IOptionsMonitor _tokenOptionsMonitor;
        private readonly IUserService _userService;
        private readonly IUserCredentialService _userCredentialService;
        private readonly IUserTokenHandler _userTokenService;
        private readonly IClock _clock;
        public UserTokenManager(ILogger logger, IOptionsMonitor tokenOptionsMonitor, IUserService userService, IUserCredentialService userCredentialService, IUserTokenHandler userTokenService, IClock clock)
        {
            _logger = logger;
            _tokenOptionsMonitor = tokenOptionsMonitor;
            _userService = userService;
            _userCredentialService = userCredentialService;
            _userTokenService = userTokenService;
            _clock = clock;
        }
        public async Task CreateToken(string username, string password, DateTime? expireAt = null)
        {
            expireAt = expireAt?.MyToUtc();
            if (username == null)
                throw new ArgumentNullException(nameof(username));
            if (password == null)
                throw new ArgumentNullException(nameof(password));
            var userId = await _userCredentialService.VerifyCredential(username, password);
            var user = await _userService.GetUser(userId);
            var token = _userTokenService.GenerateToken(new UserTokenInfo
            {
                Id = user.Id,
                Version = user.Version,
                ExpireAt = expireAt ?? _clock.GetCurrentTime() + TimeSpan.FromSeconds(_tokenOptionsMonitor.CurrentValue.DefaultExpireSeconds)
            });
            return new UserTokenCreateResult { Token = token, User = user };
        }
        public async Task VerifyToken(string token)
        {
            if (token == null)
                throw new ArgumentNullException(nameof(token));
            var tokenInfo = _userTokenService.VerifyToken(token);
            var currentTime = _clock.GetCurrentTime();
            if (tokenInfo.ExpireAt < currentTime)
                throw new UserTokenTimeExpiredException(token, tokenInfo.ExpireAt, currentTime);
            try
            {
                var user = await _userService.GetUser(tokenInfo.Id);
                if (tokenInfo.Version < user.Version)
                    throw new UserTokenVersionExpiredException(token, tokenInfo.Version, user.Version);
                return user;
            }
            catch (UserNotExistException e)
            {
                throw new UserTokenUserNotExistException(token, e);
            }
        }
    }
}