This is how I do it (this is based on Hodgman posts):
At asset build time I assign each material a set of base "shader resources" (one for each pass the material should be drawn in).
The material chooses the right permutation of each "shader resource" based on what it needs and then, using the correct shader permutation, it finds out which cbuffers/textures slots to use using shader reflection.
A material will contain a bitset where each bit specifies a shader feature that the material needs and the Renderer will use that bitset to choose the right shader permutation every time the material is used. So you can change this list to change the material appearance by changing the bitset.
The number of lights or other external factors shouldn't modify the material, after all they're external factors.
So in my engine I have a class called Actor (basically a Model instance), that holds a pointer to a Model and vectors like position, rotation, etc.
The Actor also has a bitset, like the Materials, that will dynamically be updated based on the Actor/lights positions, so the Renderer will get the bitsets from Material/Actor/Geometry and other classes in order to choose the right shader permutation.