"""Tests for the EffectColorStripStream renderer registry.

Lock in the per-class decorator-built _RENDERERS dict so:

* every render method is registered exactly once,
* the registry is built at class-creation (not per-frame), and
* duplicate registration is caught early.
"""

from __future__ import annotations

import pytest

from ledgrab.core.processing.effect_stream import (
    EffectColorStripStream,
    _collect_effect_renderers,
    _effect_renderer,
)

EXPECTED_EFFECTS = frozenset(
    {
        "fire",
        "meteor",
        "plasma",
        "noise",
        "aurora",
        "rain",
        "comet",
        "bouncing_ball",
        "fireworks",
        "sparkle_rain",
        "lava_lamp",
        "wave_interference",
    }
)


def test_registry_contains_every_known_effect():
    assert set(EffectColorStripStream._RENDERERS.keys()) == EXPECTED_EFFECTS


def test_each_registered_method_is_callable_and_unbound():
    for name, method in EffectColorStripStream._RENDERERS.items():
        assert callable(method), f"renderer for {name!r} is not callable"
        # Unbound: bind requires self argument, name should start with _render_
        assert method.__name__.startswith(
            "_render_"
        ), f"renderer for {name!r} has unexpected name {method.__name__}"


def test_registry_is_one_per_class_not_per_instance():
    """The _RENDERERS dict lives on the CLASS, not on each instance."""
    assert isinstance(EffectColorStripStream._RENDERERS, dict)
    # Should be the exact same object across attribute access
    assert EffectColorStripStream._RENDERERS is EffectColorStripStream._RENDERERS


def test_duplicate_registration_is_rejected():
    """A class that registers two methods under the same name fails at decoration."""

    with pytest.raises(RuntimeError, match="Duplicate"):

        @_collect_effect_renderers
        class _Bad:  # noqa: D401
            @_effect_renderer("fire")
            def a(self):
                pass

            @_effect_renderer("fire")
            def b(self):
                pass


def test_renderer_decorator_stamps_name_attribute():
    """The marker attribute is what the class decorator reads."""

    @_effect_renderer("my_effect")
    def fn(self):
        pass

    assert fn._effect_renderer_name == "my_effect"
