<commit_before># frozen_string_literal: true

class Post < ApplicationRecord
  include Websocket
  include PostConcerns::Autoflagging
  include PostConcerns::Review

  validate :reject_recent_duplicates
  validates :link, format: { with: %r{\A\/\/(.*?)\/(questions|a)\/(\d+)\Z} }, allow_blank: true, on: :create

  serialize :tags, JSON

  belongs_to :site
  belongs_to :stack_exchange_user
  belongs_to :smoke_detector
  has_and_belongs_to_many :reasons
  has_and_belongs_to_many :post_tags, class_name: 'DomainTag'
  has_and_belongs_to_many :spam_domains
  has_many :feedbacks, dependent: :destroy
  has_many :deletion_logs, dependent: :destroy
  has_many :flag_logs, dependent: :destroy
  has_many :flags, dependent: :destroy
  has_many :comments, class_name: 'PostComment', dependent: :destroy
  has_many :abuse_reports, as: :reportable
  has_one :review_item, as: :reviewable

  scope(:includes_for_post_row, -> do
    includes(:stack_exchange_user).includes(:reasons).includes(:site)
           .includes(feedbacks: [:user, :api_key]).includes(:comments)
  end)

  scope(:without_feedback, -> { where(feedbacks_count: 0).or(where(feedbacks_count: nil)) })

  scope(:today, -> { where('created_at > ?', Date.today) })

  scope(:tp, -> { where(is_tp: true) })
  scope(:fp, -> { where(is_fp: true) })
  scope(:naa, -> { where(is_naa: true) })

  scope(:undeleted, -> { where(deleted_at: nil) })

  after_commit :parse_domains, on: :create

  after_create do
    match = %r{\/(?:q(?:uestions)?|a(?:nswers)?)\/(\d+)}.match(link)
    update(native_id: match[1]) if match
  end

  after_create do
    ActionCable.server.broadcast 'posts_realtime', row: PostsController.render(locals: { post: Post.last }, partial: 'post').html_safe
    ActionCable.server.broadcast 'topbar', review: ReviewItem.active.count
  end

  after_save :populate_redis
  after_destroy :remove_from_redis, prepend: true

  def reject_recent_duplicates
    # If a different SmokeDetector has reported the same post in the last 5 minutes, reject it

    return unless respond_to?(:smoke_detector_id) && smoke_detector.present?

    conflict = Post.where(link: link)
                   .where('created_at > ?', 5.minutes.ago)
                   .where.not(smoke_detector: smoke_detector)
                   .last

    return if conflict.blank?

    errors.add(:base, "Reported in the last 5 minutes by a different instance: #{conflict.id}")
  end

  def populate_redis
    post = {
      body: body,
      title: title,
      reason_weight: reasons.map(&:weight).reduce(:+),
      created_at: created_at,
      username: username,
      link: link,
      site_site_logo: site.try(:site_logo),
      stack_exchange_user_username: stack_exchange_user.try(:username),
      stack_exchange_user_id: stack_exchange_user.try(:id),
      flagged: flagged?,
      site_id: site_id,
      post_comments_count: comments.count,
      why: why
    }
    redis.hmset("posts/#{id}", *post.to_a)

    reason_names = reasons.map(&:reason_name)
    reason_weights = reasons.map(&:weight)
    redis.zadd("posts/#{id}/reasons", reason_weights.zip(reason_names)) unless reasons.empty?

    feedbacks.each(&:populate_redis)
    deletion_logs.each(&:update_deletion_data)

    reasons.each do |reason|
      redis.sadd "reasons/#{reason.id}", id
    end

    redis.sadd 'all_posts', id
    redis.zadd 'posts', created_at.to_i, id

    redis.sadd 'tps', id if is_tp
    redis.sadd 'fps', id if is_fp
    redis.sadd 'naas', id if is_naa
    if link.nil?
      redis.sadd 'nolink', id
    else
      redis.sadd 'questions', id if question?
      redis.sadd 'answers', id if answer?
    end
    redis.sadd 'autoflagged', id if flagged?
    redis.sadd 'deleted', id unless deleted_at.nil?
    redis.sadd "sites/#{site_id}/posts", id
  end

  def remove_from_redis(post)
    redis.del "posts/#{post.id}"
    redis.del "posts/#{post.id}/reasons"
    # Test this one:
    Reason.find_each do |reason|
      redis.srem "reasons/#{reason.id}", post.id
    end
    redis.srem 'all_posts', post.id
    redis.zrem 'posts', post.id
    redis.srem 'tps', post.id
    redis.srem 'fps', post.id
    redis.srem 'naas', post.id
    redis.srem 'nolink', post.id
    redis.srem 'questions', post.id
    redis.srem 'answers', post.id
    redis.srem 'autoflagged', post.id
    redis.srem 'deleted', post.id
    redis.srem "sites/#{post.site_id}/posts", post.id
  end

  def self.populate_redis_meta
    progressbar = ProgressBar.create total: Post.count
    ilevel = ActiveRecord::Base.logger.level
    ActiveRecord::Base.logger.level = 1
    Post.all.eager_load(:reasons).eager_load(:flag_logs).find_each(batch_size: 50_000) do |post|
      # tps, fps, naas, reasons/id, questions, answers, autoflagged<commit_msg>Fix deletion, close #546<commit_after># frozen_string_literal: true

