module Walruz 

  # The objective of this class is to start the invocation
  # of the authorization process, the methods of this class are used
  # internally by the actor and subject classes.
  #
  class Manager

    module AuthorizationQuery

      def can?(actor, action, subject)
        Walruz::Manager.check_action_authorization(actor, action, subject)[0]
      end

      def authorize!(actor, action, subject)
        result = Walruz::Manager.check_action_authorization(actor, action, subject)
        if result[0]
          result[1]
        else
          response_params = result[1]
          error_message   = response_params[:error_message] || "You are not authorized to access this content"
          raise NotAuthorized.new(actor, subject, action, error_message)
        end
      end

      def authorize(actor, action, subject)
        result = Walruz::Manager.check_action_authorization(actor, action, subject)
        result[0] ? result[1] : nil
      end

      def satisfies?(actor, policy_label, subject)
        result = Walruz::Manager.check_policy_authorization(actor, policy_label, subject)
        result[0]
      end

      def satisfies(actor, policy_label, subject)
        result = Walruz::Manager.check_policy_authorization(actor, policy_label, subject)
        result[0] ? result[1] : nil
      end

      def satisfies!(actor, policy_label, subject)
        result = Walruz::Manager.check_policy_authorization(actor, policy_label, subject)
        if result[0]
          result[1]
        else
          response_params = result[1]
          error_message = response_params[:error_message] || "You are not authorized to access this content"
          raise NotAuthorized.new(actor, subject, :access, error_message)
        end
      end

    end


    #
    # core method used on all the actor methods:
    # can?
    # authorize!
    # authorize
    # :private:
    def self.check_action_authorization(actor, action, subject)
      check_action_authorization_is_declared_on_subject(subject, action)
      action = if subject.class._walruz_policies.key?(:default)
                subject.class._walruz_policies.key?(action) ? action : :default
              else
                if subject.class._walruz_policies.key?(action) 
                  action
                else 
                  raise ActionNotFound.new(:subject_action, :subject => subject, 
                                                            :action => action)
                end
              end
      
      begin
        result = subject.class._walruz_policies[action].
                              return_policy.
                              new.
                              safe_authorized?(actor, subject)
      rescue PolicyHalted => e
        result = [false, {:error_message => e.message }]
      end

      result
    end

    def self.check_policy_authorization(actor, policy_label, subject)
      policy_clz = Walruz.fetch_policy(policy_label)
      
      begin
        result = policy_clz.return_policy.new.safe_authorized?(actor, subject)
      rescue PolicyHalted => e
        result = [false, { :error_message => e.message }]
      end

      result 
    end

    private

    def self.check_action_authorization_is_declared_on_subject(subject, action) 
      if subject.class._walruz_policies.nil?
        message =<<-BEGIN
You need to invoke `check_authorizations :#{action} => Policies::SomePolicy` on the #{subject.class.name} class
        BEGIN
        raise AuthorizationActionsNotDefined.new(message)
      end
    end

  end
end
