A different class (or addressable function) for each buff is not overkill if the behavior of those buffs is different from one another. One thing would be having +10% or +20% buffs (that, of course, would be better represented as two objects of the same class), other would be implementing wildly different effects that would require custom code anyway. However, I believe it's better to have standard ways of customizing the game logic instead of letting each buff do whatever it pleases (and possibliy interfering with each other in unforeseen ways, disturbing game balance).
I'd suggest dividing each "attack cycle" into steps, where each step has a base value, an ordered list of modifications that can be applied to that value (maybe capped), and a final cap. Each modification has an identity transformation as default, and can be influenced by zero or more buffs/debuffs. The specifics of each modification would depend on the step applied. How the cycle is implemented is up to you (including the option of an event-driven architecture, as you've been discussing).
- calculate player agility (base + mods);
- calculate opponent evade (base + mods);
- do the difference (and apply mods) and determine if there was a hit;
- calculate player attack (base + mods);
- calculate opponent defense (base + mods);
- do the difference (and apply mods) and determine the base damage;
- calculate any parry/armor effects (mods on base damage) and apply damage;
- calculate any recoil effect (mods on base damage) and apply to the attacker.
The distinction between "Modifications" and "Buffs" I mentioned earlier has a purpose: if you want to have some buffs that are an upgraded version of another onedecisions about rules and balance can be implemented on the former, you'd normally not want bothso any changes on those don't need to be applied atreflect in changes to every class of the same timelatter. SoOTOH, if your Modification is something like "add X% to the stat Y"numbers and you have 3 activekinds of buffs where X = (10,20,30)are only limited by your imagination, the modification can pick the maxsince each of them instead of adding them. If the 3rd one expires but the other ones are still active, the next cycle will stillcan express their desired behavior without having to take the 2nd into account. That's a possibility, adding and capping are another one, any possible interaction between them and you could have the two strategies at the same timeothers - one Modification for cumulative effects, other for effects that pick(or even the maxexistence of others at all).