module Symphonia
  module ControllerExtensions

    extend ActiveSupport::Concern

    class Unauthorized < RuntimeError
    end

    included do

      before_action :current_user, :menu_item
      before_action :set_default_locale

      add_flash_types :notice
      add_flash_types :info
      add_flash_types :warning
      add_flash_types :error

      rescue_from ::ActiveRecord::RecordNotFound, with: :render_404
      rescue_from Unauthorized, CanCan::AccessDenied, with: :render_403

      helper_method :current_user, :back_url
    end

    def back_url
      url = params[:back_url].presence
      if url.nil? && (referer = request.env['HTTP_REFERER'].presence)
        url = CGI.unescape(referer.to_s)
      end
      url
    end

    # Redirects to the request referer if present, redirects to args or call block otherwise.
    def redirect_to_referer_or(*args, &block)
      redirect_to :back
    rescue ::ActionController::RedirectBackError
      if args.any?
        redirect_to(*args)
      elsif block_given?
        block.call
      else
        raise "#redirect_to_referer_or takes arguments or a block"
      end
    end

    # private

    def set_locale
      params_locale = params.fetch(:locale, nil).presence
      params_locale ||= session[:locale].presence
      params_locale ||= request.env['HTTP_ACCEPT_LANGUAGE'].to_s.split(',').collect { |l| l.scan(/^[a-z]{2}/) }.flatten
      client_lang = Array(params_locale).compact
      client_lang.unshift current_user.language if current_user.language
      @client_lang = client_lang.detect { |l| I18n.available_locales.include?(l.to_sym) }

      I18n.locale = (@client_lang || I18n.default_locale)
      session[:locale] = I18n.locale

      logger.debug "* Locale set to '#{I18n.locale}'"
    end

    def set_default_locale
      return unless (enforce_default = Symphonia.config.default_locale)

      I18n.locale = enforce_default
    end

    # protected

    def login_require(_format = nil)
      if current_user.nil? || !current_user.logged_in?
        respond_to do |format|
          format.html do
            store_location
            redirect_to symphonia.login_path, flash: { error: t(:text_login_require) }
          end
          format.json do
            render json: { errors: 'You must be logged in to access this endpoint' }, status: :unauthorized
          end
          format.any { head :unauthorized }
        end
        return false
      end
      true
    end

    alias require_login login_require
    alias require_user login_require

    def admin_require
      return unless login_require

      unless current_user.admin?
        render_403
        false
      end
    end

    alias require_admin admin_require

    def render_403
      respond_to do |format|
        format.html { render template: 'common/403', message: :notice_not_authorized, status: :forbidden }
        format.js do
          render plain: "alert('#{t :text_access_deny}')", message: :notice_not_authorized, status: :forbidden
        end
        format.any { head 403, message: :notice_not_authorized }
      end
    end

    def render_404
      respond_to do |format|
        format.html { render template: 'common/404', message: :notice_page_not_found, status: :not_found }
        format.any { head 404, message: :not_found }
      end
    end

    # Renders a 200 response for successful updates or deletions via the API
    def render_api_ok
      render_api_head :ok
    end

    # Renders a head API response
    def render_api_head(status)
      head status
    end

    def menu_item(item = nil)
      @menu_item = (item || controller_name)
    end

    def current_user_session
      return @current_user_session if defined?(@current_user_session)

      @current_user_session = UserSession.find
    end

    def current_user
      return (Symphonia::User.current ||= @current_user) if defined?(@current_user)

      @current_user = current_user_session&.user
      Symphonia::User.current = @current_user || Symphonia::User::Anonymous.new
    end

    def authorize
      return true if Symphonia::User.current.admin?
      raise Unauthorized if Symphonia::User.current.logged_in?

      login_require
    end

    def handle_unverified_request
      # raise an exception
      raise ActionController::InvalidAuthenticityToken
      # or destroy session, redirect
      current_user_session&.destroy
      redirect_to main_app.root_url
    end

    def store_location
      session[:return_to] = request.url
    end

    def redirect_back_or_default(default, options = {})
      options ||= {}
      redirect_to(params[:back_url] || default, options)
    end

  end
end