class Post < ApplicationRecord
  include Websocket
  include PostConcerns::Autoflagging
  include PostConcerns::Review

  validate :reject_recent_duplicates
  validates :link, format: { with: %r{\A\/\/(.*?)\/(questions|a)\/(\d+)\Z} }, allow_blank: true, on: :create

  serialize :tags, JSON

  belongs_to :site
  belongs_to :stack_exchange_user
  belongs_to :smoke_detector
  has_and_belongs_to_many :reasons
  has_and_belongs_to_many :post_tags, class_name: 'DomainTag'
  has_and_belongs_to_many :spam_domains
  has_many :feedbacks, dependent: :destroy
  has_many :deletion_logs, dependent: :destroy
  has_many :flag_logs, dependent: :destroy
  has_many :flags, dependent: :destroy
  has_many :comments, class_name: 'PostComment', dependent: :destroy
  has_many :abuse_reports, as: :reportable
  has_one :review_item, as: :reviewable

  scope(:includes_for_post_row, -> do
    includes(:stack_exchange_user).includes(:reasons).includes(:site)
           .includes(feedbacks: [:user, :api_key]).includes(:comments)
  end)

  scope(:without_feedback, -> { where(feedbacks_count: 0).or(where(feedbacks_count: nil)) })

  scope(:today, -> { where('created_at > ?', Date.today) })

  scope(:tp, -> { where(is_tp: true) })
  scope(:fp, -> { where(is_fp: true) })
  scope(:naa, -> { where(is_naa: true) })

  scope(:undeleted, -> { where(deleted_at: nil) })

  after_commit :parse_domains, on: :create

  after_create do
    match = %r{\/(?:q(?:uestions)?|a(?:nswers)?)\/(\d+)}.match(link)
    update(native_id: match[1]) if match
  end

  after_create do
    ActionCable.server.broadcast 'posts_realtime', row: PostsController.render(locals: { post: Post.last }, partial: 'post').html_safe
    ActionCable.server.broadcast 'topbar', review: ReviewItem.active.count
  end

  after_save :populate_redis
  after_destroy :remove_from_redis

  def reject_recent_duplicates
    # If a different SmokeDetector has reported the same post in the last 5 minutes, reject it

    return unless respond_to?(:smoke_detector_id) && smoke_detector.present?

    conflict = Post.where(link: link)
                   .where('created_at > ?', 5.minutes.ago)
                   .where.not(smoke_detector: smoke_detector)
                   .last

    return if conflict.blank?

    errors.add(:base, "Reported in the last 5 minutes by a different instance: #{conflict.id}")
  end

  def populate_redis
    post = {
      body: body,
      title: title,
      reason_weight: reasons.map(&:weight).reduce(:+),
      created_at: created_at,
      username: username,
      link: link,
      site_site_logo: site.try(:site_logo),
      stack_exchange_user_username: stack_exchange_user.try(:username),
      stack_exchange_user_id: stack_exchange_user.try(:id),
      flagged: flagged?,
      site_id: site_id,
      post_comments_count: comments.count,
      why: why
    }
    redis.hmset("posts/#{id}", *post.to_a)

    reason_names = reasons.map(&:reason_name)
    reason_weights = reasons.map(&:weight)
    redis.zadd("posts/#{id}/reasons", reason_weights.zip(reason_names)) unless reasons.empty?

    feedbacks.each(&:populate_redis)
    deletion_logs.each(&:update_deletion_data)

    reasons.each do |reason|
      redis.sadd "reasons/#{reason.id}", id
    end

    redis.sadd 'all_posts', id
    redis.zadd 'posts', created_at.to_i, id

    redis.sadd 'tps', id if is_tp
    redis.sadd 'fps', id if is_fp
    redis.sadd 'naas', id if is_naa
    if link.nil?
      redis.sadd 'nolink', id
    else
      redis.sadd 'questions', id if question?
      redis.sadd 'answers', id if answer?
    end
    redis.sadd 'autoflagged', id if flagged?
    redis.sadd 'deleted', id unless deleted_at.nil?
    redis.sadd "sites/#{site_id}/posts", id
  end

  def remove_from_redis
    redis.del "posts/#{id}"
    redis.del "posts/#{id}/reasons"
    # Test this one:
    Reason.find_each do |reason|
      redis.srem "reasons/#{reason.id}", id
    end
    redis.srem 'all_posts', id
    redis.zrem 'posts', id
    redis.srem 'tps', id
    redis.srem 'fps', id
    redis.srem 'naas', id
    redis.srem 'nolink', id
    redis.srem 'questions', id
    redis.srem 'answers', id
    redis.srem 'autoflagged', id
    redis.srem 'deleted', id
    redis.srem "sites/#{site_id}/posts", id
  end

  def self.populate_redis_meta
    progressbar = ProgressBar.create total: Post.count
    ilevel = ActiveRecord::Base.logger.level
    ActiveRecord::Base.logger.level = 1
    Post.all.eager_load(:reasons).eager_load(:flag_logs).find_each(batch_size: 50_000) do |post|
      # tps, fps, naas, reasons/id, questions, answers, autoflagged
{'fraction_non_alphanumeric': 0.1297774938784201, 'fraction_numerical': 0.002661556478228468, 'mean_word_length': 3.629866929521932, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 21, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5274303', 'n_tokens_mistral': 3411, 'n_tokens_neox': 3285, 'n_words': 696}