# frozen_string_literal: true

class Chat::ChatMessageUpdater
  attr_reader :error

  def self.update(opts)
    instance = new(**opts)
    instance.update
    instance
  end

  def initialize(guardian:, chat_message:, new_content:, upload_ids: nil)
    @guardian = guardian
    @user = guardian.user
    @chat_message = chat_message
    @old_message_content = chat_message.message
    @chat_channel = @chat_message.chat_channel
    @new_content = new_content
    @upload_ids = upload_ids
    @error = nil
  end

  def update
    begin
      validate_channel_status!
      @guardian.ensure_can_edit_chat!(@chat_message)
      @chat_message.message = @new_content
      @chat_message.last_editor_id = @user.id
      upload_info = get_upload_info
      validate_message!(has_uploads: upload_info[:uploads].any?)
      @chat_message.cook
      @chat_message.save!
      update_uploads(upload_info)
      revision = save_revision!
      @chat_message.reload
      ChatPublisher.publish_edit!(@chat_channel, @chat_message)
      Jobs.enqueue(:process_chat_message, { chat_message_id: @chat_message.id })
      Chat::ChatNotifier.notify_edit(chat_message: @chat_message, timestamp: revision.created_at)
      DiscourseEvent.trigger(:chat_message_edited, @chat_message, @chat_channel, @user)
    rescue => error
      @error = error
    end
  end

  def failed?
    @error.present?
  end

  private

  def validate_channel_status!
    return if @guardian.can_modify_channel_message?(@chat_channel)
    raise StandardError.new(
            I18n.t(
              "chat.errors.channel_modify_message_disallowed",
              status: @chat_channel.status_name,
            ),
          )
  end

  def validate_message!(has_uploads:)
    @chat_message.validate_message(has_uploads: has_uploads)
    if @chat_message.errors.present?
      raise StandardError.new(@chat_message.errors.map(&:full_message).join(", "))
    end
  end

  def get_upload_info
    return { uploads: [] } if @upload_ids.nil? || !SiteSetting.chat_allow_uploads

    uploads = Upload.where(id: @upload_ids, user_id: @user.id)
    if uploads.count != @upload_ids.count
      # User is passing upload_ids for uploads that they don't own. Don't change anything.
      return { uploads: @chat_message.uploads, changed: false }
    end

    new_upload_ids = uploads.map(&:id)
    existing_upload_ids = @chat_message.upload_ids
    difference = (existing_upload_ids + new_upload_ids) - (existing_upload_ids & new_upload_ids)
    { uploads: uploads, changed: difference.any? }
  end

  def update_uploads(upload_info)
    return unless upload_info[:changed]

    DB.exec("DELETE FROM chat_uploads WHERE chat_message_id = #{@chat_message.id}")
    UploadReference.where(target: @chat_message).destroy_all
    @chat_message.attach_uploads(upload_info[:uploads])
  end

  def save_revision!
    @chat_message.revisions.create!(
      old_message: @old_message_content,
      new_message: @chat_message.message,
      user_id: @user.id,
    )
  end
end
