# frozen_string_literal: true
module Monarchy
  module ActsAsUser
    extend ActiveSupport::Concern

    module ClassMethods
      def acts_as_user
        has_many :members, class_name: "::#{Monarchy.member_class}", dependent: :destroy
        has_many :hierarchies, through: :members, class_name: 'Monarchy::Hierarchy'

        scope :accessible_for, (lambda do |user|
          where(id: Monarchy::Hierarchy.accessible_for(user)
                                       .joins(members: [:user]).select(:user_id)).union(where(id: user.id))
        end)

        include Monarchy::ActsAsUser::InstanceMethods
      end
    end

    module InstanceMethods
      def roles_for(resource, inheritence = true)
        return Monarchy.role_class.none unless resource.hierarchy
        accessible_roles_for(resource, inheritence)
      end

      def member_for(resource)
        resource.hierarchy.members.where(monarchy_members: { user_id: id }).first
      end

      def grant(role_name, resource)
        ActiveRecord::Base.transaction do
          grant_or_create_member(role_name, resource)
        end
      end

      def revoke_access(resource, hierarchy_ids = nil)
        hierarchy_ids ||= resource.hierarchy.self_and_descendant_ids
        members_for(hierarchy_ids).delete_all
      end

      def revoke_role(role_name, resource)
        revoking_role(role_name, resource)
      end

      def revoke_role!(role_name, resource)
        revoking_role(role_name, resource, true)
      end

      private

      def accessible_roles_for(resource, inheritnce)
        accessible_roles = if inheritnce
                             resource_and_inheritence_roles(resource)
                           else
                             resource_roles(resource).order('level desc')
                           end

        accessible_roles.present? ? accessible_roles : descendant_role(resource)
      end

      def resource_and_inheritence_roles(resource)
        hierarchy_ids = resource.hierarchy.ancestors.select(:id)
        Monarchy.role_class.where(id:
                      Monarchy.role_class.joins(:members).where('monarchy_members.user_id': id)
                      .where('monarchy_roles.inherited': 't', 'monarchy_members.hierarchy_id': hierarchy_ids)
                      .select(:inherited_role_id))
                .union(resource_roles(resource))
                .distinct
      end

      def resource_roles(resource)
        Monarchy.role_class.joins(:members)
                .where('monarchy_members.hierarchy_id': resource.hierarchy.id, 'monarchy_members.user_id': id)
                .distinct
      end

      def descendant_role(resource)
        descendant_ids = resource.hierarchy.descendant_ids
        children_access = members_for(descendant_ids).present?
        children_access ? Monarchy.role_class.where(id: default_role) : Monarchy.role_class.none
      end

      def revoking_role(role_name, resource, force = false)
        member = member_for(resource)
        member_roles = member.members_roles

        return revoke_access(resource) if last_role?(member_roles, role_name) && force
        member_roles.joins(:role).where(monarchy_roles: { name: role_name }).delete_all
      end

      def grant_or_create_member(role_name, resource)
        role = Monarchy.role_class.find_by(name: role_name)
        raise 'Role does not exist' unless role

        member = member_for(resource)
        if member
          Monarchy::MembersRole.create(member: member, role: role)
        else
          member = Monarchy.member_class.create(user: self, hierarchy: resource.hierarchy, roles: [role])
        end

        member
      end

      def members_for(hierarchy_ids)
        Monarchy.member_class.where(hierarchy_id: hierarchy_ids, user_id: id)
      end

      def default_role
        @default_role ||= Monarchy.role_class.find_by(name: Monarchy.configuration.default_role.name)
      end

      def last_role?(member_roles, role_name = nil)
        role_name ||= default_role.name
        member_roles.count == 1 && member_roles.first.role.name == role_name.to_s
      end

      def default_role?(role_name)
        default_role.name.to_s == role_name.to_s
      end
    end
  end
end

ActiveRecord::Base.send :include, Monarchy::ActsAsUser