class EmsInfraController < ApplicationController
  include Mixins::GenericListMixin
  include Mixins::GenericShowMixin
  include Mixins::EmsCommon # common methods for EmsInfra/Cloud controllers
  include Mixins::DashboardViewMixin
  include Mixins::BreadcrumbsMixin

  before_action :check_privileges
  before_action :get_session_data
  after_action :cleanup_action
  after_action :set_session_data

  # when methods are evaluated from this constant and return true that means: column is displayed
  DISPLAY_GTL_METHODS = [
    :user_super_admin?
  ].freeze

  def user_super_admin?
    current_user.super_admin_user?
  end

  def self.model
    ManageIQ::Providers::InfraManager
  end

  def self.table_name
    @table_name ||= "ems_infra"
  end

  def show
    @breadcrumbs =  [{:name => _('Infrastructure Providers'), :url => '/ems_infra/show_list'}]
    super
  end

  def show_list
    @showtype = nil
    super
  end

  def index
    redirect_to(:action => 'show_list')
  end

  def scaling
    assert_privileges("ems_infra_scale")

    # Hiding the toolbars
    @in_a_form = true

    redirect_to(ems_infra_path(params[:id])) if params[:cancel]

    drop_breadcrumb(:name => _("Scale Infrastructure Provider"), :url => "/ems_infra/scaling")
    @infra = get_infra_provider(params[:id])
    # TODO: Currently assumes there is a single stack per infrastructure provider. This should
    # be improved to support multiple stacks.
    @stack = @infra.direct_orchestration_stacks.first
    if @stack.nil?
      add_flash(_('Orchestration stack could not be found.'), :error)
      return
    end

    @count_parameters = @stack.parameters.select { |x| x.name.include?('::count') || x.name.include?('Count') }

    return unless params[:scale]

    scale_parameters = params.select { |k, _v| k.include?('::count') || k.include?('Count') }.to_unsafe_h
    assigned_hosts = scale_parameters.values.sum(&:to_i)
    infra = get_infra_provider(params[:id])
    if assigned_hosts > infra.hosts.count
      # Validate number of selected hosts is not more than available
      add_flash(_("Assigning %{hosts} but only have %{hosts_count} hosts available.") % {
        :hosts => assigned_hosts, :hosts_count => infra.hosts.count.to_s
      }, :error)
    else
      scale_parameters_formatted = {}
      return_message = _("Scaling")
      if @count_parameters.any? { |p| scale_parameters[p.name].present? && scale_parameters[p.name].to_s < p.value.to_s }
        add_flash(_("Scaling down is not supported. New value for %{name} %{new_value} is lower than current value %{current_value}.") % {:name => p.name, :new_value => scale_parameters[p.name], :current_value => p.value}, :error)
        return
      end
      @count_parameters.each do |p|
        next if scale_parameters[p.name].nil? || scale_parameters[p.name] == p.value

        return_message += _(" %{name} from %{value} to %{parameters} ") % {:name => p.name, :value => p.value, :parameters => scale_parameters[p.name]}
        scale_parameters_formatted[p.name] = scale_parameters[p.name]
      end

      update_stack_up(@stack, scale_parameters_formatted, params[:id], return_message)
    end
  end

  def scaledown
    assert_privileges("ems_infra_scale")
    redirect_to(ems_infra_path(params[:id])) if params[:cancel]

    # Hiding the toolbars
    @in_a_form = true

    drop_breadcrumb(:name => _("Scale Infrastructure Provider Down"), :url => "/ems_infra/scaling")
    @infra = get_infra_provider(params[:id])
    # TODO: Currently assumes there is a single stack per infrastructure provider. This should
    # be improved to support multiple stacks.
    @stack = @infra.direct_orchestration_stacks.first
    if @stack.nil?
      add_flash(_('Orchestration stack could not be found.'), :error)
      return
    end

    @compute_hosts = @infra.hosts.select { |host| host.name.include?('Compute') }

    return unless params[:scaledown]

    host_ids = params[:host_ids]
    if host_ids.nil?
      add_flash(_("No compute hosts were selected for scale down."), :error)
    else
      hosts = get_hosts_to_scaledown_from_ids(host_ids)

      # verify selected nodes can be removed
      has_invalid_nodes, error_return_message = verify_hosts_for_scaledown(hosts)
      if has_invalid_nodes
        add_flash(error_return_message, :error)
        return
      end

      # figure out scaledown parameters and update stack
      stack_parameters = get_scaledown_parameters(hosts, @infra, @compute_hosts)

      update_stack_down(@stack, stack_parameters, params[:id], hosts)
    end
  end

  def register_nodes
    assert_privileges("host_register_nodes")
    redirect_to(ems_infra_path(params[:id], :display => "hosts")) if params[:cancel]

    # Hiding the toolbars
    @in_a_form = true
    drop_breadcrumb(:name => _("Register Nodes"), :url => "/ems_infra/register_nodes")

    @infra = ManageIQ::Providers::Openstack::InfraManager.find(params[:id])

    if params[:register]
      if params[:nodes_json].nil? || params[:nodes_json][:file].nil?
        add_flash(_('Please select a JSON file containing the nodes you would like to register.'), :error)
        return
      end

      begin
        uploaded_file = params[:nodes_json][:file]
        nodes_json = parse_json(uploaded_file)
        if nodes_json.nil?
          add_flash(_("JSON file format is incorrect, missing 'nodes'."), :error)
        end
      rescue StandardError => ex
        add_flash(_("Cannot parse JSON file: %{message}") % {:message => ex}, :error)
      end

      if nodes_json
        begin
          @infra.workflow_service
        rescue StandardError => ex
          add_flash(_("Cannot connect to workflow service: %{message}") % {:message => ex}, :error)
          return
        end
        begin
          state, response = @infra.register_and_configure_nodes(nodes_json)
        rescue StandardError => ex
          add_flash(_("Error executing register and configure workflows: %{message}") % {:message => ex}, :error)
          return
        end
        if state == "SUCCESS"
          redirect_to(ems_infra_path(params[:id],
                                     :display   => "hosts",
                                     :flash_msg => _("Nodes were added successfully. Refresh queued.")))
        else
          add_flash(_("Unable to add nodes: %{error}") % {:error => response}, :error)
        end
      end
    end
  end

  def open_admin_ui
    assert_privileges("ems_infra_admin_ui")
    ems = identify_record(params[:id], ManageIQ::Providers::InfraManager)

    if ems.supports?(:admin_ui)
      task_id = ems.queue_generate_admin_ui_url
      initiate_wait_for_task(:task_id => task_id, :action => "open_admin_ui_done")
    else
      javascript_flash(:text     => _("Admin UI feature is not supported for this infrastructure provider"),
                       :severity => :error)
    end
  end

  def open_admin_ui_done
    assert_privileges("ems_infra_admin_ui")
    task = MiqTask.find(params[:task_id])

    if task.results_ready? && task.task_results.kind_of?(String)
      javascript_open_window(task.task_results)
    else
      message = MiqTask.status_ok?(task.status) ? _("The URL is blank or not a String") : task.message
      javascript_flash(:text     => _("Infrastructure provider failed to generate Admin UI URL: %{message}") % {:message => message},
                       :severity => :error)
    end
  end

  def restful?
    true
  end

  def launch_console
    open_console('ems_native_console')
  end

  def download_data
    assert_privileges('ems_infra_show_list')
    super
  end

  def download_summary_pdf
    assert_privileges('ems_infra_show')
    super
  end

  private

  def record_class
    case params[:pressed]
    when /^ems_cluster/
      EmsCluster
    when /^orchestration_stack/
      OrchestrationStack
    when /^storage/
      Storage
    else
      VmOrTemplate
    end
  end

  ############################
  # Special EmsInfra link builder for restful routes
  def show_link(ems, options = {})
    ems_infra_path(ems.id, options)
  end

  def update_stack_up(stack, stack_parameters, provider_id, return_message)
    if stack_parameters_changed?(stack_parameters)
      begin
        stack.scale_up_queue(session[:userid], stack_parameters)
        add_flash(return_message)
        flash_to_session
        redirect_to(ems_infra_path(provider_id))
      rescue StandardError => ex
        add_flash(_("Unable to initiate scale up: %{message}") % {:message => ex}, :error)
      end
    end
  end

  def update_stack_down(stack, stack_parameters, provider_id, hosts)
    return_message = _(" Scaling down to %{a} compute nodes") % {:a => stack_parameters['ComputeCount']}
    if stack_parameters_changed?(stack_parameters)
      begin
        stack.scale_down_queue(session[:userid], stack_parameters, hosts)
        add_flash(return_message)
        flash_to_session
        redirect_to(ems_infra_path(provider_id))
      rescue StandardError => ex
        add_flash(_("Unable to initiate scale down: %{message}") % {:message => ex}, :error)
      end
    end
  end

  def stack_parameters_changed?(stack_parameters)
    if stack_parameters.empty?
      # No values were changed
      add_flash(_("A value must be changed or provider stack will not be updated."), :error)
      return false
    end
    true
  end

  def verify_hosts_for_scaledown(hosts)
    has_invalid_nodes = false
    error_return_message = _("Not all hosts can be removed from the deployment.")

    hosts.each do |host|
      unless host.maintenance
        has_invalid_nodes = true
        error_return_message += _(" %{host_uid_ems} needs to be in maintenance mode before it can be removed ") %
                                {:host_uid_ems => host.uid_ems}
      end
      if host.number_of(:vms).positive?
        has_invalid_nodes = true
        error_return_message += _(" %{host_uid_ems} needs to be evacuated before it can be removed ") %
                                {:host_uid_ems => host.uid_ems}
      end
      unless host.name.include?('Compute')
        has_invalid_nodes = true
        error_return_message += _(" %{host_uid_ems} is not a compute node ") % {:host_uid_ems => host.uid_ems}
      end
    end

    return has_invalid_nodes, error_return_message
  end

  def get_scaledown_parameters(hosts, provider, compute_hosts)
    resources_by_physical_resource_id = {}
    provider.orchestration_stacks.each do |s|
      s.resources.each do |r|
        resources_by_physical_resource_id[r.physical_resource] = r
      end
    end

    host_physical_resource_ids = hosts.map(&:ems_ref)
    parent_resource_names = []
    host_physical_resource_ids.each do |pr_id|
      host_resource = resources_by_physical_resource_id[pr_id]
      host_stack = find_record_with_rbac(OrchestrationStack, host_resource.stack_id)
      parent_host_resource = resources_by_physical_resource_id[host_stack.ems_ref]
      parent_resource_names << parent_host_resource.logical_resource
    end

    stack_parameters = {}
    stack_parameters['ComputeCount'] = compute_hosts.length - hosts.length
    stack_parameters['ComputeRemovalPolicies'] = [{:resource_list => parent_resource_names}]
    stack_parameters
  end

  def parse_json(uploaded_file)
    JSON.parse(uploaded_file.read)["nodes"]
  end

  def get_infra_provider(provider_id)
    ManageIQ::Providers::Openstack::InfraManager.find(provider_id)
  end

  def get_hosts_to_scaledown_from_ids(host_ids)
    host_ids.map { |host_id| find_record_with_rbac(Host, host_id) }
  end

  def breadcrumbs_options
    {
      :breadcrumbs => [
        {:title => _("Compute")},
        {:title => _("Infrastructure")},
        {:title => _("Providers"), :url => controller_url},
      ],
      :record_info => @ems,
    }.compact
  end

  menu_section :inf
  feature_for_actions "#{controller_name}_show_list", *ADV_SEARCH_ACTIONS
  has_custom_buttons
end
