# frozen_string_literal: true
class UserStat < ActiveRecord::Base

  belongs_to :user
  after_save :trigger_badges

  def self.ensure_consistency!(last_seen = 1.hour.ago)
    reset_bounce_scores
    update_view_counts(last_seen)
  end

  def self.reset_bounce_scores
    UserStat.where("reset_bounce_score_after < now()")
      .where("bounce_score > 0")
      .update_all(bounce_score: 0)
  end

  # Updates the denormalized view counts for all users
  def self.update_view_counts(last_seen = 1.hour.ago)

    # NOTE: we only update the counts for users we have seen in the last hour
    #  this avoids a very expensive query that may run on the entire user base
    #  we also ensure we only touch the table if data changes

    # Update denormalized topics_entered
    DB.exec(<<~SQL, seen_at: last_seen)
      UPDATE user_stats SET topics_entered = X.c
       FROM
      (SELECT v.user_id, COUNT(topic_id) AS c
       FROM topic_views AS v
       WHERE v.user_id IN (
          SELECT u1.id FROM users u1 where u1.last_seen_at > :seen_at
       )
       GROUP BY v.user_id) AS X
      WHERE
        X.user_id = user_stats.user_id AND
        X.c <> topics_entered
    SQL

    # Update denormalzied posts_read_count
    DB.exec(<<~SQL, seen_at: last_seen)
      UPDATE user_stats SET posts_read_count = X.c
      FROM
      (SELECT pt.user_id,
              COUNT(*) AS c
       FROM users AS u
       JOIN post_timings AS pt ON pt.user_id = u.id
       JOIN topics t ON t.id = pt.topic_id
       WHERE u.last_seen_at > :seen_at AND
             t.archetype = 'regular' AND
             t.deleted_at IS NULL
       GROUP BY pt.user_id) AS X
       WHERE X.user_id = user_stats.user_id AND
             X.c <> posts_read_count
    SQL
  end

  # topic_reply_count is a count of posts in other users' topics
  def update_topic_reply_count
    self.topic_reply_count =
        Topic
      .where(['id in (
              SELECT topic_id FROM posts p
              JOIN topics t2 ON t2.id = p.topic_id
              WHERE p.deleted_at IS NULL AND
                t2.user_id <> p.user_id AND
                p.user_id = ?
              )', self.user_id])
      .count
  end

  MAX_TIME_READ_DIFF = 100
  # attempt to add total read time to user based on previous time this was called
  def self.update_time_read!(id)
    if last_seen = last_seen_cached(id)
      diff = (Time.now.to_f - last_seen.to_f).round
      if diff > 0 && diff < MAX_TIME_READ_DIFF
        update_args = ["time_read = time_read + ?", diff]
        UserStat.where(user_id: id).update_all(update_args)
        UserVisit.where(user_id: id, visited_at: Time.zone.now.to_date).update_all(update_args)
      end
    end
    cache_last_seen(id, Time.now.to_f)
  end

  def update_time_read!
    UserStat.update_time_read!(id)
  end

  def reset_bounce_score!
    update_columns(reset_bounce_score_after: nil, bounce_score: 0)
  end

  def self.last_seen_key(id)
    # frozen
    -"user-last-seen:#{id}"
  end

  def self.last_seen_cached(id)
    $redis.get(last_seen_key(id))
  end

  def self.cache_last_seen(id, val)
    $redis.set(last_seen_key(id), val)
  end

  protected

  def trigger_badges
    BadgeGranter.queue_badge_grant(Badge::Trigger::UserChange, user: self.user)
  end
end

# == Schema Information
#
# Table name: user_stats
#
#  user_id                  :integer          not null, primary key
#  topics_entered           :integer          default(0), not null
#  time_read                :integer          default(0), not null
#  days_visited             :integer          default(0), not null
#  posts_read_count         :integer          default(0), not null
#  likes_given              :integer          default(0), not null
#  likes_received           :integer          default(0), not null
#  topic_reply_count        :integer          default(0), not null
#  new_since                :datetime         not null
#  read_faq                 :datetime
#  first_post_created_at    :datetime
#  post_count               :integer          default(0), not null
#  topic_count              :integer          default(0), not null
#  bounce_score             :integer          default(0), not null
#  reset_bounce_score_after :datetime
#
