module BeefCake
  class Role < BeefCake::Base
    attributes :id, :name, :memberships, :permissions, :attached_permissions
    validates :name, presence: true

    def self.all(_options = {})
      paginate twirp_data(
        client.get_roles(Twitch::Fulton::Beefcake::GetRolesRequest.new)
      )
        .roles
        .map { |role| from_attributes(id: role.id, name: role.name) }
        .sort_by(&:name)
    end

    def self.find(id, _options = {})
      data = twirp_data(
        client.get_role(
          Twitch::Fulton::Beefcake::GetRoleRequest.new(id: id)
        )
      )

      role = from_attributes(
        id: data.id,
        name: data.name,
        memberships: data.user_memberships.map do |m|
          Membership.from_attributes(
            id: Membership.composite_key(role_id: data.id, uid: m.user_id),
            uid: m.user_id,
            role_id: data.id,
            expires_at: m.expiration.nil? ? nil : Time.at(m.expiration.seconds).to_date
          )
        end,
        permissions: data.permissions
          .select { |p| p.permission.legacy }
          .map { |p| p.permission.legacy }
          .map { |p| Permission.from_attributes(id: p.id, canonical_name: p.id, name: p.name) }
      )
      role.attached_permissions = data
                                  .permissions
                                  .select { |p| p.permission.legacy }
                                  .map do |p|
          AttachedPermission.from_attributes(
            id: p.id,
            role: role,
            permission: Permission.from_attributes(
              id: p.permission.legacy.id,
              canonical_name: p.permission.legacy.id,
              name: p.permission.legacy.name
            ),
            scope: p.scope
          )
      end
      role
    end

    def user_uids
      memberships
        .map(&:uid)
        .uniq
        .sort
    end

    def save
      return false unless valid?

      if persisted?
        req = Twitch::Fulton::Beefcake::UpdateRoleRequest.new(
          id: id,
          name: Google::Protobuf::StringValue.new(value: name)
        )
        req.legacy_permission_ids = Twitch::Fulton::Beefcake::StringArrayValue.new
        req.legacy_permission_ids.value.replace(
          permissions
            .reject { |p| p.id.empty? }
            .map(&:id)
        )

        handle_error(client.update_role(req))
      else
        data = twirp_data(client.create_role(
                            Twitch::Fulton::Beefcake::CreateRoleRequest.new(name: name)
        ))
        self.id = data.id
        permissions
          .reject { |p| p.id.empty? }
          .each { |p| add_permission(p.id) }
      end
      true
    end

    def destroy
      handle_error(
        client.delete_role(Twitch::Fulton::Beefcake::DeleteRoleRequest.new(
                             id: id
        ))
      )
      true
    end

    def add_permission(permission_id)
      handle_error(client.add_legacy_permission_to_role(
                     Twitch::Fulton::Beefcake::AddLegacyPermissionToRoleRequest.new(
                       role_id: id,
                       legacy_permission_id: permission_id
                     )
      ))
    end

    def bulk_add_realm_users(realm, user_ids)
      users = validated_users(realm, user_ids)
      return unless errors.empty?

      users.each do |user|
        handle_error(client.add_user_to_role(
                       Twitch::Fulton::Beefcake::AddUserToRoleRequest.new(
                         role_id: id,
                         user_id: user.uri
                       )
        ))
      end
      true
    end

    def bulk_remove_realm_users(realm, user_ids)
      users = validated_users(realm, user_ids)
      return unless errors.empty?

      users.each { |user| remove_user(user.uri) }
      true
    end

    def remove_user(user_id)
      handle_error(
        client.remove_user_from_role(
          Twitch::Fulton::Beefcake::RemoveUserFromRoleRequest.new(
            role_id: id,
            user_id: user_id
          )
        )
      )
      true
    end

    def as_json(options = {})
      role_hash = super(options)
      role_hash[:permission_ids] = permission_ids
      {role: role_hash}
    end

    def permissions
      @permissions.to_a
    end

    def permission_ids
      permissions.map &:id
    end

    def permission_ids=(ids = [])
      self.permissions = ids.map do |permission_id|
        Permission.new(id: permission_id)
      end
    end

    protected

    def validated_users(realm, user_ids)
      begin
        users = user_ids.map { |x| RealmUser.from_realm_and_id(realm, x) }
      rescue Realm::InvalidRealm => e
        errors.add(:user_ids, e)
        return
      end

      users.each do |user|
        next if user.valid? || !errors.empty?
        errors.add(
          :user_ids,
          user.errors.full_messages.to_sentence
        )
      end

      users
    end
  end
end
