class MiqTaskController < ApplicationController
  before_action :check_privileges
  before_action :get_session_data
  after_action :cleanup_action
  after_action :set_session_data

  include Mixins::GenericSessionMixin
  include Mixins::BreadcrumbsMixin

  # Per page choices for task/jobs
  PPCHOICES2 = [
    [5, 5],
    [10, 10],
    [20, 20],
    [50, 50],
    [100, 100],
  ].freeze

  TASK_STATES = [
    [N_("Initializing"), "initializing"],
    [N_("Cancelling"), "Cancelling"],
    [N_("Aborting"), "Aborting"],
    [N_("Finished"), "Finished"],
    [N_("Snapshot Create"), "Snapshot_create"],
    [N_("Scanning"), "Scanning"],
    [N_("Snapshot Delete"), "Snapshot_delete"],
    [N_("Synchronizing"), "Synchronizing"],
    [N_("Deploy Smartproxy"), "Deploy_smartproxy"],
    [N_("Initialized"), "Initialized"],
    [N_("Queued"), "Queued"],
    [N_("Active"), "Active"]
  ].freeze

  def index
    assert_privileges('miq_task_all_ui', 'miq_task_my_ui', :any => false)
    @tabform = nil
    # TODO: remove :feature => "job_my_smartproxy" and  :feature => "job_all_smartproxy" from miq_user_roles.yml
    # above features assigned to the same roles as corresponding :feature => "miq_task_my_ui"
    # and :feature => "miq_task_all_ui"
    @tabform ||= "tasks_1" if role_allows?(:feature => "miq_task_my_ui")
    @tabform ||= "tasks_2" if role_allows?(:feature => "miq_task_all_ui")
    tab_vars(@tabform.split("_")[1])
    jobs
    render :action => "jobs"
  end

  def show
    assert_privileges('miq_task_all_ui', 'miq_task_my_ui', :any => false)
    @tabs = nil
    @in_a_form = true
    @miq_task = identify_record(params[:id], MiqTask)
    @miq_server = identify_record(@miq_task.miq_server_id, MiqServer)
    drop_breadcrumb(:name => @miq_task.name)
    render :action => "show"
  end

  # New tab was pressed
  def change_tab
    assert_privileges('miq_task_all_ui', 'miq_task_my_ui', :any => false)
    @tabform = "tasks_#{params[:'uib-tab']}"
    tab_vars(params[:'uib-tab'])
    jobs
    render :action => "jobs"
  end

  def tab_vars(current_tab)
    @path = '/miq_task/change_tab/'
    @current_tab = current_tab.to_s
  end

  def build_jobs_tab
    @pp_choices = PPCHOICES2 # Get special pp choices for jobs/tasks lists
    @settings[:perpage][:job_task] ||= 50 # Default to 50 per page until changed
    @tasks_options = HashWithIndifferentAccess.new if @tasks_options.blank?
    @tasks_options[:zones] = Zone.visible.includes(:miq_servers).collect { |z| z.name if z.miq_servers.present? }.compact
    tasks_set_default_options if @tasks_options[@tabform].blank?

    @tabs = []

    if role_allows?(:feature => "miq_task_my_ui")
      @tabs.push(["1", _("My Tasks")])
    end
    if role_allows?(:feature => "miq_task_all_ui")
      @tabs.push(["2", _("All Tasks")])
    end
  end

  # Show job list for the current user
  def jobs
    assert_privileges('miq_task_all_ui', 'miq_task_my_ui', :any => false)
    jobs_info
  end

  def list_jobs
    @lastaction = "jobs"
    @active_tab = @tabform.split("_").last

    case @tabform
    when "tasks_1" then @layout = "my_tasks"
    when "tasks_2", "alltasks_2" then @layout = "all_tasks"
    end
    tasks_set_default_options
    @view, @pages = get_view(MiqTask, :named_scope => tasks_scopes(@tasks_options[@tabform]))
    @user_names = MiqTask.distinct.pluck("userid").delete_if(&:blank?) if @active_tab.to_i == 2
  end

  # Cancel a single selected job
  def cancel_task
    assert_privileges("miq_task_canceljob")
    task_id = find_checked_items
    task = MiqTask.find_by(:id => task_id)
    message = if task.nil?
                task_id.empty? ? _("No task were selected to cancel") : _("Task %{id} not found") % {:id => task_id}
              elsif task.state.downcase == "finished"
                _("Finished Task cannot be cancelled")
              else
                begin
                  task.process_cancel
                rescue RuntimeError => e
                  e.message
                end
              end
    add_flash(message, :error)
    jobs
    @refresh_partial = "layouts/tasks"
  end

  # Delete selected tasks
  def delete_tasks
    assert_privileges("miq_task_delete")

    new_checked_items = find_checked_items.select { |item| params["select-row-#{item}"] == "on" }

    delete_tasks_from_table(new_checked_items, "Delete selected tasks")
    jobs
    @refresh_partial = "layouts/tasks"
  end

  # Delete all finished job(s)
  def delete_all_tasks
    assert_privileges("miq_task_deleteall")
    task_ids = []
    if session[:view].table.present?
      session[:view].table.data.each do |rec|
        task_ids.push(rec["id"])
      end
    end
    delete_tasks_from_table(task_ids, "Delete all finished tasks")
    jobs
    @refresh_partial = "layouts/tasks"
  end

  # Delete all task(s) older than selected task(s)
  def delete_older_tasks
    assert_privileges("miq_task_deleteolder")
    taskid = find_checked_items
    task = MiqTask.find_by(:id => taskid)
    if task
      MiqTask.delete_older(task.updated_on, tasks_condition(@tasks_options[@tabform], false))
      message = "Delete started for records older than #{task.updated_on}, conditions: #{@tasks_options[@tabform].inspect}"
      AuditEvent.success(:userid       => session[:userid],
                         :event        => "Delete older tasks",
                         :message      => message,
                         :target_class => "MiqTask")
      add_flash(_("Deleting all Tasks older than %{date} from the %{product} Database initiated") %
                 {:date => task.updated_on, :product => Vmdb::Appliance.PRODUCT_NAME})
    else
      add_flash(_("The selected task no longer exists, Delete all older Tasks was not completed"), :warning)
    end
    jobs
    @refresh_partial = "layouts/tasks"
  end

  TASK_X_BUTTON_ALLOWED_ACTIONS = {
    "miq_task_delete"      => :delete_tasks,
    "miq_task_deleteall"   => :delete_all_tasks,
    "miq_task_deleteolder" => :delete_older_tasks,
    "miq_task_canceljob"   => :cancel_task,
    "miq_task_reload"      => :reload_tasks,
  }.freeze

  # handle buttons pressed on the button bar
  def button
    @edit = session[:edit] # Restore @edit for adv search box

    generic_x_button(TASK_X_BUTTON_ALLOWED_ACTIONS)

    render :update do |page|
      page << javascript_prologue
      unless @refresh_partial.nil?
        page << "miqSetButtons(0, 'center_tb');"
        if @refresh_div == "flash_msg_div"
          page.replace(@refresh_div, :partial => @refresh_partial)
        else
          page.replace_html("main_div", :partial => @refresh_partial)
        end
      end
    end
  end

  private ############################

  def delete_tasks_from_table(task_ids, event_message)
    if task_ids.empty?
      add_flash(_("No task were selected to delete"), :error)
    else
      MiqTask.delete_by_id(task_ids)
      AuditEvent.success(:userid       => session[:userid],
                         :event        => event_message,
                         :message      => "Delete started for record ids: #{task_ids.inspect}",
                         :target_class => "MiqTask")
      add_flash(n_("Delete initiated for %{count} Task from the %{product} Database",
                   "Delete initiated for %{count} Tasks from the %{product} Database",
                   task_ids.length) % {:count => task_ids.length, :product => Vmdb::Appliance.PRODUCT_NAME})
    end
  end

  # Set all task options to default
  def tasks_set_default_options
    @tasks_options[@tabform] = {
      :ok           => true,
      :queued       => true,
      :error        => true,
      :warn         => true,
      :running      => true,
      :states       => TASK_STATES,
      :state_choice => "all",
      :time_period  => 0,
    }

    @tasks_options[@tabform][:zone]        = "<all>"
    @tasks_options[@tabform][:user_choice] = "all" if @tabform == 'tasks_2'
  end

  # Create a condition from the passed in options
  def tasks_condition(opts, use_times = true)
    cond = [[]]
    cond = add_to_condition(cond, *build_query_for_userid(opts))

    if !opts[:ok] && !opts[:queued] && !opts[:error] && !opts[:warn] && !opts[:running]
      query, values = build_query_for_status_none_selected
    else
      query, *values = build_query_for_status(opts)
    end
    cond = add_to_condition(cond, query, values)

    # Add time condition
    cond = add_to_condition(cond, *build_query_for_time_period(opts)) if use_times

    # Add zone condition
    cond = add_to_condition(cond, *build_query_for_zone(opts)) if opts[:zone] && opts[:zone] != "<all>"

    cond = add_to_condition(cond, *build_query_for_state(opts)) if opts[:state_choice] != "all"

    cond[0] = cond[0].join(" AND ")
    cond.flatten
  end

  def add_to_condition(cond, query, values)
    cond[0] << query unless query.nil?
    cond << values unless values.nil?
    cond
  end

  def build_query_for_userid(opts)
    sql = "miq_tasks.userid=?"
    if @tabform == 'tasks_1'
      [sql, session[:userid]]
    elsif opts[:user_choice] && opts[:user_choice] != "all"
      [sql, opts[:user_choice]]
    else
      [nil, nil]
    end
  end

  def build_query_for_status(opts)
    cond = [[]]
    %i[queued ok error warn running].each do |st|
      cond = add_to_condition(cond, *send("build_query_for_" + st.to_s)) if opts[st]
    end

    cond[0] = "(#{cond[0].join(" OR ")})"
    cond
  end

  def build_query_for_queued
    ["(miq_tasks.state=? OR miq_tasks.state=?)", %w[Waiting_to_start Queued]]
  end

  def build_query_for_ok
    build_query_for_status_completed("ok")
  end

  def build_query_for_error
    build_query_for_status_completed("error")
  end

  def build_query_for_warn
    build_query_for_status_completed("warn")
  end

  def build_query_for_status_completed(status)
    sql = "(miq_tasks.state=? AND miq_tasks.status=?)"
    [sql, ["Finished", status.try(:capitalize)]]
  end

  def build_query_for_running
    sql = "(miq_tasks.state!=? AND miq_tasks.state!=? AND miq_tasks.state!=?)"
    [sql, %w[Finished Waiting_to_start Queued]]
  end

  def build_query_for_status_none_selected
    sql = "(miq_tasks.status!=? AND miq_tasks.status!=? AND miq_tasks.status!=? AND "\
          "miq_tasks.state!=? AND miq_tasks.state!=? AND miq_tasks.state!=?)"
    [sql, %w[Ok Error Warn Finished Queued Waiting_to_start]]
  end

  def build_query_for_time_period(opts)
    t = format_timezone(opts[:time_period].to_i != 0 ? opts[:time_period].days.ago : Time.now, Time.zone, "raw")
    ["miq_tasks.updated_on>=? AND miq_tasks.updated_on<=?", [t.beginning_of_day, t.end_of_day]]
  end

  def build_query_for_zone(opts)
    ["miq_tasks.zone=?", opts[:zone]]
  end

  def build_query_for_state(opts)
    ["miq_tasks.state=?", opts[:state_choice]]
  end

  # Create a scope chain from the passed in options
  def tasks_scopes(opts, use_times = true)
    scope = []

    # Specify user scope
    if @tabform == "tasks_1"
      scope << [:with_userid, session[:userid]]
      @userid = session[:userid]
    elsif opts[:user_choice] && opts[:user_choice] != "all"
      scope << [:with_userid, opts[:user_choice]]
    end

    # Add status scope
    status = (opts.compact.symbolize_keys.keys & %i[ok queued error warn running])
    if status.any?
      status_scope_mapping = { :ok => :completed_ok, :warn => :completed_warn, :error => :completed_error } # remap reserved names
      status.map! { |s| status_scope_mapping[s] || s }
      scope << [:with_status_in, *status]
    else
      scope << :no_status_selected
    end

    # Add time scope
    if use_times
      t = format_timezone(opts[:time_period].to_i != 0 ? opts[:time_period].days.ago : Time.now, Time.zone, "raw")
      scope << [:with_updated_on_between, t.beginning_of_day, t.end_of_day]
    end

    # Add zone scope
    scope << [:with_zone, opts[:zone]] if opts[:zone] && opts[:zone] != "<all>"

    # Add state scope
    scope << [:with_state, opts[:state_choice]] if opts[:state_choice] != "all"

    scope
  end

  def reload_tasks
    assert_privileges("miq_task_reload")
    jobs
    @refresh_partial = "layouts/tasks"
  end

  def get_layout
    %w[my_tasks all_tasks].include?(session[:layout]) ? session[:layout] : "my_tasks"
  end

  def get_session_data
    super
    @layout = get_layout
    @tabform = session[:tabform] if session[:tabform]
    @tasks_options = session[:tasks_options] || ""
    @tabs ||= session[:tabs]
  end

  def set_session_data
    super
    session[:tabform]             = @tabform
    session[:layout]              = @layout
    session[:tasks_options]       = @tasks_options unless @tasks_options.nil?
    session[:tabs]                ||= @tabs
  end

  def jobs_info
    build_jobs_tab
    @title = _("Tasks for %{name}") % {:name => current_user.name}
    @lastaction = "jobs"

    @edit = {}
    @edit[:opts] = {}
    @edit[:opts] = copy_hash(@tasks_options[@tabform]) # Backup current settings

    list_jobs
    {:view => @view, :pages => @pages}
  end

  def breadcrumbs_options
    {
      :breadcrumbs => [
        {:url => controller_url, :title => _("Tasks")},
      ],
    }
  end

  menu_section :set
end
