using Core.Abstractions;
using Core.DomainObjects;
using DataProviders.Collections;
using DomainResults.Common;
using ExtensionMethods;
using HashService;
using JWTService;
using WeatherForecast.Models.Requests;

namespace WeatherForecast.Services {

  /// <summary>
  /// 
  /// </summary>
  public interface IAuthenticationService {

    /// <summary>
    /// 
    /// </summary>
    /// <param name="siteId"></param>
    /// <param name="requestData"></param>
    /// <returns></returns>
    (string?, IDomainResult) Post(Guid siteId, AuthenticationRequestModel requestData);
  }

  /// <summary>
  /// 
  /// </summary>
  public class AutheticationService : ServiceBase<AutheticationService>, IAuthenticationService {

    private readonly IUserDataProvider _userDataProvider;
    private readonly IHashService _hashService;
    private readonly IJWTService _jwtService;
    
    /// <summary>
    /// 
    /// </summary>
    /// <param name="logger"></param>
    /// <param name="userDataProvider"></param>
    /// <param name="hashService"></param>
    /// <param name="jwtService"></param>
    public AutheticationService (
      ILogger<AutheticationService> logger,
      IUserDataProvider userDataProvider,
      IHashService hashService,
      IJWTService jwtService
    ) : base(logger) {
      _userDataProvider = userDataProvider;
      _hashService = hashService;
      _jwtService = jwtService;
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="siteId"></param>
    /// <param name="requestData"></param>
    /// <returns></returns>
    public (string?, IDomainResult) Post(Guid siteId, AuthenticationRequestModel requestData) {
      var opId = Guid.NewGuid();

      // Retrieve user from database by userName
      var (user, getUserResult) = _userDataProvider.GetByNickName(requestData.Username);
      if (!getUserResult.IsSuccess || user == null)
        return IDomainResult.NotFound<string?>();

      if (user.Passwords.Password == null)
        return IDomainResult.Failed<string?>($"Opid = [{opId}] Password is not set, create new password.");

      // Check provided password hash with the stored one
      var (salt, hash) = _hashService.CreateSaltedHash(requestData.Password);
      if (!_hashService.ValidateHash(requestData.Password, salt, hash))
        return IDomainResult.Unauthorized<string?>();

      // Check password expiration if enabled
      if (user.Passwords.Expiration.Enabled && DateTime.UtcNow > user.Passwords.Password.Created.AddDays(user.Passwords.Expiration.Days)) {
        user.Passwords.Expired.Add(user.Passwords.Password.Prototype());
        user.Passwords.Password = null;

        return IDomainResult.Failed<string?>();
      }

      // Creating JWT token
      var claims = new List<KeyValuePair<string, string>> {
        new KeyValuePair<string, string>("UserId", $"{user.Id}")
      };

      var created = DateTime.UtcNow;
      var expires = created.AddDays(365);

      var token = _jwtService.CreateJwtToken(expires, claims);

      user.Tokens.Add(new Token {
        Value = token,
        Created = created,
        Expires = expires,
      });

      return IDomainResult.Success(token);
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="token"></param>
    /// <returns></returns>
    public IDomainResult Get(string token) {

      #region Retrieve user id from token claim
      var (claims, getClaimsResult) = _jwtService.JwtTokenClaims(token);
      if (!getClaimsResult.IsSuccess || claims == null)
        return IDomainResult.Failed();

      var userId = claims.SingleOrDefault(x => x.Key == "UserId").Value.ToGuid();
      if(userId == Guid.Empty)
        return IDomainResult.Failed();
      #endregion

      var (user, getUserRersult) = _userDataProvider.Get(userId);
      if(!getUserRersult.IsSuccess || user == null)
        return IDomainResult.Failed();

      // remove expired tokens
      user.Tokens = user.Tokens.Where(x => x.Expires < DateTime.UtcNow).ToList();

      if (!user.Tokens.Any(x => x.Value == token))
        return IDomainResult.Failed();

      return IDomainResult.Success();
    }
  }
}
