class UserSerializer < BasicUserSerializer

  attr_accessor :omit_stats,
                :topic_post_count

  def self.staff_attributes(*attrs)
    attributes(*attrs)
    attrs.each do |attr|
      define_method "include_#{attr}?" do
        scope.is_staff?
      end
    end
  end

  def self.private_attributes(*attrs)
    attributes(*attrs)
    attrs.each do |attr|
      define_method "include_#{attr}?" do
        can_edit
      end
    end
  end

  # attributes that are hidden for TL0 users when seen by anonymous
  def self.untrusted_attributes(*attrs)
    attrs.each do |attr|
      method_name = "include_#{attr}?"
      define_method(method_name) do
        return false if scope.restrict_user_fields?(object)
        send(attr).present?
      end
    end
  end

  attributes :name,
             :email,
             :last_posted_at,
             :last_seen_at,
             :bio_raw,
             :bio_cooked,
             :created_at,
             :website,
             :website_name,
             :profile_background,
             :card_background,
             :location,
             :can_edit,
             :can_edit_username,
             :can_edit_email,
             :can_edit_name,
             :stats,
             :ignored,
             :muted,
             :can_ignore_user,
             :can_mute_user,
             :can_send_private_messages,
             :can_send_private_message_to_user,
             :bio_excerpt,
             :trust_level,
             :moderator,
             :admin,
             :title,
             :suspend_reason,
             :suspended_till,
             :uploaded_avatar_id,
             :badge_count,
             :has_title_badges,
             :custom_fields,
             :user_fields,
             :topic_post_count,
             :pending_count,
             :profile_view_count,
             :time_read,
             :recent_time_read,
             :primary_group_name,
             :primary_group_flair_url,
             :primary_group_flair_bg_color,
             :primary_group_flair_color,
             :staged,
             :second_factor_enabled,
             :second_factor_backup_enabled,
             :second_factor_remaining_backup_codes,
             :associated_accounts

  has_one :invited_by, embed: :object, serializer: BasicUserSerializer
  has_many :groups, embed: :object, serializer: BasicGroupSerializer
  has_many :group_users, embed: :object, serializer: BasicGroupUserSerializer
  has_many :featured_user_badges, embed: :ids, serializer: UserBadgeSerializer, root: :user_badges
  has_one :user_option, embed: :object, serializer: UserOptionSerializer

  def include_user_option?
    can_edit
  end

  staff_attributes :post_count,
                   :can_be_deleted,
                   :can_delete_all_posts

  private_attributes :locale,
                     :muted_category_ids,
                     :watched_tags,
                     :watching_first_post_tags,
                     :tracked_tags,
                     :muted_tags,
                     :tracked_category_ids,
                     :watched_category_ids,
                     :watched_first_post_category_ids,
                     :private_messages_stats,
                     :system_avatar_upload_id,
                     :system_avatar_template,
                     :gravatar_avatar_upload_id,
                     :gravatar_avatar_template,
                     :custom_avatar_upload_id,
                     :custom_avatar_template,
                     :has_title_badges,
                     :muted_usernames,
                     :ignored_usernames,
                     :mailing_list_posts_per_day,
                     :can_change_bio,
                     :user_api_keys,
                     :user_auth_tokens,
                     :user_auth_token_logs

  untrusted_attributes :bio_raw,
                       :bio_cooked,
                       :bio_excerpt,
                       :location,
                       :website,
                       :website_name,
                       :profile_background,
                       :card_background

  ###
  ### ATTRIBUTES
  ###

  def mailing_list_posts_per_day
    val = Post.estimate_posts_per_day
    [val, SiteSetting.max_emails_per_day_per_user].min
  end

  def groups
    object.groups.order(:id)
      .visible_groups(scope.user)
  end

  def group_users
    object.group_users.order(:group_id)
  end

  def include_email?
    (object.id && object.id == scope.user.try(:id)) ||
      (scope.is_staff? && object.staged?)
  end

  def include_associated_accounts?
    (object.id && object.id == scope.user.try(:id))
  end

  def include_second_factor_enabled?
    (object&.id == scope.user&.id) || scope.is_staff?
  end

  def second_factor_enabled
    object.totp_enabled?
  end

  def include_second_factor_backup_enabled?
    object&.id == scope.user&.id
  end

  def second_factor_backup_enabled
    object.backup_codes_enabled?
  end

  def include_second_factor_remaining_backup_codes?
    (object&.id == scope.user&.id) && object.backup_codes_enabled?
  end

  def second_factor_remaining_backup_codes
    object.remaining_backup_codes
  end

  def can_change_bio
    !(SiteSetting.enable_sso && SiteSetting.sso_overrides_bio)
  end

  def user_api_keys
    keys = object.user_api_keys.where(revoked_at: nil).map do |k|
      {
        id: k.id,
        application_name: k.application_name,
        scopes: k.scopes.map { |s| I18n.t("user_api_key.scopes.#{s}") },
        created_at: k.created_at,
        last_used_at: k.last_used_at,
      }
    end

    keys.sort! { |a, b| a[:last_used_at].to_time <=> b[:last_used_at].to_time }
    keys.length > 0 ? keys : nil
  end

  def user_auth_tokens
    ActiveModel::ArraySerializer.new(
      object.user_auth_tokens,
      each_serializer: UserAuthTokenSerializer,
      scope: scope
    )
  end

  def user_auth_token_logs
    ActiveModel::ArraySerializer.new(
      object.user_auth_token_logs.where(
        action: UserAuthToken::USER_ACTIONS
      ).order(:created_at).reverse_order,
      each_serializer: UserAuthTokenLogSerializer
    )
  end

  def bio_raw
    object.user_profile.bio_raw
  end

  def bio_cooked
    object.user_profile.bio_processed
  end

  def website
    object.user_profile.website
  end

  def website_name
    uri = begin
      URI(website.to_s)
    rescue URI::Error
    end

    return if uri.nil? || uri.host.nil?
    uri.host.sub(/^www\./, '') + uri.path
  end

  def include_website_name
    website.present?
  end

  def profile_background
    object.user_profile.profile_background
  end

  def card_background
    object.user_profile.card_background
  end

  def location
    object.user_profile.location
  end

  def can_edit
    scope.can_edit?(object)
  end

  def can_edit_username
    scope.can_edit_username?(object)
  end

  def can_edit_email
    scope.can_edit_email?(object)
  end

  def can_edit_name
    scope.can_edit_name?(object)
  end

  def include_stats?
    !omit_stats == true
  end

  def stats
    UserAction.stats(object.id, scope)
  end

  def ignored
    IgnoredUser.where(user_id: scope.user&.id, ignored_user_id: object.id).exists?
  end

  def muted
    MutedUser.where(user_id: scope.user&.id, muted_user_id: object.id).exists?
  end

  def can_mute_user
    scope.can_mute_user?(object.id)
  end

  def can_ignore_user
    scope.can_ignore_user?(object.id)
  end

  # Needed because 'send_private_message_to_user' will always return false
  # when the current user is being serialized
  def can_send_private_messages
    scope.can_send_private_message?(Discourse.system_user)
  end

  def can_send_private_message_to_user
    scope.can_send_private_message?(object) && scope.current_user != object
  end

  def bio_excerpt
    object.user_profile.bio_excerpt(350 , keep_newlines: true, keep_emoji_images: true)
  end

  def include_suspend_reason?
    scope.can_see_suspension_reason?(object) && object.suspended?
  end

  def include_suspended_till?
    object.suspended?
  end

  def primary_group_name
    object.primary_group.try(:name)
  end

  def primary_group_flair_url
    object.try(:primary_group).try(:flair_url)
  end

  def primary_group_flair_bg_color
    object.try(:primary_group).try(:flair_bg_color)
  end

  def primary_group_flair_color
    object.try(:primary_group).try(:flair_color)
  end

  ###
  ### STAFF ATTRIBUTES
  ###

  def post_count
    object.user_stat.try(:post_count)
  end

  def can_be_deleted
    scope.can_delete_user?(object)
  end

  def can_delete_all_posts
    scope.can_delete_all_posts?(object)
  end

  ###
  ### PRIVATE ATTRIBUTES
  ###
  def muted_tags
    TagUser.lookup(object, :muted).joins(:tag).pluck('tags.name')
  end

  def tracked_tags
    TagUser.lookup(object, :tracking).joins(:tag).pluck('tags.name')
  end

  def watching_first_post_tags
    TagUser.lookup(object, :watching_first_post).joins(:tag).pluck('tags.name')
  end

  def watched_tags
    TagUser.lookup(object, :watching).joins(:tag).pluck('tags.name')
  end

  def muted_category_ids
    CategoryUser.lookup(object, :muted).pluck(:category_id)
  end

  def tracked_category_ids
    CategoryUser.lookup(object, :tracking).pluck(:category_id)
  end

  def watched_category_ids
    CategoryUser.lookup(object, :watching).pluck(:category_id)
  end

  def watched_first_post_category_ids
    CategoryUser.lookup(object, :watching_first_post).pluck(:category_id)
  end

  def muted_usernames
    MutedUser.where(user_id: object.id).joins(:muted_user).pluck(:username)
  end

  def ignored_usernames
    IgnoredUser.where(user_id: object.id).joins(:ignored_user).pluck(:username)
  end

  def include_private_messages_stats?
    can_edit && !(omit_stats == true)
  end

  def private_messages_stats
    UserAction.private_messages_stats(object.id, scope)
  end

  def system_avatar_upload_id
    # should be left blank
  end

  def system_avatar_template
    User.system_avatar_template(object.username)
  end

  def include_gravatar_avatar_upload_id?
    object.user_avatar&.gravatar_upload_id
  end

  def gravatar_avatar_upload_id
    object.user_avatar.gravatar_upload_id
  end

  def include_gravatar_avatar_template?
    include_gravatar_avatar_upload_id?
  end

  def gravatar_avatar_template
    User.avatar_template(object.username, object.user_avatar.gravatar_upload_id)
  end

  def include_custom_avatar_upload_id?
    object.user_avatar&.custom_upload_id
  end

  def custom_avatar_upload_id
    object.user_avatar.custom_upload_id
  end

  def include_custom_avatar_template?
    include_custom_avatar_upload_id?
  end

  def custom_avatar_template
    User.avatar_template(object.username, object.user_avatar.custom_upload_id)
  end

  def has_title_badges
    object.badges.where(allow_title: true).exists?
  end

  def user_fields
    allowed_keys = scope.allowed_user_field_ids(object).map(&:to_s)
    object.user_fields&.select { |k, v| allowed_keys.include?(k) }
  end

  def include_user_fields?
    user_fields.present?
  end

  def include_topic_post_count?
    topic_post_count.present?
  end

  def custom_fields
    fields = User.whitelisted_user_custom_fields(scope)

    if scope.can_edit?(object)
      fields += DiscoursePluginRegistry.serialized_current_user_fields.to_a
    end

    if fields.present?
      User.custom_fields_for_ids([object.id], fields)[object.id] || {}
    else
      {}
    end
  end

  def pending_count
    0
  end

  def profile_view_count
    object.user_profile.views
  end

  def time_read
    object.user_stat&.time_read
  end

  def recent_time_read
    time = object.recent_time_read
  end

  def include_staged?
    scope.is_staff?
  end

end