namespace CSR.Domain.Entities;

using System.Net.Mail;
using CSR.Domain.Interfaces;

public class User
{
  public int Id { get; private set; }
  public string Username { get; private set; }
  public string Email { get; private set; }
  public string PasswordHash { get; private set; }

  public int RoleId { get; private set; }
  public Role Role { get; private set; }

  private User(int id, string username, string email, string passwordHash, int roleId, Role role)
  {
    Id = id;
    Username = username;
    Email = email;
    PasswordHash = passwordHash;
    RoleId = roleId;
    Role = role;
  }

  // This role is used when a new user is created
  public static readonly Role DefaultRole = Role.User;


  internal static User LoadExisting(int id, string username, string email, string passwordHash, int roleId, Role role)
  {
    return new User(id, username, email, passwordHash, roleId, role);
  }

  public static User CreateNew(string username, string email, string password, IPasswordHasher passwordHasher)
  {
    if (!IsValidUsername(username).IsValid)
    {
      throw new ArgumentException("Invalid username.");
    }
    if (!IsValidEmail(email))
    {
      throw new ArgumentException("Invalid email.");
    }
    if (!IsValidPassword(password).IsValid)
    {
      throw new ArgumentException("Invalid password.");
    }

    var user = new User(0, username, email, password, DefaultRole.Id, DefaultRole);
    user.PasswordHash = passwordHasher.HashPassword(user, password);
    return user;
  }


  public (bool Success, IEnumerable<string>? Errors) UpdatePassword(string password, IPasswordHasher passwordHasher, User requestingUser)
  {
    if (requestingUser.Id != Id || requestingUser.Role.Name != "Admin")
    {
      throw new UnauthorizedAccessException("Only admins or the same user can update passwords.");
    }

    var validityCheck = IsValidPassword(password);

    if (validityCheck.IsValid)
    {
      PasswordHash = passwordHasher.HashPassword(this, password);
    }

    return validityCheck;
  }


  public bool VerifyPassword(string providedPassword, IPasswordHasher passwordHasher)
  {
    return passwordHasher.VerifyHashedPassword(this, PasswordHash, providedPassword);
  }


  public static (bool IsValid, IEnumerable<string>? Errors) IsValidPassword(string password)
  {
    var Errors = new List<string>();
    if (password.Length < 8 || password.Length > 32)
    {
      Errors.Add("Password must be between 8 and 32 characters long.");
    }
    if (!password.Any(char.IsUpper))
    {
      Errors.Add("Password must contain at least one uppercase letter.");
    }
    if (!password.Any(char.IsLower))
    {
      Errors.Add("Password must contain at least one lowercase letter.");
    }
    if (!password.Any(char.IsDigit))
    {
      Errors.Add("Password must contain at least one digit.");
    }
    if (Errors.Count > 0)
    {
      return (false, Errors);
    }
    return (true, null);
  }


  public (bool Success, IEnumerable<string>? Errors) UpdateEmail(string email, User requestingUser)
  {
    if (requestingUser.Id != Id || requestingUser.Role.Name != "Admin")
    {
      throw new UnauthorizedAccessException("Only admins or the same user can update email.");
    }

    if (IsValidEmail(email))
    {
      Email = email;
      return (true, null);
    }
    return (false, new List<string> { "Invalid email format." });
  }


  public static bool IsValidEmail(string email)
  {
    try
    {
      var mailAddress = new MailAddress(email);
      return true;
    }
    catch (FormatException)
    {
      return false;
    }
  }


  public (bool Success, IEnumerable<string>? Errors) UpdateUsername(string username, User requestingUser)
  {
    if (requestingUser.Role.Name != "Admin")
    {
      throw new UnauthorizedAccessException("Only admins can update username.");
    }

    var validityCheck = IsValidUsername(username);
    if (validityCheck.IsValid)
    {
      Username = username;
    }

    return validityCheck;
  }


  public static (bool IsValid, IEnumerable<string>? Errors) IsValidUsername(string username)
  {
    var Errors = new List<string>();
    if (!username.All(char.IsLetterOrDigit))
    {
      Errors.Add("Username must be alphanumeric.");
    }
    if (string.IsNullOrWhiteSpace(username) || username.Length < 3)
    {
      Errors.Add("Username must be at least 3 characters long.");
    }
    if (username.Length > 32)
    {
      Errors.Add("Username must be less than 32 characters long.");
    }

    if (Errors.Count > 0)
    {
      return (false, Errors);
    }

    return (true, null);
  }


  public void UpdateRole(Role role, User requestingUser)
  {
    if (requestingUser.Role.Name != "Admin")
    {
      throw new UnauthorizedAccessException("Only admins can assign roles.");
    }
    Role = role;
    RoleId = role.Id;
  }
}
