# typed: strict
class Domain::User < ReduxApplicationRecord
  extend T::Helpers
  include HasAuxTable
  include AttrJsonRecordAliases
  include HasCompositeToParam
  include HasViewPrefix
  include HasDescriptionHtmlForView
  include HasTimestampsWithDueAt
  include HasDomainType

  self.table_name = "domain_users"
  abstract!

  class_attribute :class_has_created_posts,
                  :class_has_faved_posts,
                  :class_has_followed_users,
                  :class_has_followed_by_users,
                  :due_at_timestamp_fields

  sig(:final) { returns(T::Boolean) }
  def has_created_posts?
    class_has_created_posts.present?
  end

  sig(:final) { returns(T::Boolean) }
  def has_faved_posts?
    class_has_faved_posts.present?
  end

  sig(:final) { returns(T::Boolean) }
  def has_followed_users?
    class_has_followed_users.present?
  end

  sig(:final) { returns(T::Boolean) }
  def has_followed_by_users?
    class_has_followed_by_users.present?
  end

  sig { overridable.returns(T.class_of(Domain::UserPostFav)) }
  def self.fav_model_type
    if self.is_a?(Domain::User::FaUser)
      Domain::UserPostFav::FaUserPostFav
    else
      Domain::UserPostFav
    end
  end

  sig { returns(String) }
  def type
    super
  end

  sig { params(value: String).returns(String) }
  def type=(value)
    super
  end

  after_save :set_names_for_search

  sig { void }
  def set_names_for_search
    values = names_for_search_values
    models = user_search_names.pluck(:name)

    missing_values = values - models
    extra_values = models - values

    if missing_values.any?
      ::Domain::UserSearchName.upsert_all(
        missing_values.map { |name| { user_id: id, name: name } },
        unique_by: %i[user_id name],
      )
    end

    if extra_values.any?
      ::Domain::UserSearchName.where(user_id: id, name: extra_values).delete_all
    end
  end

  sig(:final) { returns(T::Array[String]) }
  def names_for_search_values
    names_for_search
      .map { |name| name.downcase.strip }
      .reject(&:blank?)
      .uniq
      .sort
  end

  attr_json :migrated_user_favs_at, ActiveModelUtcTimeValue.new
  attr_json_due_timestamp :scanned_favs_at, 3.months

  has_many :user_search_names,
           class_name: "::Domain::UserSearchName",
           inverse_of: :user,
           dependent: :destroy

  has_many :user_post_creations,
           -> { extending CounterCacheWithFallback[:user_post_creations] },
           class_name: "::Domain::UserPostCreation",
           inverse_of: :user,
           dependent: :destroy

  has_many :user_post_favs,
           -> { extending CounterCacheWithFallback[:user_post_favs] },
           class_name: "::Domain::UserPostFav",
           inverse_of: :user,
           dependent: :destroy

  has_many :user_user_follows_from,
           -> { extending CounterCacheWithFallback[:user_user_follows_from] },
           class_name: "::Domain::UserUserFollow",
           foreign_key: :from_id,
           inverse_of: :from,
           dependent: :destroy

  has_many :user_user_follows_to,
           -> { extending CounterCacheWithFallback[:user_user_follows_to] },
           class_name: "::Domain::UserUserFollow",
           foreign_key: :to_id,
           inverse_of: :to,
           dependent: :destroy

  has_many :posts, through: :user_post_creations, source: :post
  has_many :faved_posts, through: :user_post_favs, source: :post
  has_many :followed_users, through: :user_user_follows_from, source: :to
  has_many :followed_by_users, through: :user_user_follows_to, source: :from

  sig { params(klass: T.class_of(Domain::Post)).void }
  def self.has_created_posts!(klass)
    self.class_has_created_posts = klass
    has_many :posts,
             -> { order(klass.param_order_attribute => :desc) },
             through: :user_post_creations,
             source: :post,
             class_name: klass.name
  end

  sig { params(klass: T.class_of(Domain::Post)).void }
  def self.has_faved_posts!(klass)
    self.class_has_faved_posts = klass
    has_many :faved_posts,
             -> { order(klass.param_order_attribute => :desc) },
             through: :user_post_favs,
             source: :post,
             class_name: klass.name
  end

  has_many :add_tracked_objects,
           -> { order(created_at: :desc) },
           inverse_of: :user,
           class_name: Domain::UserJobEvent::AddTrackedObject.name

  has_many :favs_scans,
           -> { where(kind: "favs").order(requested_at: :asc) },
           inverse_of: :user,
           class_name: Domain::UserJobEvent::AddTrackedObject.name

  sig { params(post_ids: T::Array[Integer], log_entry: HttpLogEntry).void }
  def upsert_new_favs(post_ids, log_entry:)
    self.class.transaction do
      type = self.class.fav_model_type.name
      if post_ids.any?
        post_ids.each_slice(1000) do |slice|
          self.user_post_favs.upsert_all(
            slice.map { |post_id| { post_id:, type: } },
            unique_by: %i[user_id post_id],
            update_only: %i[type],
          )
        end
      end
      current_time = Time.now
      user_post_favs_count = self.user_post_favs.count
      self[:user_post_favs_count] = user_post_favs_count
      self.favs_scans.create!(
        num_added: post_ids.count,
        num_total: user_post_favs_count,
        duration_since_last_scan: duration_since_last_scan,
        log_entry:,
      )
      self.scanned_favs_at = current_time
      logger.info(format_tags(make_tag("upsert new favs", post_ids.size)))
    end
  end

  sig { returns(T.nilable(ActiveSupport::Duration)) }
  def duration_since_last_scan
    if (sfa = self.scanned_favs_at)
      ActiveSupport::Duration.build(Time.now - sfa.utc)
    else
      nil
    end
  end

  sig { params(klass: T.class_of(Domain::User)).void }
  def self.has_followed_users!(klass)
    self.class_has_followed_users = klass
    has_many :followed_users,
             through: :user_user_follows_from,
             source: :to,
             class_name: klass.name
  end

  sig { params(klass: T.class_of(Domain::User)).void }
  def self.has_followed_by_users!(klass)
    self.class_has_followed_by_users = klass
    has_many :followed_by_users,
             through: :user_user_follows_to,
             source: :from,
             class_name: klass.name
  end

  has_one :avatar,
          -> { order(created_at: :desc) },
          class_name: "::Domain::UserAvatar",
          inverse_of: :user

  has_many :avatars,
           class_name: "::Domain::UserAvatar",
           inverse_of: :user,
           dependent: :destroy

  sig { abstract.returns(String) }
  def account_status_for_view
  end

  sig { abstract.returns(T.nilable(String)) }
  def external_url_for_view
  end

  sig { abstract.returns(T.nilable(String)) }
  def name_for_view
  end

  sig { overridable.returns(T::Array[String]) }
  def names_for_search
    []
  end

  sig { abstract.returns(String) }
  def site_name_for_view
  end
end

# eager load all subclasses
Dir[Rails.root.join("app/models/domain/user/**/*.rb")].each do |file|
  require_dependency file
end
