# frozen_string_literal: true

require "distributed_mutex"

module DiscourseNarrativeBot
  class NewUserNarrative < Base
    I18N_KEY = "discourse_narrative_bot.new_user_narrative".freeze
    BADGE_NAME = "Certified".freeze

    TRANSITION_TABLE = {
      begin: {
        init: {
          next_state: :tutorial_bookmark,
          next_instructions:
            Proc.new { I18n.t("#{I18N_KEY}.bookmark.instructions", base_uri: Discourse.base_path) },
          action: :say_hello,
        },
      },
      tutorial_bookmark: {
        next_state: :tutorial_onebox,
        next_instructions:
          Proc.new { I18n.t("#{I18N_KEY}.onebox.instructions", base_uri: Discourse.base_path) },
        bookmark: {
          action: :reply_to_bookmark,
        },
        reply: {
          next_state: :tutorial_bookmark,
          action: :missing_bookmark,
        },
      },
      tutorial_onebox: {
        next_state: :tutorial_emoji,
        next_instructions:
          Proc.new { I18n.t("#{I18N_KEY}.emoji.instructions", base_uri: Discourse.base_path) },
        reply: {
          action: :reply_to_onebox,
        },
      },
      tutorial_emoji: {
        prerequisite: Proc.new { SiteSetting.enable_emoji },
        next_state: :tutorial_mention,
        next_instructions:
          Proc.new do
            I18n.t(
              "#{I18N_KEY}.mention.instructions",
              discobot_username: self.discobot_username,
              base_uri: Discourse.base_path,
            )
          end,
        reply: {
          action: :reply_to_emoji,
        },
      },
      tutorial_mention: {
        prerequisite: Proc.new { SiteSetting.enable_mentions },
        next_state: :tutorial_formatting,
        next_instructions:
          Proc.new { I18n.t("#{I18N_KEY}.formatting.instructions", base_uri: Discourse.base_path) },
        reply: {
          action: :reply_to_mention,
        },
      },
      tutorial_formatting: {
        next_state: :tutorial_quote,
        next_instructions:
          Proc.new { I18n.t("#{I18N_KEY}.quoting.instructions", base_uri: Discourse.base_path) },
        reply: {
          action: :reply_to_formatting,
        },
      },
      tutorial_quote: {
        next_state: :tutorial_images,
        next_instructions:
          Proc.new { I18n.t("#{I18N_KEY}.images.instructions", base_uri: Discourse.base_path) },
        reply: {
          action: :reply_to_quote,
        },
      },
      # Note: tutorial_images and tutorial_likes are mutually exclusive.
      #       The prerequisites should ensure only one of them is called.
      tutorial_images: {
        prerequisite:
          Proc.new { @user.has_trust_level?(SiteSetting.min_trust_to_post_embedded_media) },
        next_state: :tutorial_likes,
        next_instructions:
          Proc.new { I18n.t("#{I18N_KEY}.likes.instructions", base_uri: Discourse.base_path) },
        reply: {
          action: :reply_to_image,
        },
        like: {
          action: :track_images_like,
        },
      },
      tutorial_likes: {
        prerequisite:
          Proc.new { !@user.has_trust_level?(SiteSetting.min_trust_to_post_embedded_media) },
        next_state: :tutorial_flag,
        next_instructions:
          Proc.new do
            I18n.t(
              "#{I18N_KEY}.flag.instructions",
              guidelines_url: url_helpers(:guidelines_url),
              about_url: url_helpers(:about_index_url),
              base_uri: Discourse.base_path,
            )
          end,
        like: {
          action: :reply_to_likes,
        },
        reply: {
          next_state: :tutorial_likes,
          action: :missing_likes_like,
        },
      },
      tutorial_flag: {
        prerequisite: Proc.new { SiteSetting.allow_flagging_staff },
        next_state: :tutorial_search,
        next_instructions:
          Proc.new { I18n.t("#{I18N_KEY}.search.instructions", base_uri: Discourse.base_path) },
        flag: {
          action: :reply_to_flag,
        },
        reply: {
          next_state: :tutorial_flag,
          action: :missing_flag,
        },
      },
      tutorial_search: {
        next_state: :end,
        reply: {
          action: :reply_to_search,
        },
      },
    }

    def self.badge_name
      BADGE_NAME
    end

    def self.search_answer
      ":herb:"
    end

    def self.search_answer_emoji
      "\u{1F33F}"
    end

    def self.reset_trigger
      I18n.t("discourse_narrative_bot.new_user_narrative.reset_trigger")
    end

    def reset_bot(user, post)
      if pm_to_bot?(post)
        reset_data(user, topic_id: post.topic_id)
      else
        reset_data(user)
      end

      Jobs.enqueue_in(2.seconds, :narrative_init, user_id: user.id, klass: self.class.to_s)
    end

    private

    def synchronize(user)
      if Rails.env.test?
        yield
      else
        DistributedMutex.synchronize("new_user_narrative_#{user.id}") { yield }
      end
    end

    def init_tutorial_search
      topic = @post.topic
      post = topic.first_post

      MessageBus.publish(
        "/new_user_narrative/tutorial_search/#{@user.id}",
        {},
        user_ids: [@user.id],
      )

      raw = <<~MD
      #{post.raw}

      #{I18n.t("#{I18N_KEY}.search.hidden_message", i18n_post_args.merge(search_answer: NewUserNarrative.search_answer))}
      MD

      PostRevisor.new(post, topic).revise!(
        self.discobot_user,
        { raw: raw },
        skip_validations: true,
        force_new_version: true,
      )

      set_state_data(:post_version, post.reload.version || 0)
    end

    def clean_up_tutorial_search
      first_post = @post.topic.first_post
      first_post.revert_to(get_state_data(:post_version) - 1)
      first_post.save!
      first_post.publish_change_to_clients!(:revised)
    end

    def say_hello
      raw =
        I18n.t(
          "#{I18N_KEY}.hello.message",
          i18n_post_args(username: @user.username, title: SiteSetting.title),
        )

      raw = <<~MD
      #{raw}

      #{instance_eval(&@next_instructions)}
      MD

      title = I18n.t("#{I18N_KEY}.hello.title", title: SiteSetting.title)
      title = title.gsub(/:([\w\-+]+(?::t\d)?):/, "").strip if SiteSetting.max_emojis_in_title == 0

      opts = {
        title: title,
        target_usernames: @user.username,
        archetype: Archetype.private_message,
        subtype: TopicSubtype.system_message,
      }

      if @post && @post.topic.private_message? &&
           @post.topic.topic_allowed_users.pluck(:user_id).include?(@user.id)
        opts = opts.merge(topic_id: @post.topic_id)
      end

      if @data[:topic_id]
        opts = opts.merge(topic_id: @data[:topic_id]).except(:title, :target_usernames, :archetype)
      end

      post = reply_to(@post, raw, opts)
      @data[:topic_id] = post.topic.id
      @data[:track] = self.class.to_s
      post
    end

    def missing_bookmark
      return unless valid_topic?(@post.topic_id)
      return if @post.user_id == self.discobot_user.id

      fake_delay
      enqueue_timeout_job(@user)
      unless @data[:attempted]
        reply_to(@post, I18n.t("#{I18N_KEY}.bookmark.not_found", i18n_post_args))
      end
      false
    end

    def reply_to_bookmark
      return unless valid_topic?(@post.topic_id)
      return unless @post.user_id == self.discobot_user.id

      profile_page_url = url_helpers(:user_url, username: @user.username)
      bookmark_url = "#{profile_page_url}/activity/bookmarks"
      raw = <<~MD
        #{I18n.t("#{I18N_KEY}.bookmark.reply", i18n_post_args(bookmark_url: bookmark_url))}

        #{instance_eval(&@next_instructions)}
      MD

      fake_delay

      reply = reply_to(@post, raw)
      enqueue_timeout_job(@user)
      reply
    end

    def reply_to_onebox
      post_topic_id = @post.topic_id
      return unless valid_topic?(post_topic_id)

      @post.post_analyzer.cook(@post.raw, {})

      if @post.post_analyzer.found_oneboxes?
        raw = <<~MD
          #{I18n.t("#{I18N_KEY}.onebox.reply", i18n_post_args)}

          #{instance_eval(&@next_instructions)}
        MD

        fake_delay

        reply = reply_to(@post, raw)
        enqueue_timeout_job(@user)
        reply
      else
        fake_delay
        unless @data[:attempted]
          reply_to(@post, I18n.t("#{I18N_KEY}.onebox.not_found", i18n_post_args))
        end
        enqueue_timeout_job(@user)
        false
      end
    end

    def track_images_like
      post_topic_id = @post.topic_id
      return unless valid_topic?(post_topic_id)

      post_liked =
        PostAction.exists?(
          post_action_type_id: PostActionType.types[:like],
          post_id: @data[:last_post_id],
          user_id: @user.id,
        )

      if post_liked
        set_state_data(:liked, true)

        if (post_id = get_state_data(:post_id)) && (post = Post.find_by(id: post_id))
          fake_delay
          like_post(post)

          raw = <<~MD
            #{I18n.t("#{I18N_KEY}.images.reply", i18n_post_args)}

            #{instance_eval(&@next_instructions)}
          MD

          reply = reply_to(@post, raw)
          enqueue_timeout_job(@user)
          return reply
        end
      end

      false
    end

    def reply_to_image
      post_topic_id = @post.topic_id
      return unless valid_topic?(post_topic_id)

      transition = true
      attempted_count = get_state_data(:attempted) || 0

      if attempted_count < 2
        @data[:skip_attempted] = true
        @data[:attempted] = false
      else
        @data[:skip_attempted] = false
      end

      cooked = @post.post_analyzer.cook(@post.raw, {})

      if Nokogiri::HTML5.fragment(cooked).css("img").size > 0
        set_state_data(:post_id, @post.id)

        if get_state_data(:liked)
          raw = <<~MD
            #{I18n.t("#{I18N_KEY}.images.reply", i18n_post_args)}

            #{instance_eval(&@next_instructions)}
          MD

          like_post(@post)
        else
          raw =
            I18n.t(
              "#{I18N_KEY}.images.like_not_found",
              i18n_post_args(url: Post.find_by(id: @data[:last_post_id]).url),
            )

          transition = false
        end
      else
        raw =
          I18n.t(
            "#{I18N_KEY}.images.not_found",
            i18n_post_args(
              image_url:
                "#{Discourse.base_url}/plugins/discourse-narrative-bot/images/dog-walk.gif",
            ),
          )

        transition = false
      end

      fake_delay

      set_state_data(:attempted, attempted_count + 1) if !transition
      reply = reply_to(@post, raw) unless @data[:attempted] && !transition
      enqueue_timeout_job(@user)
      transition ? reply : false
    end

    def missing_likes_like
      return unless valid_topic?(@post.topic_id)
      return if @post.user_id == self.discobot_user.id

      fake_delay
      enqueue_timeout_job(@user)

      last_post = Post.find_by(id: @data[:last_post_id])
      reply_to(@post, I18n.t("#{I18N_KEY}.likes.not_found", i18n_post_args(url: last_post.url)))
      false
    end

    def reply_to_likes
      post_topic_id = @post.topic_id
      return unless valid_topic?(post_topic_id)

      post_liked =
        PostAction.exists?(
          post_action_type_id: PostActionType.types[:like],
          post_id: @data[:last_post_id],
          user_id: @user.id,
        )

      if post_liked
        raw = <<~MD
          #{I18n.t("#{I18N_KEY}.likes.reply", i18n_post_args)}

          #{instance_eval(&@next_instructions)}
        MD

        fake_delay

        reply = reply_to(@post, raw)
        enqueue_timeout_job(@user)
        return reply
      end

      false
    end

    def reply_to_formatting
      post_topic_id = @post.topic_id
      return unless valid_topic?(post_topic_id)

      if Nokogiri::HTML5
           .fragment(@post.cooked)
           .css("b", "strong", "em", "i", ".bbcode-i", ".bbcode-b")
           .size > 0
        raw = <<~MD
          #{I18n.t("#{I18N_KEY}.formatting.reply", i18n_post_args)}

          #{instance_eval(&@next_instructions)}
        MD

        fake_delay

        reply = reply_to(@post, raw)
        enqueue_timeout_job(@user)
        reply
      else
        fake_delay
        unless @data[:attempted]
          reply_to(@post, I18n.t("#{I18N_KEY}.formatting.not_found", i18n_post_args))
        end
        enqueue_timeout_job(@user)
        false
      end
    end

    def reply_to_quote
      post_topic_id = @post.topic_id
      return unless valid_topic?(post_topic_id)

      doc = Nokogiri::HTML5.fragment(@post.cooked)

      if doc.css(".quote").size > 0
        raw = <<~MD
          #{I18n.t("#{I18N_KEY}.quoting.reply", i18n_post_args)}

          #{instance_eval(&@next_instructions)}
        MD

        fake_delay

        reply = reply_to(@post, raw)
        enqueue_timeout_job(@user)
        reply
      else
        fake_delay
        unless @data[:attempted]
          reply_to(@post, I18n.t("#{I18N_KEY}.quoting.not_found", i18n_post_args))
        end
        enqueue_timeout_job(@user)
        false
      end
    end

    def reply_to_emoji
      post_topic_id = @post.topic_id
      return unless valid_topic?(post_topic_id)

      doc = Nokogiri::HTML5.fragment(@post.cooked)

      if doc.css(".emoji").size > 0
        raw = <<~MD
          #{I18n.t("#{I18N_KEY}.emoji.reply", i18n_post_args)}

          #{instance_eval(&@next_instructions)}
        MD

        fake_delay

        reply = reply_to(@post, raw)
        enqueue_timeout_job(@user)
        reply
      else
        fake_delay
        unless @data[:attempted]
          reply_to(@post, I18n.t("#{I18N_KEY}.emoji.not_found", i18n_post_args))
        end
        enqueue_timeout_job(@user)
        false
      end
    end

    def reply_to_mention
      post_topic_id = @post.topic_id
      return unless valid_topic?(post_topic_id)

      if bot_mentioned?(@post)
        raw = <<~MD
          #{I18n.t("#{I18N_KEY}.mention.reply", i18n_post_args)}

          #{instance_eval(&@next_instructions)}
        MD

        fake_delay

        reply = reply_to(@post, raw)
        enqueue_timeout_job(@user)
        reply
      else
        fake_delay

        unless @data[:attempted]
          reply_to(
            @post,
            I18n.t(
              "#{I18N_KEY}.mention.not_found",
              i18n_post_args(username: @user.username, discobot_username: self.discobot_username),
            ),
          )
        end

        enqueue_timeout_job(@user)
        false
      end
    end

    def missing_flag
      return unless valid_topic?(@post.topic_id)

      # Remove any incorrect flags so that they can try again
      if @post.user_id == -2
        @post
          .post_actions
          .where(user_id: @user.id)
          .where(
            "post_action_type_id IN (?)",
            (PostActionType.flag_types.values - [PostActionType.types[:inappropriate]]),
          )
          .destroy_all
      end

      fake_delay
      reply_to(@post, I18n.t("#{I18N_KEY}.flag.not_found", i18n_post_args)) unless @data[:attempted]
      false
    end

    def reply_to_flag
      post_topic_id = @post.topic_id
      return unless valid_topic?(post_topic_id)
      return unless @post.user.id == -2

      raw = <<~MD
        #{I18n.t("#{I18N_KEY}.flag.reply", i18n_post_args)}

        #{instance_eval(&@next_instructions)}
      MD

      fake_delay

      reply = reply_to(@post, raw)
      @post.post_actions.where(user_id: @user.id).destroy_all

      enqueue_timeout_job(@user)
      reply
    end

    def reply_to_search
      post_topic_id = @post.topic_id
      return unless valid_topic?(post_topic_id)

      if @post.raw.include?(NewUserNarrative.search_answer) ||
           @post.raw.include?(NewUserNarrative.search_answer_emoji)
        fake_delay
        reply_to(
          @post,
          I18n.t("#{I18N_KEY}.search.reply", i18n_post_args(search_url: url_helpers(:search_url))),
        )
      else
        fake_delay
        unless @data[:attempted]
          reply_to(@post, I18n.t("#{I18N_KEY}.search.not_found", i18n_post_args))
        end
        enqueue_timeout_job(@user)
        false
      end
    end

    def end_reply
      fake_delay

      reply_to(
        @post,
        I18n.t(
          "#{I18N_KEY}.end.message",
          i18n_post_args(
            username: @user.username,
            base_url: Discourse.base_url,
            certificate: certificate,
            discobot_username: self.discobot_username,
            advanced_trigger: AdvancedUserNarrative.reset_trigger,
          ),
        ),
        topic_id: @data[:topic_id],
      )
    end

    def like_post(post)
      PostActionCreator.like(self.discobot_user, post)
    end

    def welcome_topic
      Topic.find_by(slug: "welcome-to-discourse", archetype: Archetype.default) ||
        Topic.recent(1).first
    end

    def url_helpers(url, opts = {})
      Rails.application.routes.url_helpers.public_send(
        url,
        opts.merge(host: Discourse.base_url_no_prefix),
      )
    end
  end
end