# typed: strict
# frozen_string_literal: true

class User < ApplicationRecord
  extend T::Sig

  rolify
  validate :validate_email_domain

  # For sorbet
  include Devise::Models::Authenticatable
  include Devise::Models::Confirmable
  include Devise::Models::Recoverable
  include Devise::Models::Validatable
  extend Devise::Models::Validatable::ClassMethods
  extend Rolify
  include Rolify::Role

  # Include default devise modules. Others available are:
  # :token_authenticatable and :timeoutable
  devise :database_authenticatable, :registerable, :recoverable, :rememberable,
         :trackable, :validatable, :confirmable, :lockable
  has_many :api_keys, dependent: :destroy
  has_many :alerts, dependent: :destroy
  # If a user is destroyed we want to keep the comments because they
  # are part of the public record
  has_many :comments, dependent: :nullify
  # Same for reports but they're not public
  has_many :reports, dependent: :nullify
  has_many :contact_messages, dependent: :nullify

  # Returns true if the user has at least one key that is permanent
  # and active
  sig { returns(T::Boolean) }
  def active_permanent_api_key?
    api_keys.any? { |key| key.active? && key.permanent? }
  end

  sig { returns(T::Boolean) }
  def active_api_key?
    api_keys.any?(&:active?)
  end

  # rubocop:disable Style/ArgumentsForwarding
  # TODO: Arguments forwarding doesn't seem to be supported by sorbet right now?
  sig { params(notification: T.untyped, args: T.untyped).void }
  def send_devise_notification(notification, *args)
    devise_mailer.send(notification, self, *args).deliver_later
  end
  # rubocop:enable Style/ArgumentsForwarding

  sig { returns(T::Boolean) }
  def can_login_to_admin?
    has_role?(:admin) || has_role?(:api_editor) || has_role?(:scraper_editor)
  end

  # This is currently used when creating users via an alert
  # TODO: Remove this as soon as users are purely being created by people registering
  sig { void }
  def temporarily_allow_empty_password!
    @temporarily_allow_empty_password = T.let(true, T.nilable(T::Boolean))
  end

  sig { returns(T::Boolean) }
  def requires_activation?
    encrypted_password.blank?
  end

  # Returns the name of the user. If that isn't set just returns the email
  # We want to use this very sparingly. The only place it really makes sense
  # is in the nav bar. In places where we are referring to people by name
  # it doesn't make sense to use their email address if their name isn't
  # available. It makes much more sense in that case to just have a more
  # generic "Hello!" rather than "Hello foo@foo.com!".
  sig { returns(String) }
  def name_with_fallback
    name.presence || email
  end

  # This is for the benefit of
  # https://github.com/IrvanFza/administrate-field-paper_trail/pull/1/files
  sig { returns(String) }
  def to_s
    name_with_fallback
  end

  sig { void }
  def send_activation_instructions
    Users::ActivationMailer.notify(self, set_reset_password_token).deliver_later!
  end

  private

  sig { returns(T::Boolean) }
  def password_required?
    return false if @temporarily_allow_empty_password

    super
  end

  sig { void }
  def validate_email_domain
    return unless IsEmailAddressBannedService.call(email:)

    errors.add(:email, "is not allowed. Please contact us if you think this is in error.")
  end
end
