class Comment < ApplicationRecord
  include Flaggable
  include HasPublicAuthor
  include Graphqlable
  include Notifiable
  include Searchable

  COMMENTABLE_TYPES = %w[Debate Proposal Budget::Investment Poll Topic
                         Legislation::Question Legislation::Annotation
                         Legislation::Proposal].freeze

  acts_as_paranoid column: :hidden_at
  include ActsAsParanoidAliases
  acts_as_votable
  has_ancestry touch: true

  attr_accessor :as_moderator, :as_administrator

  translates :body, touch: true
  include Globalizable

  validates_translation :body, presence: true
  validates :user, presence: true

  validates :commentable_type, inclusion: { in: ->(*) { COMMENTABLE_TYPES }}

  validate :validate_body_length
  validate :comment_valuation, if: -> { valuation }

  belongs_to :commentable, -> { with_hidden }, polymorphic: true, counter_cache: true, touch: true
  belongs_to :user, -> { with_hidden }, inverse_of: :comments

  before_save :calculate_confidence_score

  scope :for_render, -> { with_hidden.includes(user: :organization) }
  scope :with_visible_author, -> { joins(:user).where(users: { hidden_at: nil }) }
  scope :not_as_admin_or_moderator, -> { where(administrator_id: nil).where(moderator_id: nil) }
  scope :sort_by_flags, -> { order(flags_count: :desc, updated_at: :desc) }
  scope :public_for_api, -> do
    not_valuations
      .where(commentable: [Debate.public_for_api,
                           Proposal.public_for_api,
                           Poll.public_for_api,
                           Budget::Investment.public_for_api])
  end

  scope :sort_by_most_voted, -> { order(confidence_score: :desc, created_at: :desc) }
  scope :sort_descendants_by_most_voted, -> { order(confidence_score: :desc, created_at: :asc) }
  scope :sort_by_supports, -> { order(Arel.sql("cached_votes_up - cached_votes_down DESC")) }

  scope :sort_by_newest, -> { order(created_at: :desc) }
  scope :sort_descendants_by_newest, -> { order(created_at: :desc) }

  scope :sort_by_oldest, -> { order(created_at: :asc) }
  scope :sort_descendants_by_oldest, -> { order(created_at: :asc) }

  scope :not_valuations, -> { where(valuation: false) }

  after_create :call_after_commented

  def self.build(commentable, user, body, p_id = nil, valuation = false)
    new(commentable: commentable,
        user_id: user.id,
        body: body,
        parent_id: p_id,
        valuation: valuation)
  end

  def self.find_commentable(c_type, c_id)
    c_type.constantize.find(c_id)
  end

  def author_id
    user_id
  end

  def author
    user
  end

  def author=(author)
    self.user = author
  end

  def human_name
    body.truncate(32)
  end

  def total_votes
    cached_votes_total
  end

  def total_likes
    cached_votes_up
  end

  def total_dislikes
    cached_votes_down
  end

  def as_administrator?
    administrator_id.present?
  end

  def as_moderator?
    moderator_id.present?
  end

  def after_hide
    commentable_type.constantize.with_hidden.reset_counters(commentable_id, :comments)
  end

  def after_restore
    commentable_type.constantize.with_hidden.reset_counters(commentable_id, :comments)
  end

  def reply?
    !root?
  end

  def call_after_commented
    commentable.after_commented if commentable.respond_to?(:after_commented)
  end

  def self.body_max_length
    Setting["comments_body_max_length"].to_i
  end

  def calculate_confidence_score
    self.confidence_score = ScoreCalculator.confidence_score(cached_votes_total,
                                                             cached_votes_up)
  end

  def votes_score
    cached_votes_up - cached_votes_down
  end

  def searchable_values
    {
      body => "A",
      commentable&.title => "B"
    }
  end

  def self.search(terms)
    pg_search(terms)
  end

  private

    def validate_body_length
      validator = ActiveModel::Validations::LengthValidator.new(
        attributes: :body,
        maximum: Comment.body_max_length
      )
      validator.validate(self)
    end

    def comment_valuation
      unless author.can?(:comment_valuation, commentable)
        errors.add(:valuation, :cannot_comment_valuation)
      end
    end
end
