module Symphonia
  module ControllerExtensions

    extend ActiveSupport::Concern


    included do
      class Unauthorized < Exception;
      end

      before_action :current_user, :menu_item
      before_action :set_default_locale

      # force_ssl if: -> {Rails.env.production? && User.current.ssl?}

      add_flash_types :warning
      add_flash_types :error

      rescue_from ::ActiveRecord::RecordNotFound do
        render_404
      end

      rescue_from Unauthorized do
        render_403
      end

      helper_method :current_user

    end

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

    # def redirect_back_or_default(default, options={})
    #   back_url = params[:back_url].to_s
    #   if back_url.present?
    #     begin
    #       uri = URI.parse(back_url)
    #       # do not redirect user to another host or to the login or register page
    #       if (uri.relative? || (uri.host == request.host)) && !uri.path.match(%r{/(login|account/register)})
    #         redirect_to(back_url)
    #         return
    #       end
    #     rescue URI::InvalidURIError
    #       logger.warn("Could not redirect to invalid URL #{back_url}")
    #       # redirect to default
    #     end
    #   elsif options[:referer]
    #     redirect_to_referer_or default
    #     return
    #   end
    #   redirect_to default, options
    #   false
    # 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
      client_lang = params.fetch(:locale, nil).presence || session[:locale].presence || request.env['HTTP_ACCEPT_LANGUAGE'].to_s.split(',').collect { |l| l.scan(/^[a-z]{2}/) }.flatten
      @client_lang = Array(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
      if (enforce_default = Symphonia.config.default_locale)
        I18n.locale = enforce_default
      end
    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 { render json: { errors: 'You must be logged in to access this endpoint' }, status: 401 }
          format.any { head 401 }
        end
        return false
      end
      true
    end

    alias_method :require_login, :login_require
    alias_method :require_user, :login_require

    def admin_require
      return unless login_require

      unless current_user.admin?
        render_403
        return false
      end
    end

    alias_method :require_admin, :admin_require

    def render_403
      respond_to do |format|
        format.html { render template: 'common/403', message: :notice_not_authorized, status: 403 }
        format.js { render plain: "alert('#{t :text_access_deny}')", message: :notice_not_authorized, status: 403 }
        format.json { 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: 404 }
        format.json { head 404, message: :not_found }
      end
    end

    # Renders a 200 response for successful updates or deletions via the API
    def render_api_ok
      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 && current_user_session.user
      Symphonia::User.current = @current_user || Symphonia::User::Anonymous.new
    end

    def authorize
      if Symphonia::User.current.authorize?(controller_name, action_name)
        return true
      else
        if Symphonia::User.current.logged_in?
          raise Unauthorized
        else
          respond_to do |format|
            format.html do
              return redirect_to(symphonia.login_path(back_url: request.path), error: t(:text_error_login_required))
            end
            format.any { return head 401 }
          end
        end
      end
      raise Unauthorized
    end

    def handle_unverified_request
      # raise an exception
      fail ActionController::InvalidAuthenticityToken
      # or destroy session, redirect
      if current_user_session
        current_user_session.destroy
      end
      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