# frozen_string_literal: true

class EmailToken < ActiveRecord::Base
  class TokenAccessError < StandardError
  end

  belongs_to :user

  validates :user_id, :email, :token_hash, presence: true

  scope :unconfirmed, -> { where(confirmed: false) }
  scope :active,
        -> do
          where(expired: false).where(
            "created_at >= ?",
            SiteSetting.email_token_valid_hours.hours.ago,
          )
        end

  after_initialize do
    if self.token_hash.blank?
      @token ||= SecureRandom.hex
      self.token_hash = self.class.hash_token(@token)
    end
  end

  after_create do
    EmailToken
      .where(user_id: self.user_id)
      .where(scope: [nil, self.scope])
      .where.not(id: self.id)
      .update_all(expired: true)
  end

  before_validation { self.email = self.email.downcase if self.email }

  before_save do
    if self.scope.blank?
      Discourse.deprecate("EmailToken#scope cannot be empty.", output_in_test: true)
    end
  end

  self.ignored_columns = %w[token] # TODO: Remove when 20240212034010_drop_deprecated_columns has been promoted to pre-deploy

  def self.scopes
    @scopes ||= Enum.new(signup: 1, password_reset: 2, email_login: 3, email_update: 4)
  end

  def token
    raise TokenAccessError.new if @token.blank?

    @token
  end

  def self.confirm(token, scope: nil, skip_reviewable: false)
    User.transaction do
      email_token = confirmable(token, scope: scope)
      return if email_token.blank?

      email_token.update!(confirmed: true)

      user = email_token.user
      user.send_welcome_message = !user.active?
      user.email = email_token.email
      user.active = true
      user.custom_fields.delete("activation_reminder")
      user.save!
      user.create_reviewable if !skip_reviewable
      user.set_automatic_groups
      DiscourseEvent.trigger(:user_confirmed_email, user)
      Invite.redeem_for_existing_user(user) if scope == EmailToken.scopes[:signup]

      user.reload
    end
  rescue ActiveRecord::RecordInvalid
    # If the user's email is already taken, just return nil (failure)
  end

  def self.confirmable(token, scope: nil)
    return nil if token.blank?

    relation = unconfirmed.active.includes(:user).where(token_hash: hash_token(token))

    # TODO(2022-01-01): All email tokens should have scopes by now
    if !scope
      relation.first
    else
      relation.where(scope: scope).first || relation.where(scope: nil).first
    end
  end

  def self.enqueue_signup_email(email_token, to_address: nil)
    Jobs.enqueue(
      :critical_user_email,
      type: "signup",
      user_id: email_token.user_id,
      email_token: email_token.token,
      to_address: to_address,
    )
  end

  def self.hash_token(token)
    Digest::SHA256.hexdigest(token)
  end
end

# == Schema Information
#
# Table name: email_tokens
#
#  id         :integer          not null, primary key
#  user_id    :integer          not null
#  email      :string           not null
#  confirmed  :boolean          default(FALSE), not null
#  expired    :boolean          default(FALSE), not null
#  created_at :datetime         not null
#  updated_at :datetime         not null
#  token_hash :string           not null
#  scope      :integer
#
# Indexes
#
#  index_email_tokens_on_token_hash  (token_hash) UNIQUE
#  index_email_tokens_on_user_id     (user_id)
#