using Microsoft.Extensions.Logging;
using System;
using System.Threading.Tasks;
using Timeline.Entities;
using Timeline.Helpers;
using Timeline.Services.Exceptions;
namespace Timeline.Services
{
    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 IUserService _userService;
        private readonly IUserCredentialService _userCredentialService;
        private readonly IUserTokenService _userTokenService;
        private readonly IClock _clock;
        public UserTokenManager(ILogger logger, IUserService userService, IUserCredentialService userCredentialService, IUserTokenService userTokenService, IClock clock)
        {
            _logger = logger;
            _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 });
            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);
            if (tokenInfo.ExpireAt.HasValue)
            {
                var currentTime = _clock.GetCurrentTime();
                if (tokenInfo.ExpireAt < currentTime)
                    throw new UserTokenTimeExpireException(token, tokenInfo.ExpireAt.Value, currentTime);
            }
            var user = await _userService.GetUser(tokenInfo.Id);
            if (tokenInfo.Version < user.Version)
                throw new UserTokenBadVersionException(token, tokenInfo.Version, user.Version);
            return user;
        }
    }
}