# frozen_string_literal: true

class TagUser < ActiveRecord::Base
  belongs_to :tag
  belongs_to :user

  scope :notification_level_visible,
        ->(notification_levels = TagUser.notification_levels.values) do
          select("tag_users.*")
            .distinct
            .joins(
              "LEFT OUTER JOIN tag_group_memberships ON tag_users.tag_id = tag_group_memberships.tag_id",
            )
            .joins(
              "LEFT OUTER JOIN tag_group_permissions ON tag_group_memberships.tag_group_id = tag_group_permissions.tag_group_id",
            )
            .joins("LEFT OUTER JOIN group_users on group_users.user_id = tag_users.user_id")
            .where(
              "(tag_group_permissions.group_id IS NULL
               OR tag_group_permissions.group_id IN (:everyone_group_id, group_users.group_id)
               OR group_users.group_id = :staff_group_id)
              AND tag_users.notification_level IN (:notification_levels)",
              staff_group_id: Group::AUTO_GROUPS[:staff],
              everyone_group_id: Group::AUTO_GROUPS[:everyone],
              notification_levels: notification_levels,
            )
        end

  def self.notification_levels
    NotificationLevels.all
  end

  def self.lookup(user, level)
    where(user: user, notification_level: notification_levels[level])
  end

  def self.batch_set(user, level, tags)
    tags ||= []
    changed = false

    records = TagUser.where(user: user, notification_level: notification_levels[level])
    old_ids = records.pluck(:tag_id)

    tag_ids =
      if tags.empty?
        []
      elsif tags.first.is_a?(String)
        Tag.where_name(tags).pluck(:id)
      else
        tags
      end

    Tag
      .where(id: tag_ids)
      .joins(:target_tag)
      .each { |tag| tag_ids[tag_ids.index(tag.id)] = tag.target_tag_id }

    tag_ids.uniq!

    if tag_ids.present? &&
         TagUser
           .where(user_id: user.id, tag_id: tag_ids)
           .where.not(notification_level: notification_levels[level])
           .update_all(notification_level: notification_levels[level]) > 0
      changed = true
    end

    remove = (old_ids - tag_ids)
    if remove.present?
      records.where("tag_id in (?)", remove).destroy_all
      changed = true
    end

    now = Time.zone.now

    new_records_attrs =
      (tag_ids - old_ids).map do |tag_id|
        {
          user_id: user.id,
          tag_id: tag_id,
          notification_level: notification_levels[level],
          created_at: now,
          updated_at: now,
        }
      end

    unless new_records_attrs.empty?
      result = TagUser.insert_all(new_records_attrs)
      changed = true if result.rows.length > 0
    end

    if changed
      auto_watch(user_id: user.id)
      auto_track(user_id: user.id)
    end

    changed
  end

  def self.change(user_id, tag_id, level)
    if tag_id.is_a?(::Tag)
      tag = tag_id
      tag_id = tag.id
    else
      tag = Tag.find_by_id(tag_id)
    end

    tag_id = tag.target_tag_id if tag.synonym?

    user_id = user_id.id if user_id.is_a?(::User)

    tag_id = tag_id.to_i
    user_id = user_id.to_i

    tag_user = TagUser.where(user_id:, tag_id:).first

    if tag_user
      return tag_user if tag_user.notification_level == level
      tag_user.notification_level = level
      tag_user.save
    else
      tag_user = TagUser.create(user_id:, tag_id:, notification_level: level)
    end

    auto_watch(user_id:)
    auto_track(user_id:)

    tag_user
  rescue ActiveRecord::RecordNotUnique
    # In case of a race condition to insert, do nothing
  end

  def self.auto_watch(opts)
    builder = DB.build <<~SQL
      UPDATE topic_users
         SET notification_level = CASE WHEN should_watch THEN :watching ELSE :tracking END
           , notifications_reason_id = CASE WHEN should_watch THEN :auto_watch_tag ELSE NULL END
        FROM (
            SELECT tu.topic_id
                 , tu.user_id
                 , CASE WHEN MAX(tag_users.notification_level) = :watching THEN true ELSE false END should_watch
                 , CASE WHEN MAX(tag_users.notification_level) IS NULL AND tu.notification_level = :watching AND tu.notifications_reason_id = :auto_watch_tag THEN true ELSE false END should_track
               FROM topic_users tu
         LEFT JOIN topic_tags ON tu.topic_id = topic_tags.topic_id
         LEFT JOIN tag_users ON tag_users.user_id = tu.user_id AND topic_tags.tag_id = tag_users.tag_id AND tag_users.notification_level = :watching
         /*where*/
          GROUP BY tu.topic_id, tu.user_id, tu.notification_level, tu.notifications_reason_id
        ) AS X
      WHERE X.topic_id = topic_users.topic_id
        AND X.user_id = topic_users.user_id
        AND (should_track OR should_watch)
    SQL

    builder.where("tu.notification_level IN (:tracking, :regular, :watching)")
    builder.where("COALESCE(tu.notifications_reason_id, 0) != :user_changed")

    if topic_id = opts[:topic_id]
      builder.where("tu.topic_id = :topic_id", topic_id:)
    end

    user_ids = opts[:user_ids] || opts[:user_id]
    builder.where("tu.user_id IN (:user_ids)", user_ids:) if user_ids.present?

    builder.exec(
      watching: notification_levels[:watching],
      tracking: notification_levels[:tracking],
      regular: notification_levels[:regular],
      auto_watch_tag: TopicUser.notification_reasons[:auto_watch_tag],
      user_changed: TopicUser.notification_reasons[:user_changed],
    )
  end

  def self.auto_track(opts)
    builder = DB.build <<~SQL
      UPDATE topic_users
         SET notification_level = :tracking
           , notifications_reason_id = :auto_track_tag
        FROM (
          SELECT DISTINCT tu.topic_id
               , tu.user_id
            FROM topic_users tu
            JOIN topic_tags ON tu.topic_id = topic_tags.topic_id
            JOIN tag_users ON tag_users.user_id = tu.user_id AND topic_tags.tag_id = tag_users.tag_id AND tag_users.notification_level = :tracking
            /*where*/
        ) AS X
       WHERE topic_users.notification_level = :regular
         AND COALESCE(topic_users.notifications_reason_id, 0) != :user_changed
         AND topic_users.topic_id = X.topic_id
         AND topic_users.user_id = X.user_id
    SQL

    if topic_id = opts[:topic_id]
      builder.where("tu.topic_id = :topic_id", topic_id:)
    end

    user_ids = opts[:user_ids] || opts[:user_id]
    builder.where("tu.user_id IN (:user_ids)", user_ids:) if user_ids.present?

    builder.exec(
      tracking: notification_levels[:tracking],
      regular: notification_levels[:regular],
      auto_track_tag: TopicUser.notification_reasons[:auto_track_tag],
      user_changed: TopicUser.notification_reasons[:user_changed],
    )
  end

  def self.notification_levels_for(user)
    # Anonymous users have all default tags set to regular tracking,
    # except for default muted tags which stay muted.
    if user.blank?
      default_tag_names = [
        SiteSetting.default_tags_watching_first_post.split("|"),
        SiteSetting.default_tags_watching.split("|"),
        SiteSetting.default_tags_tracking.split("|"),
      ].flatten

      muted_tag_names = SiteSetting.default_tags_muted.split("|")

      tag_data = Tag.where(name: default_tag_names + muted_tag_names).pluck(:name, :id).to_h

      notification_levels =
        default_tag_names.filter_map do |name|
          id = tag_data[name]
          [id, { level: self.notification_levels[:regular], name: }] if id
        end

      notification_levels +=
        muted_tag_names.filter_map do |name|
          id = tag_data[name]
          [id, { level: self.notification_levels[:muted], name: }] if id
        end
    else
      notification_levels =
        TagUser
          .notification_level_visible
          .where(user:)
          .joins(:tag)
          .pluck("tags.id", "tags.name", :notification_level)
          .map { |id, name, level| [id, { level:, name: }] }
    end

    notification_levels.to_h
  end
end

# == Schema Information
#
# Table name: tag_users
#
#  id                 :integer          not null, primary key
#  tag_id             :integer          not null
#  user_id            :integer          not null
#  notification_level :integer          not null
#  created_at         :datetime         not null
#  updated_at         :datetime         not null
#
# Indexes
#
#  index_tag_users_on_tag_id_and_user_id_and_notification_level  (tag_id,user_id,notification_level)
#  index_tag_users_on_user_id_and_tag_id                         (user_id,tag_id) UNIQUE
#  index_tag_users_on_user_id_and_tag_id_and_notification_level  (user_id,tag_id,notification_level)
#
