# Responsible for creating posts and topics
#
require_dependency 'rate_limiter'
require_dependency 'topic_creator'

class PostCreator

  attr_reader :errors, :opts

  def self.create(user,opts)
    self.new(user,opts).create
  end

  # Acceptable options:
  #
  #   raw                     - raw text of post
  #   image_sizes             - We can pass a list of the sizes of images in the post as a shortcut.
  #   invalidate_oneboxes     - Whether to force invalidation of oneboxes in this post
  #   acting_user             - The user performing the action might be different than the user
  #                             who is the post "author." For example when copying posts to a new
  #                             topic.
  #   created_at              - Post creation time (optional)
  #
  #   When replying to a topic:
  #     topic_id              - topic we're replying to
  #     reply_to_post_number  - post number we're replying to
  #
  #   When creating a topic:
  #     title                 - New topic title
  #     archetype             - Topic archetype
  #     category              - Category to assign to topic
  #     target_usernames      - comma delimited list of usernames for membership (private message)
  #     target_group_names    - comma delimited list of groups for membership (private message)
  #     meta_data             - Topic meta data hash
  def initialize(user, opts)
    # TODO: we should reload user in case it is tainted, should take in a user_id as opposed to user
    # If we don't do this we introduce a rather risky dependency
    @user = user
    @opts = opts
    @spam = false
  end

  # True if the post was considered spam
  def spam?
    @spam
  end

  def guardian
    @guardian ||= Guardian.new(@user)
  end

  def create
    @topic = nil
    @post = nil
    @new_topic = false

    Post.transaction do
      setup_topic
      setup_post
      rollback_if_host_spam_detected
      save_post
      extract_links
      store_unique_post_key
      send_notifications_for_private_message
      track_topic
      update_user_counts
      publish
      @post.advance_draft_sequence
      @post.save_reply_relationships
    end

    enqueue_jobs
    @post
  end


  # Shortcut
  def self.create(user, opts)
    PostCreator.new(user, opts).create
  end

  protected

  def secure_group_ids(topic)
    @secure_group_ids ||= if topic.category && topic.category.secure?
      topic.category.secure_group_ids
    end
  end

  def after_post_create
    if @post.post_number > 1
      TopicTrackingState.publish_unread(@post)
    end
  end

  def after_topic_create

    # Don't publish invisible topics
    return unless @topic.visible?

    return if @topic.private_message?

    @topic.posters = @topic.posters_summary
    @topic.posts_count = 1

    TopicTrackingState.publish_new(@topic)
  end


  def clear_possible_flags(topic)
    # at this point we know the topic is a PM and has been replied to ... check if we need to clear any flags
    #
    first_post = Post.select(:id).where(topic_id: topic.id).where('post_number = 1').first
    post_action = nil

    if first_post
      post_action = PostAction.where(
        related_post_id: first_post.id,
        deleted_at: nil,
        post_action_type_id: PostActionType.types[:notify_moderators]
      ).first
    end

    if post_action
      post_action.remove_act!(@user)
    end
  end

  private

  def setup_topic
    if @opts[:topic_id].blank?
      topic_creator = TopicCreator.new(@user, guardian, @opts)

      begin
        topic = topic_creator.create
        @errors = topic_creator.errors
      rescue ActiveRecord::Rollback => ex
        # In the event of a rollback, grab the errors from the topic
        @errors = topic_creator.errors
        raise ex
      end

      @new_topic = true
    else
      topic = Topic.where(id: @opts[:topic_id]).first
      guardian.ensure_can_create!(Post, topic)
    end
    @topic = topic
  end

  def setup_post
    post = @topic.posts.new(raw: @opts[:raw],
                           user: @user,
                           reply_to_post_number: @opts[:reply_to_post_number])

    post.post_type = @opts[:post_type] if @opts[:post_type].present?
    post.no_bump = @opts[:no_bump] if @opts[:no_bump].present?
    post.extract_quoted_post_numbers
    post.acting_user = @opts[:acting_user] if @opts[:acting_user].present?
    post.created_at = Time.zone.parse(@opts[:created_at].to_s) if @opts[:created_at].present?

    post.image_sizes = @opts[:image_sizes] if @opts[:image_sizes].present?
    post.invalidate_oneboxes = @opts[:invalidate_oneboxes] if @opts[:invalidate_oneboxes].present?
    @post = post
  end

  def rollback_if_host_spam_detected
    if @post.has_host_spam?
      @post.errors.add(:base, I18n.t(:spamming_host))
      @errors = @post.errors
      @spam = true
      raise ActiveRecord::Rollback.new
    end
  end

  def save_post
    unless @post.save
      @errors = @post.errors
      raise ActiveRecord::Rollback.new
    end
  end

  def store_unique_post_key
    if SiteSetting.unique_posts_mins > 0
      $redis.setex(@post.unique_post_key, SiteSetting.unique_posts_mins.minutes.to_i, "1")
    end
  end

  def send_notifications_for_private_message
    # send a mail to notify users in case of a private message
    if @topic.private_message?
      @topic.allowed_users.where(["users.email_private_messages = true and users.id != ?", @user.id]).each do |u|
        Jobs.enqueue_in(SiteSetting.email_time_window_mins.minutes,
                          :user_email,
                          type: :private_message,
                          user_id: u.id,
                          post_id: @post.id
                       )
      end

      clear_possible_flags(@topic) if @post.post_number > 1 && @topic.user_id != @post.user_id
    end
  end

  def update_user_counts
    # We don't count replies to your own topics
    if @user.id != @topic.user_id
      @user.update_topic_reply_count
    end

    @user.last_posted_at = @post.created_at
    @user.save!
  end

  def publish
    if @post.post_number > 1
      MessageBus.publish("/topic/#{@post.topic_id}",{
                      id: @post.id,
                      created_at: @post.created_at,
                      user: BasicUserSerializer.new(@post.user).as_json(root: false),
                      post_number: @post.post_number
                    },
                    group_ids: secure_group_ids(@topic)
      )
    end
  end

  def extract_links
    TopicLink.extract_from(@post)
  end

  def track_topic
    TopicUser.auto_track(@user.id, @topic.id, TopicUser.notification_reasons[:created_post])
  end

  def enqueue_jobs
    if @post && !@post.errors.present?

      # We need to enqueue jobs after the transaction. Otherwise they might begin before the data has
      # been comitted.
      topic_id = @opts[:topic_id] || @topic.try(:id)
      Jobs.enqueue(:feature_topic_users, topic_id: @topic.id) if topic_id.present?
      @post.trigger_post_process
      after_post_create
      after_topic_create if @new_topic
    end
  end
end