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

  include Mixins::GenericButtonMixin
  include Mixins::GenericListMixin
  include Mixins::GenericSessionMixin
  include Mixins::GenericFormMixin
  include Mixins::GenericShowMixin
  include Mixins::BreadcrumbsMixin

  # TODO: feature `chargeback_rates_show_list' needs to be renamed to chargeback_rate_show_list
  #       and then this method can be removed
  def index
    assert_privileges("chargeback_rates_show_list")

    super
  end

  # TODO: feature `chargeback_rates_show_list' needs to be renamed to chargeback_rate_show_list
  def show_list
    assert_privileges("chargeback_rates_show_list")

    super
  end

  # TODO: feature `chargeback_rates_show' needs to be renamed to chargeback_rate_show
  def show
    assert_privileges("chargeback_rates_show")

    super
  end

  # TODO: this will be unnecessary after
  #       `chargeback_rates_new`  gets renamed to `chargeback_rate_new`,
  #       `chargeback_rates_copy` gets renamed to `chargeback_rate_copy` and
  #       `chargeback_rates_edit` gets renamed to `chargeback_rate_edit`
  #
  EDIT_CHARGEBACK_RATE_FEATURES_WHITELIST = %w[
    chargeback_rates_new
    chargeback_rates_copy
    chargeback_rates_edit
  ]

  def assert_privileges_for_edit
    feature = case params[:button]
              when "cancel"
                params[:id] ? 'chargeback_rates_new' : 'chargeback_rates_edit'
              when "save", "add"
                params[:button] == "add" ? "chargeback_rates_new" : "chargeback_rates_edit"
              when "reset", nil
                EDIT_CHARGEBACK_RATE_FEATURES_WHITELIST.include?(params[:pressed]) ? params[:pressed] : "chargeback_rates_edit"
              end

    assert_privileges(feature)
  end

  def edit
    assert_privileges_for_edit
    @_params[:pressed] ||= 'chargeback_rates_edit'

    @_params[:pressed] ||= 'chargeback_rates_edit'

    case params[:button]
    when "cancel"
      rate_cancel
    when "save", "add"
      rate_save_add
    when "reset", nil # displaying edit from for actions: new, edit or copy
      @_params[:id] ||= find_checked_items[0]
      @redirect_id = params[:id] if params[:id]
      @refresh_partial = "edit"
      rate_reset_or_set
    end
  end

  def new
    rate_reset_or_set
  end

  def copy
    # TODO: this will be unnecessary after `chargeback_rates_copy` gets renamed to `chargeback_rate_copy`
    assert_privileges("chargeback_rates_copy")

    @_params[:id] ||= find_checked_items[0]
    @_params[:pressed] = "chargeback_rates_copy"
    rate_reset_or_set
  end

  # AJAX driven routine to check for changes in ANY field on the form
  def form_field_changed
    # TODO: rename feature `chargeback_rates_edit` to `chargeback_rate_edit`
    assert_privileges("chargeback_rates_edit")

    return unless load_edit("cbrate_edit__#{params[:id]}")
    cb_rate_get_form_vars
    render :update do |page|
      page << javascript_prologue
      changed = (@edit[:new] != @edit[:current])
      page.replace('chargeback_rate_edit_form', :partial => 'cb_rate_edit_table') if params[:rate_type]
      # Update the new column with the code of the currency selected by the user
      page.replace('chargeback_rate_currency', :partial => 'cb_rate_currency')
      page << javascript_for_miq_button_visibility(changed)
    end
  end

  # Delete all selected or single displayed action(s)
  def delete
    # TODO: this will be unnecessary after `chargeback_rates_delete` gets renamed to `chargeback_rate_delete`
    assert_privileges("chargeback_rates_delete")
    rates = []
    if !params[:id] # showing a list
      rates = find_checked_items
      if rates.empty?
        add_flash(_("No Chargeback Rates were selected for deletion"), :error)
      end
    else # showing 1 rate, delete it
      cb_rate = ChargebackRate.find_by(:id => params[:id])
      if cb_rate.nil?
        add_flash(_("Chargeback Rate no longer exists"), :error)
      else
        rates.push(params[:id])
      end
    end
    process_cb_rates(rates, 'destroy') if rates.present?
    flash_to_session
    javascript_redirect(:action => 'show_list')
  end

  # Add a new tier at the end
  def tier_add
    # TODO: rename feature `chargeback_rates_edit` to `chargeback_rate_edit`
    assert_privileges("chargeback_rates_edit")

    detail_index = params[:detail_index]
    ii = detail_index.to_i

    @edit  = session[:edit]
    detail = @edit[:new][:details][ii]

    @edit[:new][:num_tiers][ii] = detail[:chargeback_tiers].to_a.length if detail[:chargeback_tiers]
    @edit[:new][:num_tiers][ii] = 1 unless @edit[:new][:num_tiers][ii] || @edit[:new][:num_tiers][ii].zero?
    @edit[:new][:num_tiers][ii] += 1

    tier_index = @edit[:new][:num_tiers][ii] - 1
    tier_list = @edit[:new][:tiers][ii]
    tier_list[tier_index] = {}

    tier                 = tier_list[tier_index]
    tier[:start]         = tier_list[tier_index - 1][:finish]
    tier[:finish]        = Float::INFINITY
    tier[:fixed_rate]    = 0.0
    tier[:variable_rate] = 0.0

    code_currency = Currency.find_by(:id => detail[:currency]).code
    add_row(detail_index, tier_index - 1, code_currency)
  end

  # Remove the selected tier
  def tier_remove
    # TODO: rename feature `chargeback_rates_edit` to `chargeback_rate_edit`
    assert_privileges("chargeback_rates_edit")

    @edit = session[:edit]
    index = params[:index]
    detail_index, tier_to_remove_index = index.split("-")
    detail_index = detail_index.to_i
    @edit[:new][:num_tiers][detail_index] = @edit[:new][:num_tiers][detail_index] - 1

    # Delete tier record
    @edit[:new][:tiers][detail_index].delete_at(tier_to_remove_index.to_i)
    @changed = session[:changed] = true
    render :update do |page|
      page << javascript_prologue
      page.replace_html("chargeback_rate_edit_form", :partial => "cb_rate_edit_table")
      page << javascript_for_miq_button_visibility(@changed)
    end
  end

  def title
    @title = _("Chargeback Rates")
  end

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

  def rate_cancel
    if params[:id]
      flash_msg = _("Edit of Chargeback Rate \"%{name}\" was cancelled by the user") % {:name => session[:edit][:new][:description]}
    else
      flash_msg = _("Add of new Chargeback Rate was cancelled by the user")
    end
    @edit = session[:edit] = nil # clean out the saved info
    session[:changed] = false
    javascript_redirect(:action => @lastaction, :id => params[:id], :flash_msg => flash_msg)
  end

  def rate_save_add
    # TODO: rename chargeback_rates_edit and chargeback_rates_new to chargeback_rate_edit and chargeback_rate_new
    assert_privileges(params[:id] ? 'chargeback_rates_edit' : 'chargeback_rates_new')
    id = params[:button] == "save" ? params[:id] : "new"
    return unless load_edit("cbrate_edit__#{id}")

    @rate = params[:button] == "add" ? ChargebackRate.new : ChargebackRate.find(params[:id])
    if @edit[:new][:description].nil? || @edit[:new][:description] == ""
      render_flash(_("Description is required"), :error)
      return
    end
    @rate.description = @edit[:new][:description]
    @rate.rate_type   = @edit[:new][:rate_type] if @edit[:new][:rate_type]

    cb_rate_set_record_vars
    # Detect errors saving tiers
    tiers_valid = @rate_tiers.all? { |tiers| tiers.all?(&:valid?) }

    @rate.chargeback_rate_details.replace(@rate_details)
    @rate.chargeback_rate_details.each_with_index do |_detail, i|
      @rate_details[i].save_tiers(@rate_tiers[i])
    end

    tiers_valid &&= @rate_details.all? { |rate_detail| rate_detail.errors.messages.blank? }

    if tiers_valid && @rate.save
      if params[:button] == "add"
        AuditEvent.success(build_created_audit(@rate, @edit))
        flash_msg = _("Chargeback Rate \"%{name}\" was added") % {:name => @rate.description}
      else
        AuditEvent.success(build_saved_audit(@rate, @edit))
        flash_msg = _("Chargeback Rate \"%{name}\" was saved") % {:name => @rate.description}
      end
      @edit = session[:edit] = nil # clean out the saved info
      session[:changed] = @changed = false
      javascript_redirect(:action => @lastaction, :id => @rate[:id], :flash_msg => flash_msg)
    else
      @rate.errors.each do |error|
        add_flash("#{error.attribute.to_s.capitalize} #{error.message}", :error)
      end
      @rate_details.each do |detail|
        display_detail_errors(detail, detail.errors)
      end
      @rate_tiers.each_with_index do |tiers, detail_index|
        tiers.each do |tier|
          display_detail_errors(@rate_details[detail_index], tier.errors)
        end
      end
      @changed = session[:changed] = (@edit[:new] != @edit[:current])
      javascript_flash
    end
  end

  def rate_reset_or_set
    # TODO: rename chargeback_rates_edit to chargeback_rate_edit
    assert_privileges('chargeback_rates_edit') if params[:button] == "reset"
    @rate = new_rate_edit? ? ChargebackRate.new : ChargebackRate.find(params[:id])
    if params[:pressed] == 'chargeback_rates_edit' && @rate.default?
      add_flash(_("Default Chargeback Rate \"%{name}\" cannot be edited.") % {:name => @rate.description}, :error)
      flash_to_session
      redirect_to(:action => 'show_list')
    end

    @in_a_form = true
    session[:changed] = params[:pressed] == 'chargeback_rates_copy'
    cb_rate_set_form_vars
    javascript_redirect(:action        => 'edit',
                        :id            => params[:id],
                        :flash_msg     => _("All changes have been reset"),
                        :flash_warning => true) if params[:button] == "reset"
  end

  # Common Schedule button handler routines
  def process_cb_rates(rates, task)
    process_elements(rates, ChargebackRate, task)
  end

  # Set form variables for edit
  def cb_rate_set_form_vars
    @edit = {}
    @edit[:new] = HashWithIndifferentAccess.new
    @edit[:current] = HashWithIndifferentAccess.new
    @edit[:new][:tiers] = []
    @edit[:new][:num_tiers] = []
    @edit[:new][:description] = @rate.description
    @edit[:new][:rate_type] = @rate.rate_type || "Compute"

    set_rate_details

    if params[:pressed] == 'chargeback_rates_copy'
      @rate.id = nil
      @edit[:new][:description] = "copy of #{@rate.description}"
    end

    @edit[:rec_id] = @rate.id || nil
    @edit[:key] = "cbrate_edit__#{@rate.id || "new"}"
    @edit[:current] = copy_hash(@edit[:new])
    session[:edit] = @edit
  end

  def set_rate_details
    @edit[:new][:details] = []
    tiers = []
    @rate ||= ChargebackRate.new
    rate_details = @rate.chargeback_rate_details
    rate_details = ChargebackRateDetail.default_rate_details_for(@edit[:new][:rate_type]) if new_rate_edit?

    # Select the currency of the first chargeback_rate_detail. All the chargeback_rate_details have the same currency
    @edit[:new][:currency] = rate_details[0].detail_currency.id
    @edit[:new][:code_currency] = "#{rate_details[0].detail_currency.symbol} [#{rate_details[0].detail_currency.full_name}]"

    rate_details.each_with_index do |detail, detail_index|
      temp = detail.slice(*ChargebackRateDetail::FORM_ATTRIBUTES)
      temp[:report_column_name] = Dictionary.gettext(detail.chargeable_field.metric_key, :type => :column, :notfound => :titleize)
      temp[:group] = detail.chargeable_field.group
      temp[:per_time] ||= "hourly"

      temp[:currency] = detail.detail_currency.id

      if detail.chargeable_field.detail_measure.present?
        temp[:detail_measure] = {}
        temp[:detail_measure][:measures] = detail.chargeable_field.detail_measure.measures
        temp[:chargeback_rate_detail_measure_id] = detail.chargeable_field.detail_measure.id
      end

      temp[:id] = params[:pressed] == 'chargeback_rates_copy' ? nil : detail.id
      temp[:sub_metrics] = detail.sub_metrics
      temp[:sub_metric_human] = detail.sub_metric_human

      tiers[detail_index] ||= []

      detail.chargeback_tiers.each do |tier|
        new_tier = tier.slice(*ChargebackTier::FORM_ATTRIBUTES)
        new_tier[:id] = params[:pressed] == 'chargeback_rates_copy' ? nil : tier.id
        new_tier[:chargeback_rate_detail_id] = params[:pressed] == 'chargeback_rates_copy' ? nil : detail.id
        new_tier[:start] = new_tier[:start].to_f
        new_tier[:finish] = ChargebackTier.to_float(new_tier[:finish])
        tiers[detail_index].push(new_tier)
      end

      @edit[:new][:tiers][detail_index] = tiers[detail_index]
      @edit[:new][:num_tiers][detail_index] = tiers[detail_index].size
      @edit[:new][:details].push(temp)
    end
    @edit[:new][:per_time_types] = ChargebackRateDetail::PER_TIME_TYPES.map { |x, y| [x, _(y)] }.to_h
  end

  # Get variables from edit form
  def cb_rate_get_form_vars
    @edit[:new][:description] = params[:description] if params[:description]
    if params[:rate_type]
      @edit[:new][:rate_type] = params[:rate_type]
      set_rate_details
    end

    if params[:currency]
      @edit[:new][:currency] = params[:currency].to_i
      rate_detail_currency = Currency.find(params[:currency])
      @edit[:new][:code_currency] = "#{rate_detail_currency.symbol} [#{rate_detail_currency.full_name}]"
    end
    @edit[:new][:details].each_with_index do |detail, detail_index|
      %i[per_time per_unit sub_metric].each do |measure|
        key = "#{measure}_#{detail_index}".to_sym
        detail[measure] = params[key] if params[key]
      end
      # Add currencies to chargeback_controller.rb
      detail[:currency] = params[:currency].to_i if params[:currency]

      # Save tiers into @edit
      (0..@edit[:new][:num_tiers][detail_index].to_i - 1).each do |tier_index|
        tier = @edit[:new][:tiers][detail_index][tier_index] || {}
        %i[fixed_rate variable_rate start finish].each do |field|
          key = "#{field}_#{detail_index}_#{tier_index}".to_sym
          tier[field] = params[key] if params[key]
        end
      end
    end
  end

  def cb_rate_set_record_vars
    @rate_details = []
    @rate_tiers = []
    @edit[:new][:details].each_with_index do |detail, detail_index|
      rate_detail = detail[:id] ? ChargebackRateDetail.find(detail[:id]) : ChargebackRateDetail.new
      rate_detail.attributes = detail.slice(*ChargebackRateDetail::FORM_ATTRIBUTES)
      rate_detail.sub_metric = detail[:sub_metric] if rate_detail.sub_metric
      rate_detail_edit = @edit[:new][:details][detail_index]
      # C: Record the currency selected in the edit view, in my chargeback_rate_details table
      rate_detail.chargeback_rate_detail_currency_id = rate_detail_edit[:currency]
      rate_detail.chargeback_rate_detail_measure_id = rate_detail_edit[:chargeback_rate_detail_measure_id]
      rate_detail.chargeback_rate_id = @rate.id
      # Save tiers into @sb
      rate_tiers = []
      @edit[:new][:tiers][detail_index].each do |tier|
        rate_tier = tier[:id] ? ChargebackTier.find(tier[:id]) : ChargebackTier.new
        tier[:start] = Float::INFINITY if tier[:start].blank?
        tier[:finish] = Float::INFINITY if tier[:finish].blank?
        rate_tier.attributes = tier.slice(*ChargebackTier::FORM_ATTRIBUTES)
        rate_tier.chargeback_rate_detail_id = rate_detail.id
        rate_tiers.push(rate_tier)
      end
      @rate_tiers[detail_index] = rate_tiers
      @rate_details.push(rate_detail)
    end
  end

  def new_rate_edit?
    !params[:id].present? || params[:id] == 'new' || params[:pressed] == 'chargeback_rates_new'
  end

  def display_detail_errors(detail, errors)
    errors.each { |error| add_flash("'#{detail.chargeable_field.description}' #{error.attribute.to_s.humanize.downcase} #{error.message}", :error) }
  end

  def add_row(i, pos, code_currency)
    locals = {:code_currency => code_currency}
    render :update do |page|
      page << javascript_prologue
      # Update the first row to change the colspan
      page.replace("rate_detail_row_#{i}_0",
                   :partial => "tier_first_row",
                   :locals  => locals)
      # Insert the new tier after the last one
      page.insert_html(:after,
                       "rate_detail_row_#{i}_#{pos}",
                       :partial => "tier_row",
                       :locals  => locals)
      page << javascript_for_miq_button_visibility(true)
    end
  end

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

  toolbar :chargeback_rate, :chargeback_rates
  menu_section :chargeback
end
