# frozen_string_literal: true

class ReviewableScore < ActiveRecord::Base
  belongs_to :reviewable
  belongs_to :user
  belongs_to :reviewed_by, class_name: 'User'
  belongs_to :meta_topic, class_name: 'Topic'

  # To keep things simple the types correspond to `PostActionType` for backwards
  # compatibility, but we can add extra reasons for scores.
  def self.types
    @types ||= PostActionType.flag_types.merge(
      needs_approval: 9
    )
  end

  # When extending post action flags, we need to call this method in order to
  # get the latests flags.
  def self.reload_types
    @types = nil
    types
  end

  def self.add_new_types(type_names)
    next_id = types.values.max + 1

    type_names.each_with_index do |name, idx|
      @types[name] = next_id + idx
    end
  end

  def self.statuses
    @statuses ||= Enum.new(
      pending: 0,
      agreed: 1,
      disagreed: 2,
      ignored: 3
    )
  end

  def self.score_transitions
    {
      approved: statuses[:agreed],
      rejected: statuses[:disagreed],
      ignored: statuses[:ignored]
    }
  end

  # Generate `pending?`, `rejected?`, etc helper methods
  statuses.each do |name, id|
    define_method("#{name}?") { status == id }
    self.class.define_method(name) { where(status: id) }
  end

  def score_type
    Reviewable::Collection::Item.new(reviewable_score_type)
  end

  def took_action?
    take_action_bonus > 0
  end

  def self.calculate_score(user, type_bonus, take_action_bonus)
    score = user_flag_score(user) + type_bonus + take_action_bonus
    score > 0 ? score : 0
  end

  # A user's flag score is:
  #   1.0 + trust_level + user_accuracy_bonus
  #   (trust_level is 5 for staff)
  def self.user_flag_score(user)
    1.0 + (user.staff? ? 5.0 : user.trust_level.to_f) + user_accuracy_bonus(user)
  end

  # A user's accuracy bonus is:
  #   if 5 or less flags => 0.0
  #   if > 5 flags => (agreed flags / total flags) * 5.0
  def self.user_accuracy_bonus(user)
    user_stat = user&.user_stat
    return 0.0 if user_stat.blank? || user.bot?

    calc_user_accuracy_bonus(user_stat.flags_agreed, user_stat.flags_disagreed)
  end

  def self.calc_user_accuracy_bonus(agreed, disagreed)
    agreed ||= 0
    disagreed ||= 0

    total = (agreed + disagreed).to_f
    return 0.0 if total <= 5
    accuracy_axis = 0.7

    percent_correct = agreed / total
    positive_accuracy = percent_correct >= accuracy_axis

    bottom = positive_accuracy ? accuracy_axis : 0.0
    top = positive_accuracy ? 1.0 : accuracy_axis

    absolute_distance = positive_accuracy ?
                        percent_correct - bottom :
                        top - percent_correct

    axis_distance_multiplier = 1.0 / (top - bottom)
    positivity_multiplier = positive_accuracy ? 1.0 : -1.0

    (absolute_distance * axis_distance_multiplier * positivity_multiplier * (Math.log(total, 4) * 5.0))
      .round(2)
  end

  def reviewable_conversation
    return if meta_topic.blank?
    Reviewable::Conversation.new(meta_topic)
  end

end

# == Schema Information
#
# Table name: reviewable_scores
#
#  id                    :bigint           not null, primary key
#  reviewable_id         :integer          not null
#  user_id               :integer          not null
#  reviewable_score_type :integer          not null
#  status                :integer          not null
#  score                 :float            default(0.0), not null
#  take_action_bonus     :float            default(0.0), not null
#  reviewed_by_id        :integer
#  reviewed_at           :datetime
#  meta_topic_id         :integer
#  created_at            :datetime         not null
#  updated_at            :datetime         not null
#  reason                :string
#  user_accuracy_bonus   :float            default(0.0), not null
#
# Indexes
#
#  index_reviewable_scores_on_reviewable_id  (reviewable_id)
#  index_reviewable_scores_on_user_id        (user_id)
#
