# frozen_string_literal: true
class CustomWizard::Action
  attr_accessor :submission,
                :action,
                :user,
                :guardian,
                :result

  def initialize(opts)
    @wizard = opts[:wizard]
    @action = opts[:action]
    @user = @wizard.user
    @guardian = Guardian.new(@user)
    @submission = opts[:submission]
    @log = []
    @result = CustomWizard::ActionResult.new
  end

  def perform
    ActiveRecord::Base.transaction do
      self.send(action['type'].to_sym)
    end

    if creates_post? && @result.success?
      @result.handler.enqueue_jobs
    end

    if @result.success? && @result.output.present?
      @submission.fields[action['id']] = @result.output
    end

    save_log

    @result.submission = @submission
    @result
  end

  def mapper_data
    @mapper_data ||= @submission&.fields_and_meta || {}
  end

  def mapper
    @mapper ||= CustomWizard::Mapper.new(user: user, data: mapper_data)
  end

  def create_topic
    params = basic_topic_params.merge(public_topic_params)

    if params[:title].present? && params[:raw].present?
      creator = PostCreator.new(user, params)
      post = creator.create

      if creator.errors.present?
        messages = creator.errors.full_messages.join(" ")
        log_error("failed to create", messages)
      elsif action['skip_redirect'].blank?
        @submission.redirect_on_complete = post.topic.url
      end

      if creator.errors.blank?
        log_success("created topic", "id: #{post.topic.id}")
        result.handler = creator
        result.output = post.topic.id
      end
    else
      log_error("invalid topic params", "title: #{params[:title]}; post: #{params[:raw]}")
    end
  end

  def send_message

    if action['required'].present?
      required = CustomWizard::Mapper.new(
        inputs: action['required'],
        data: mapper_data,
        user: user
      ).perform

      if required.blank?
        log_error("required input not present")
        return
      end
    end

    params = basic_topic_params

    targets = CustomWizard::Mapper.new(
      inputs: action['recipient'],
      data: mapper_data,
      user: user,
      multiple: true
    ).perform

    if targets.blank?
      log_error("no recipients", "send_message has no recipients")
      return
    end

    params[:target_group_names] = []
    params[:target_usernames] = []
    targets.each do |target|
      if Group.find_by(name: target)
        params[:target_group_names] << target
      elsif User.find_by_username(target)
        params[:target_usernames] << target
      else
        #
      end
    end

    if params[:title].present? &&
       params[:raw].present? &&
       (params[:target_usernames].present? ||
        params[:target_group_names].present?)

      params[:archetype] = Archetype.private_message

      creator = PostCreator.new(user, params)
      post = creator.create

      if creator.errors.present?
        messages = creator.errors.full_messages.join(" ")
        log_error("failed to create message", messages)
      elsif action['skip_redirect'].blank?
        @submission.redirect_on_complete = post.topic.url
      end

      if creator.errors.blank?
        log_success("created message", "id: #{post.topic.id}")
        result.handler = creator
        result.output = post.topic.id
      end
    else
      log_error(
        "invalid message params",
        "title: #{params[:title]}; post: #{params[:raw]}; recipients: #{params[:target_usernames]}"
      )
    end
  end

  def update_profile
    params = {}

    if (profile_updates = action['profile_updates'])
      profile_updates.first[:pairs].each do |pair|
        if allowed_profile_field?(pair['key'])
          key = cast_profile_key(pair['key'])
          value = cast_profile_value(
            mapper.map_field(
              pair['value'],
              pair['value_type']
            ),
            pair['key']
          )

          if user_field?(pair['key'])
            params[:custom_fields] ||= {}
            params[:custom_fields][key] = value
          else
            params[key.to_sym] = value
          end
        end
      end
    end

    params = add_custom_fields(params)

    if params.present?
      result = UserUpdater.new(Discourse.system_user, user).update(params)

      if params[:avatar].present?
        result = update_avatar(params[:avatar])
      end

      if result
        log_success("updated profile fields", "fields: #{params.keys.map(&:to_s).join(',')}")
      else
        log_error("failed to update profile fields", "result: #{result.inspect}")
      end
    else
      log_error("invalid profile fields params", "params: #{params.inspect}")
    end
  end

  def watch_categories
    watched_categories = CustomWizard::Mapper.new(
      inputs: action['categories'],
      data: mapper_data,
      user: user
    ).perform

    watched_categories = [*watched_categories].map(&:to_i)

    notification_level = action['notification_level']

    if notification_level.blank?
      log_error("Notifcation Level was not set. Exiting wizard action")
      return
    end

    mute_remainder = CustomWizard::Mapper.new(
      inputs: action['mute_remainder'],
      data: mapper_data,
      user: user
    ).perform

    users = []

    if action['usernames']
      mapped_users = CustomWizard::Mapper.new(
        inputs: action['usernames'],
        data: mapper_data,
        user: user
      ).perform

      if mapped_users.present?
        mapped_users = mapped_users.split(',')
          .map { |username| User.find_by(username: username) }
        users.push(*mapped_users)
      end
    end

    if ActiveRecord::Type::Boolean.new.cast(action['wizard_user'])
      users.push(user)
    end

    category_ids = Category.all.pluck(:id)
    set_level = CategoryUser.notification_levels[notification_level.to_sym]
    mute_level = CategoryUser.notification_levels[:muted]

    users.each do |user|
      category_ids.each do |category_id|
        new_level = nil

        if watched_categories.include?(category_id) && set_level != nil
          new_level = set_level
        elsif mute_remainder
          new_level = mute_level
        end

        if new_level
          CategoryUser.set_notification_level_for_category(user, new_level, category_id)
        end
      end

      if watched_categories.any?
        log_success("#{user.username} notifications for #{watched_categories} set to #{set_level}")
      end

      if mute_remainder
        log_success("#{user.username} notifications for all other categories muted")
      end
    end
  end

  def send_to_api
    api_body = nil

    if action['api_body'] != ""
      begin
        api_body_parsed = JSON.parse(action['api_body'])
      rescue JSON::ParserError
        raise Discourse::InvalidParameters, "Invalid API body definition: #{action['api_body']} for #{action['title']}"
      end
      api_body = JSON.parse(mapper.interpolate(JSON.generate(api_body_parsed)))
    end

    result = CustomWizard::Api::Endpoint.request(user, action['api'], action['api_endpoint'], api_body)

    if error = result['error'] || (result[0] && result[0]['error'])
      error = error['message'] || error
      log_error("api request failed", "message: #{error}")
    else
      log_success("api request succeeded", "result: #{result}")
    end
  end

  def open_composer
    params = basic_topic_params

    if params[:title].present? && params[:raw].present?
      url = "/new-topic?title=#{encode_query_param(params[:title])}"
      url += "&body=#{encode_query_param(params[:raw])}"

      if category_id = action_category
        url += "&category_id=#{category_id}"
      end

      if tags = action_tags
        url += "&tags=#{tags.join(',')}"
      end

      route_to = Discourse.base_uri + url
      @result.output = @submission.route_to = route_to

      log_success("route: #{route_to}")
    else
      log_error("invalid composer params", "title: #{params[:title]}; post: #{params[:raw]}")
    end
  end

  def add_to_group
    group_map = CustomWizard::Mapper.new(
      inputs: action['group'],
      data: mapper_data,
      user: user,
      opts: {
        multiple: true
      }
    ).perform

    group_map = group_map.flatten.compact

    unless group_map.present?
      log_error("invalid group map")
      return
    end

    groups = group_map.reduce([]) do |result, g|
      begin
        result.push(Integer(g))
      rescue ArgumentError
        group = Group.find_by(name: g)
        result.push(group.id) if group
      end

      result
    end

    result = nil

    if groups.present?
      groups.each do |group_id|
        group = Group.find(group_id) if group_id
        result = group.add(user) if group
      end
    end

    if result
      log_success("added to groups", "groups: #{groups.map(&:to_s).join(',')}")
    else
      detail = groups.present? ? "groups: #{groups.map(&:to_s).join(',')}" : nil
      log_error("failed to add to groups", detail)
    end
  end

  def route_to
    return unless (url_input = action['url']).present?

    if url_input.is_a?(String)
      url = mapper.interpolate(url_input)
    else
      url = CustomWizard::Mapper.new(
        inputs: url_input,
        data: mapper_data,
        user: user
      ).perform
    end

    if action['code']
      @submission.fields[action['code']] = SecureRandom.hex(8)
      url += "&#{action['code']}=#{@submission.fields[action['code']]}"
    end

    route_to = UrlHelper.encode(url)
    @submission.route_to = route_to

    log_info("route: #{route_to}")
  end

  def create_group
    group =
      begin
        Group.new(new_group_params.except(:usernames, :owner_usernames))
      rescue ArgumentError => e
        raise Discourse::InvalidParameters, "Invalid group params"
      end

    if group.save
      def get_user_ids(username_string)
        User.where(username: username_string.split(",")).pluck(:id)
      end

      if new_group_params[:owner_usernames].present?
        owner_ids = get_user_ids(new_group_params[:owner_usernames])
        owner_ids.each { |user_id| group.group_users.build(user_id: user_id, owner: true) }
      end

      if new_group_params[:usernames].present?
        user_ids = get_user_ids(new_group_params[:usernames])
        user_ids -= owner_ids if owner_ids
        user_ids.each { |user_id| group.group_users.build(user_id: user_id) }
      end

      log_success("Group created", group.name)

      result.output = group.name
    else
      log_error("Group creation failed", group.errors.messages)
    end
  end

  def create_category
    category =
      begin
        Category.new(new_category_params.merge(user: user))
      rescue ArgumentError => e
        raise Discourse::InvalidParameters, "Invalid category params"
      end

    if category.save
      StaffActionLogger.new(user).log_category_creation(category)
      log_success("Category created", category.name)
      result.output = category.id
    else
      log_error("Category creation failed", category.errors.messages)
    end
  end

  private

  def action_category
    output = CustomWizard::Mapper.new(
      inputs: action['category'],
      data: mapper_data,
      user: user
    ).perform

    return false unless output.present?

    if output.is_a?(Array)
      output.first
    elsif output.is_a?(Integer)
      output
    elsif output.is_a?(String)
      output.to_i
    end
  end

  def action_tags
    output = CustomWizard::Mapper.new(
      inputs: action['tags'],
      data: mapper_data,
      user: user,
    ).perform

    return false unless output.present?

    if output.is_a?(Array)
      output.flatten
    else output.is_a?(String)
      [*output]
    end
  end

  def add_custom_fields(params = {})
    if (custom_fields = action['custom_fields']).present?
      field_map = CustomWizard::Mapper.new(
        inputs: custom_fields,
        data: mapper_data,
        user: user
      ).perform
      registered_fields = CustomWizard::CustomField.full_list

      field_map.each do |field|
        keyArr = field[:key].split('.')
        value = field[:value]

        if keyArr.length > 1
          klass = keyArr.first.to_sym
          name = keyArr.second

          if keyArr.length === 3 && name.include?("{}")
            name = name.gsub("{}", "")
            json_attr = keyArr.last
            type = :json
          end
        else
          name = keyArr.first
        end

        registered = registered_fields.select { |f| f.name == name }.first
        if registered.present?
          klass = registered.klass
          type = registered.type
        end

        next if type === :json && json_attr.blank?

        if klass === :topic
          params[:topic_opts] ||= {}
          params[:topic_opts][:custom_fields] ||= {}

          if type === :json
            params[:topic_opts][:custom_fields][name] ||= {}
            params[:topic_opts][:custom_fields][name][json_attr] = value
          else
            params[:topic_opts][:custom_fields][name] = value
          end
        else
          if type === :json
            params[:custom_fields][name] ||= {}
            params[:custom_fields][name][json_attr] = value
          else
            params[:custom_fields] ||= {}
            params[:custom_fields][name] = value
          end
        end
      end
    end

    params
  end

  def basic_topic_params
    params = {
      skip_validations: true,
      topic_opts: {
        custom_fields: {
          wizard_submission_id: @wizard.current_submission.id
        }
      }
    }

    params[:title] = CustomWizard::Mapper.new(
      inputs: action['title'],
      data: mapper_data,
      user: user
    ).perform

    params[:raw] = action['post_builder'] ?
      mapper.interpolate(
        action['post_template'],
        user: true,
        value: true,
        wizard: true,
        template: true
      ) :
      @submission.fields[action['post']]

    params[:import_mode] = ActiveRecord::Type::Boolean.new.cast(action['suppress_notifications'])

    add_custom_fields(params)
  end

  def public_topic_params
    params = {}

    if category = action_category
      params[:category] = category
    end

    if tags = action_tags
      params[:tags] = tags
    end

    if public_topic_fields.any?
      public_topic_fields.each do |field|
        unless action[field].nil? || action[field] == ""
          params[field.to_sym] = CustomWizard::Mapper.new(
            inputs: action[field],
            data: mapper_data,
            user: user
          ).perform
        end
      end
    end

    params
  end

  def new_group_params
    params = {}

    %w(
      name
      full_name
      title
      bio_raw
      owner_usernames
      usernames
      mentionable_level
      messageable_level
      visibility_level
      members_visibility_level
      grant_trust_level
    ).each do |attr|
      input = action[attr]

      if attr === "name" && input.blank?
        raise ArgumentError.new
      end

      if attr === "full_name" && input.blank?
        input = action["name"]
      end

      if input.present?
        value = CustomWizard::Mapper.new(
          inputs: input,
          data: mapper_data,
          user: user
        ).perform

        if value
          value = value.parameterize(separator: '_') if attr === "name"
          value = value.to_i if attr.include?("_level")

          params[attr.to_sym] = value
        end
      end
    end

    add_custom_fields(params)
  end

  def new_category_params
    params = {}

    %w(
      name
      slug
      color
      text_color
      parent_category_id
      permissions
    ).each do |attr|
      if action[attr].present?
        value = CustomWizard::Mapper.new(
          inputs: action[attr],
          data: mapper_data,
          user: user
        ).perform

        if value
          if attr === "parent_category_id" && value.is_a?(Array)
            value = value[0]
          end

          if attr === "permissions" && value.is_a?(Array)
            permissions = value
            value = {}

            permissions.each do |p|
              k = p[:key]
              v = p[:value].to_i

              if k.is_a?(Array)
                group = Group.find_by(id: k[0])
                k = group.name
              else
                k = k.parameterize(separator: '_')
              end

              value[k] = v
            end
          end

          if attr === 'slug'
            value = value.parameterize(separator: '-')
          end

          params[attr.to_sym] = value
        end
      end
    end

    add_custom_fields(params)
  end

  def creates_post?
    [:create_topic, :send_message].include?(action['type'].to_sym)
  end

  def public_topic_fields
    ['visible']
  end

  def profile_url_fields
    ['profile_background', 'card_background']
  end

  def cast_profile_key(key)
    if profile_url_fields.include?(key)
      "#{key}_upload_url"
    else
      key
    end
  end

  def cast_profile_value(value, key)
    return value if value.nil?

    if profile_url_fields.include?(key)
      value['url']
    elsif key === 'avatar'
      value['id']
    else
      value
    end
  end

  def profile_excluded_fields
    ['username', 'email', 'trust_level'].freeze
  end

  def allowed_profile_field?(field)
    allowed_profile_fields.include?(field) || user_field?(field)
  end

  def user_field?(field)
    field.to_s.include?(::User::USER_FIELD_PREFIX) &&
    ::UserField.exists?(field.split('_').last.to_i)
  end

  def allowed_profile_fields
    CustomWizard::Mapper.user_fields.select { |f| profile_excluded_fields.exclude?(f) } +
    profile_url_fields +
    ['avatar']
  end

  def update_avatar(upload_id)
    user.create_user_avatar unless user.user_avatar
    user.user_avatar.custom_upload_id = upload_id
    user.uploaded_avatar_id = upload_id
    user.save!
    user.user_avatar.save!
  end

  def encode_query_param(param)
    Addressable::URI.encode_component(param, Addressable::URI::CharacterClasses::UNRESERVED)
  end

  def log_success(message, detail = nil)
    @log.push("success: #{message} - #{detail}")
    @result.success = true
  end

  def log_error(message, detail = nil)
    @log.push("error: #{message} - #{detail}")
    @result.success = false
  end

  def log_info(message, detail = nil)
    @log.push("info: #{message} - #{detail}")
  end

  def save_log
    CustomWizard::Log.create(
      @wizard.id,
      action['type'],
      user.username,
      @log.join('; ')
    )
  end
end