# frozen_string_literal: true

class GroupUser < ActiveRecord::Base
  belongs_to :group
  belongs_to :user

  before_create :set_notification_level
  after_destroy :grant_other_available_title
  after_destroy :remove_primary_and_flair_group, :recalculate_trust_level
  after_save :update_title

  after_save :set_primary_group

  after_save :grant_trust_level
  after_save :set_category_notifications
  after_save :set_tag_notifications

  after_commit :increase_group_user_count, on: [:create]
  after_commit :decrease_group_user_count, on: [:destroy]

  def self.notification_levels
    NotificationLevels.all
  end

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

  def self.update_first_unread_pm(last_seen, limit: 10_000)
    DB.exec(
      <<~SQL,
    UPDATE group_users gu
    SET first_unread_pm_at = Y.min_date
    FROM (
      SELECT
        X.group_id,
        X.user_id,
        X.min_date
      FROM (
        SELECT
          gu.group_id,
          gu.user_id,
          COALESCE(Z.min_date, :now) min_date
        FROM group_users gu
        LEFT JOIN (
          SELECT
            gu2.group_id,
            gu2.user_id,
            MIN(t.updated_at) min_date
          FROM group_users gu2
          INNER JOIN topic_allowed_groups tag ON tag.group_id = gu2.group_id
          INNER JOIN topics t ON t.id = tag.topic_id
          INNER JOIN users u ON u.id = gu2.user_id
          LEFT JOIN topic_users tu ON t.id = tu.topic_id AND tu.user_id = gu2.user_id
          WHERE t.deleted_at IS NULL
          AND t.archetype = :archetype
          AND tu.last_read_post_number < CASE
                                         WHEN u.admin OR u.moderator #{SiteSetting.whispers_allowed_groups_map.any? ? "OR gu2.group_id IN (:whisperers_group_ids)" : ""}
                                         THEN t.highest_staff_post_number
                                         ELSE t.highest_post_number
                                         END
          AND (COALESCE(tu.notification_level, 1) >= 2)
          GROUP BY gu2.user_id, gu2.group_id
        ) AS Z ON Z.user_id = gu.user_id AND Z.group_id = gu.group_id
      ) AS X
      WHERE X.user_id IN (
        SELECT id
        FROM users
        WHERE last_seen_at IS NOT NULL
        AND last_seen_at > :last_seen
        ORDER BY last_seen_at DESC
        LIMIT :limit
      )
    ) Y
    WHERE gu.user_id = Y.user_id AND gu.group_id = Y.group_id
    SQL
      archetype: Archetype.private_message,
      last_seen: last_seen,
      limit: limit,
      now: 10.minutes.ago,
      whisperers_group_ids: SiteSetting.whispers_allowed_groups_map,
    )
  end

  protected

  def set_notification_level
    self.notification_level = group&.default_notification_level || 3
  end

  def set_primary_group
    user.update!(primary_group: group) if group.primary_group
  end

  def remove_primary_and_flair_group
    return if self.destroyed_by_association&.active_record == User # User is being destroyed, so don't try to update

    updates = {}
    updates[:primary_group_id] = nil if user.primary_group_id == group_id
    updates[:flair_group_id] = nil if user.flair_group_id == group_id

    user.update(updates) if updates.present?
  end

  def grant_other_available_title
    if group.title.present? && group.title == user.title
      user.update_attribute(:title, user.next_best_title)
    end
  end

  def update_title
    if group.title.present?
      DB.exec(
        "
        UPDATE users SET title = :title
        WHERE (title IS NULL OR title = '') AND id = :id",
        id: user_id,
        title: group.title,
      )
    end
  end

  def grant_trust_level
    return if group.grant_trust_level.nil? || group.grant_trust_level.zero?

    TrustLevelGranter.grant(group.grant_trust_level, user)
  end

  def recalculate_trust_level
    return if group.grant_trust_level.nil? || group.grant_trust_level.zero?
    return if self.destroyed_by_association&.active_record == User # User is being destroyed, so don't try to recalculate

    Promotion.recalculate(user, use_previous_trust_level: true)
  end

  def set_category_notifications
    self.class.set_category_notifications(group, user)
  end

  def self.set_category_notifications(group, user)
    bulk_set_category_notifications(group, [user.id])
  end

  def set_tag_notifications
    self.class.set_tag_notifications(group, user)
  end

  def self.set_tag_notifications(group, user)
    bulk_set_tag_notifications(group, [user.id])
  end

  def self.bulk_set_category_notifications(group, user_ids)
    defaults = group.group_category_notification_defaults.to_a
    return if defaults.empty?

    defaults.each do |default|
      DB.exec(
        <<~SQL,
          INSERT INTO category_users (user_id, category_id, notification_level)
          SELECT unnest(ARRAY[:user_ids]::int[]), :category_id, :notification_level
          ON CONFLICT (user_id, category_id) DO UPDATE
            SET notification_level = #{semantically_higher_notification_level_sql("EXCLUDED.notification_level", "category_users.notification_level")}
        SQL
        user_ids: user_ids,
        category_id: default.category_id,
        notification_level: default.notification_level,
      )
    end

    CategoryUser.auto_watch(user_ids: user_ids)
    CategoryUser.auto_track(user_ids: user_ids)
  end

  def self.bulk_set_tag_notifications(group, user_ids)
    defaults = group.group_tag_notification_defaults.to_a
    return if defaults.empty?

    defaults.each do |default|
      DB.exec(
        <<~SQL,
          INSERT INTO tag_users (user_id, tag_id, notification_level, created_at, updated_at)
          SELECT unnest(ARRAY[:user_ids]::int[]), :tag_id, :notification_level, NOW(), NOW()
          ON CONFLICT (user_id, tag_id) DO UPDATE
            SET notification_level = #{semantically_higher_notification_level_sql("EXCLUDED.notification_level", "tag_users.notification_level")},
                updated_at = NOW()
        SQL
        user_ids: user_ids,
        tag_id: default.tag_id,
        notification_level: default.notification_level,
      )
    end

    TagUser.auto_watch(user_ids: user_ids)
    TagUser.auto_track(user_ids: user_ids)
  end

  def increase_group_user_count
    Group.increment_counter(:user_count, self.group_id)
  end

  def decrease_group_user_count
    Group.decrement_counter(:user_count, self.group_id)
  end

  def self.semantically_higher_notification_level_sql(new_col, existing_col)
    <<~SQL.squish
      CASE
        WHEN (CASE #{new_col} WHEN 3 THEN 5 ELSE #{new_col} END) >=
             (CASE #{existing_col} WHEN 3 THEN 5 ELSE #{existing_col} END)
        THEN #{new_col}
        ELSE #{existing_col}
      END
    SQL
  end
  private_class_method :semantically_higher_notification_level_sql
end

# == Schema Information
#
# Table name: group_users
#
#  id                 :integer          not null, primary key
#  group_id           :integer          not null
#  user_id            :integer          not null
#  created_at         :datetime         not null
#  updated_at         :datetime         not null
#  owner              :boolean          default(FALSE), not null
#  notification_level :integer          default(2), not null
#  first_unread_pm_at :datetime         not null
#
# Indexes
#
#  index_group_users_on_group_id_and_user_id  (group_id,user_id) UNIQUE
#  index_group_users_on_user_id_and_group_id  (user_id,group_id) UNIQUE
#
