# frozen_string_literal: true

class ScoreCalculator

  def self.default_score_weights
    {
      reply_count: 5,
      like_score: 15,
      incoming_link_count: 5,
      bookmark_count: 2,
      reads: 0.2
    }
  end

  def initialize(weightings = nil)
    @weightings = weightings || ScoreCalculator.default_score_weights
  end

  # Calculate the score for all posts based on the weightings
  def calculate(opts = nil)
    update_posts_score(opts)
    update_posts_rank(opts)
    update_topics_rank(opts)
  end

  private

  def update_posts_score(opts)
    limit = 20000

    components = []
    @weightings.each_key { |k| components << "COALESCE(posts.#{k}, 0) * :#{k}" }
    components = components.join(" + ")

    builder = DB.build <<SQL
       UPDATE posts p
        SET score = x.score
       FROM (
        SELECT posts.id, #{components} as score FROM posts
        join topics on posts.topic_id = topics.id
        /*where*/
        limit #{limit}
       ) AS x
       WHERE x.id = p.id
SQL

    builder.where("posts.score IS NULL OR posts.score <> #{components}", @weightings)

    filter_topics(builder, opts)

    while builder.exec == limit
    end
  end

  def update_posts_rank(opts)
    limit = 20000

    builder = DB.build <<~SQL
      UPDATE posts
      SET percent_rank = X.percent_rank
      FROM (
        SELECT posts.id, Y.percent_rank
        FROM posts
        JOIN (
          SELECT id, percent_rank()
                       OVER (PARTITION BY topic_id ORDER BY SCORE DESC) as percent_rank
          FROM posts
         ) Y ON Y.id = posts.id
         JOIN topics ON posts.topic_id = topics.id
        /*where*/
        LIMIT #{limit}
      ) AS X
      WHERE posts.id = X.id
    SQL

    builder.where("posts.percent_rank IS NULL OR Y.percent_rank <> posts.percent_rank")

    filter_topics(builder, opts)

    while builder.exec == limit
    end

  end

  def update_topics_rank(opts)
    builder = DB.build <<~SQL
      UPDATE topics AS topics
      SET has_summary = (topics.like_count >= :likes_required AND
                         topics.posts_count >= :posts_required AND
                         x.max_score >= :score_required),
          score = x.avg_score
      FROM (SELECT p.topic_id,
                   MAX(p.score) AS max_score,
                   AVG(p.score) AS avg_score
            FROM posts AS p
            GROUP BY p.topic_id) AS x
            /*where*/
    SQL

    defaults = {
      likes_required: SiteSetting.summary_likes_required,
      posts_required: SiteSetting.summary_posts_required,
      score_required: SiteSetting.summary_score_threshold
    }

    builder.where(<<~SQL, defaults)
      x.topic_id = topics.id AND
      (
        (topics.score <> x.avg_score OR topics.score IS NULL) OR
        (topics.has_summary IS NULL OR topics.has_summary <> (
          topics.like_count >= :likes_required AND
          topics.posts_count >= :posts_required AND
          x.max_score >= :score_required
        ))
      )
    SQL

    filter_topics(builder, opts)

    builder.exec
  end

  def filter_topics(builder, opts)
    return builder unless opts

    if min_topic_age = opts[:min_topic_age]
      builder.where("topics.bumped_at > :bumped_at ",
                 bumped_at: min_topic_age)
    end
    if max_topic_length = opts[:max_topic_length]
      builder.where("topics.posts_count < :max_topic_length",
                 max_topic_length: max_topic_length)
    end

    builder
  end

end