class ApplicationController < ActionController::Base
  include Pundit
  include Authentication
  include Redirection
  include Measurement
  include Notification

  protect_from_forgery with: :exception

  # sanitizes all request parameters before taking additional action
  before_action do
    sanitize_params!
  end
  before_action :set_sentry_context

  after_action :verify_authorized
  around_action :track_around_action

  rescue_from Exception, with: :log_error
  rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized

  # @return Array<[TrackedEvent::Scaffold,Struct]>
  attr_accessor :tracked_event

  def redirect_back(default = root_path)
    return redirect_to request.referrer if request.referrer.present?
    redirect_to default
  end

  def user_info_for_tracking
    return {
      ldap: current_user.ldap_login,
      client_time: Time.now.to_i,
      user_agent: request.headers['User-Agent'],
      ip: request.remote_ip,
    }
  end

  # @param event <TrackedEvent::Scaffold,Struct>
  def track_action!(event = nil)
    @tracked_event =
      [@tracked_event, [event]] # the current event(s) [if any], and the incoming event to add
      .flatten                  # merge into a single layer array
      .compact                  # remove nil elements
    return                      # we don't need the new value back
  end

  def track_around_action
    start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)

    yield

    return unless defined?(@tracked_event)
    return if @tracked_event.nil? || @tracked_event.empty?

    # @param event <TrackedEvent::Scaffold,Struct>
    @tracked_event.each do |event|
      event_hash = filter_trackable_event_fields!(event_hash: event.to_h)

      event_hash[:elapsed_response_time_ms] = (Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time) * 1000.0

      SpadeClient.send_event(event_hash)
    end
  end

  #
  # filters the hash variant of @tracked_event
  #
  # @param [Hash] event_hash @tracked_event.to_h
  #
  # @return [Hash] a filtered event hash
  #
  def filter_trackable_event_fields!(event_hash:)
    event_hash.delete_if do |_, v|
      # remove nil params so that we don't send spade "nil" or "" for field values
      delete = v.nil?
      # `empty?` instead of `blank?` so that we retain fields if the user only put whitespace / tabs
      delete ||= v.respond_to?(:empty?) && v.empty?

      delete
    end
  end

  def trackable_request
    TrackedEvent.create(
      ldap: current_user.ldap_login,
      user_agent: request.headers['User-Agent'],
      client_ip: request.remote_ip,
      page_url: request.original_url,
      session_id: request.session.id
    )
  end

  protected

  def user_not_authorized
    flash[:error] = "You are not authorized to perform this action."
    redirect_to root_path
  end

  def set_sentry_context
    if current_user.present?
      Raven.user_context(id: current_user.ldap_login, email: current_user.email, ip_address: request.remote_ip)
    else
      Raven.user_context(ip_address: request.remote_ip)
    end
    Raven.extra_context(params: params.to_unsafe_h, url: request.url)
  end

  def log_error(err)
    Rollbar.error(err, rollbar_request_data, rollbar_person_data)

    Service::StructuredLogger.error(
      'admin_panel_error',
      name: err.class.name,
      message: err.message,
      request_method: request.request_method,
      url: request.original_url,
      backtrace: err.backtrace
    )

    raise err
  end

  # Sanitizes user input strings to match internal expectations.
  # E.g., trimming whitespace
  def sanitize_params!
    sanitize = proc do |val|

      if val.is_a? String
        val.strip
      elsif val.respond_to?(:transform_values!)
        val.transform_values!(&sanitize)
      else
        val
      end

    end

    params.transform_values!(&sanitize)
  end
end
