"""
Tests for the new unified pipeline architecture.
"""

from unittest.mock import MagicMock

import pytest

from engine.pipeline import (
    Pipeline,
    PipelineConfig,
    PipelineContext,
    Stage,
    StageRegistry,
    create_default_pipeline,
    discover_stages,
)
from engine.pipeline.core import DataType, StageError


class TestStageRegistry:
    """Tests for StageRegistry."""

    def setup_method(self):
        """Reset registry before each test."""
        StageRegistry._discovered = False
        StageRegistry._categories.clear()
        StageRegistry._instances.clear()

    def test_discover_stages_registers_sources(self):
        """discover_stages registers source stages."""
        # Register Mainline plugins first
        try:
            from engine.plugins import register_stages

            register_stages(StageRegistry)
        except ImportError:
            pass

        discover_stages()

        sources = StageRegistry.list("source")
        assert "HeadlinesDataSource" in sources
        assert "PoetryDataSource" in sources
        assert "PipelineIntrospectionSource" in sources

    def test_discover_stages_registers_displays(self):
        """discover_stages registers display stages."""
        discover_stages()

        displays = StageRegistry.list("display")
        assert "terminal" in displays
        assert "pygame" in displays
        assert "websocket" in displays
        assert "null" in displays

    def test_create_source_stage(self):
        """StageRegistry.create creates source stages."""
        # Register Mainline plugins first
        try:
            from engine.plugins import register_stages

            register_stages(StageRegistry)
        except ImportError:
            pass

        discover_stages()

        source = StageRegistry.create("source", "HeadlinesDataSource")
        assert source is not None
        assert source.name == "headlines"

    def test_create_display_stage(self):
        """StageRegistry.create creates display stages."""
        discover_stages()

        display = StageRegistry.create("display", "terminal")
        assert display is not None
        assert hasattr(display, "_display")

    def test_create_display_stage_pygame(self):
        """StageRegistry.create creates pygame display stage."""
        discover_stages()

        display = StageRegistry.create("display", "pygame")
        assert display is not None


class TestPipeline:
    """Tests for Pipeline class."""

    def setup_method(self):
        """Reset registry before each test."""
        StageRegistry._discovered = False
        StageRegistry._categories.clear()
        StageRegistry._instances.clear()
        discover_stages()

    def test_create_pipeline(self):
        """Pipeline can be created with config."""
        config = PipelineConfig(source="headlines", display="terminal")
        pipeline = Pipeline(config=config)

        assert pipeline.config is not None
        assert pipeline.config.source == "headlines"
        assert pipeline.config.display == "terminal"

    def test_add_stage(self):
        """Pipeline.add_stage adds a stage."""
        pipeline = Pipeline()
        mock_stage = MagicMock(spec=Stage)
        mock_stage.name = "test_stage"
        mock_stage.category = "test"

        pipeline.add_stage("test", mock_stage)

        assert "test" in pipeline.stages

    def test_build_resolves_dependencies(self):
        """Pipeline.build resolves execution order."""
        from engine.pipeline.core import DataType

        pipeline = Pipeline()
        mock_source = MagicMock(spec=Stage)
        mock_source.name = "source"
        mock_source.category = "source"
        mock_source.stage_type = "source"
        mock_source.render_order = 0
        mock_source.is_overlay = False
        mock_source.inlet_types = {DataType.NONE}
        mock_source.outlet_types = {DataType.SOURCE_ITEMS}
        mock_source.dependencies = set()
        mock_source.capabilities = {"source"}

        mock_display = MagicMock(spec=Stage)
        mock_display.name = "display"
        mock_display.category = "display"
        mock_display.stage_type = "display"
        mock_display.render_order = 0
        mock_display.is_overlay = False
        mock_display.inlet_types = {DataType.ANY}  # Accept any type
        mock_display.outlet_types = {DataType.NONE}
        mock_display.dependencies = {"source"}
        mock_display.capabilities = {"display"}

        pipeline.add_stage("source", mock_source)
        pipeline.add_stage("display", mock_display)
        pipeline.build(auto_inject=False)

        assert pipeline._initialized is True
        assert "source" in pipeline.execution_order
        assert "display" in pipeline.execution_order

    def test_execute_runs_stages(self):
        """Pipeline.execute runs all stages in order."""
        from engine.pipeline.core import DataType

        pipeline = Pipeline()

        call_order = []

        mock_source = MagicMock(spec=Stage)
        mock_source.name = "source"
        mock_source.category = "source"
        mock_source.stage_type = "source"
        mock_source.render_order = 0
        mock_source.is_overlay = False
        mock_source.inlet_types = {DataType.NONE}
        mock_source.outlet_types = {DataType.SOURCE_ITEMS}
        mock_source.dependencies = set()
        mock_source.capabilities = {"source"}
        mock_source.process = lambda data, ctx: call_order.append("source") or "data"

        mock_effect = MagicMock(spec=Stage)
        mock_effect.name = "effect"
        mock_effect.category = "effect"
        mock_effect.stage_type = "effect"
        mock_effect.render_order = 0
        mock_effect.is_overlay = False
        mock_effect.inlet_types = {DataType.SOURCE_ITEMS}
        mock_effect.outlet_types = {DataType.TEXT_BUFFER}
        mock_effect.dependencies = {"source"}
        mock_effect.capabilities = {"effect"}
        mock_effect.process = lambda data, ctx: call_order.append("effect") or data

        mock_display = MagicMock(spec=Stage)
        mock_display.name = "display"
        mock_display.category = "display"
        mock_display.stage_type = "display"
        mock_display.render_order = 0
        mock_display.is_overlay = False
        mock_display.inlet_types = {DataType.TEXT_BUFFER}
        mock_display.outlet_types = {DataType.NONE}
        mock_display.dependencies = {"effect"}
        mock_display.capabilities = {"display"}
        mock_display.process = lambda data, ctx: call_order.append("display") or data

        pipeline.add_stage("source", mock_source)
        pipeline.add_stage("effect", mock_effect)
        pipeline.add_stage("display", mock_display)
        pipeline.build(auto_inject=False)

        result = pipeline.execute(None)

        assert result.success is True
        assert call_order == ["source", "effect", "display"]

    def test_execute_handles_stage_failure(self):
        """Pipeline.execute handles stage failures."""
        pipeline = Pipeline()

        mock_source = MagicMock(spec=Stage)
        mock_source.name = "source"
        mock_source.category = "source"
        mock_source.stage_type = "source"
        mock_source.render_order = 0
        mock_source.is_overlay = False
        mock_source.dependencies = set()
        mock_source.capabilities = {"source"}
        mock_source.process = lambda data, ctx: "data"

        mock_failing = MagicMock(spec=Stage)
        mock_failing.name = "failing"
        mock_failing.category = "effect"
        mock_failing.stage_type = "effect"
        mock_failing.render_order = 0
        mock_failing.is_overlay = False
        mock_failing.dependencies = {"source"}
        mock_failing.capabilities = {"effect"}
        mock_failing.optional = False
        mock_failing.process = lambda data, ctx: (_ for _ in ()).throw(
            Exception("fail")
        )

        pipeline.add_stage("source", mock_source)
        pipeline.add_stage("failing", mock_failing)
        pipeline.build(auto_inject=False)

        result = pipeline.execute(None)

        assert result.success is False
        assert result.error is not None

    def test_optional_stage_failure_continues(self):
        """Pipeline.execute continues on optional stage failure."""
        pipeline = Pipeline()

        mock_source = MagicMock(spec=Stage)
        mock_source.name = "source"
        mock_source.category = "source"
        mock_source.stage_type = "source"
        mock_source.render_order = 0
        mock_source.is_overlay = False
        mock_source.dependencies = set()
        mock_source.capabilities = {"source"}
        mock_source.process = lambda data, ctx: "data"

        mock_optional = MagicMock(spec=Stage)
        mock_optional.name = "optional"
        mock_optional.category = "effect"
        mock_optional.stage_type = "effect"
        mock_optional.render_order = 0
        mock_optional.is_overlay = False
        mock_optional.dependencies = {"source"}
        mock_optional.capabilities = {"effect"}
        mock_optional.optional = True
        mock_optional.process = lambda data, ctx: (_ for _ in ()).throw(
            Exception("fail")
        )

        pipeline.add_stage("source", mock_source)
        pipeline.add_stage("optional", mock_optional)
        pipeline.build(auto_inject=False)

        result = pipeline.execute(None)

        assert result.success is True


class TestCapabilityBasedDependencies:
    """Tests for capability-based dependency resolution."""

    def test_capability_wildcard_resolution(self):
        """Pipeline resolves dependencies using wildcard capabilities."""
        from engine.pipeline.controller import Pipeline
        from engine.pipeline.core import Stage

        class SourceStage(Stage):
            name = "headlines"
            category = "source"

            @property
            def capabilities(self):
                return {"source.headlines"}

            @property
            def dependencies(self):
                return set()

            def process(self, data, ctx):
                return data

        class RenderStage(Stage):
            name = "render"
            category = "render"

            @property
            def capabilities(self):
                return {"render.output"}

            @property
            def dependencies(self):
                return {"source.*"}

            def process(self, data, ctx):
                return data

        pipeline = Pipeline()
        pipeline.add_stage("headlines", SourceStage())
        pipeline.add_stage("render", RenderStage())
        pipeline.build(auto_inject=False)

        assert "headlines" in pipeline.execution_order
        assert "render" in pipeline.execution_order
        assert pipeline.execution_order.index(
            "headlines"
        ) < pipeline.execution_order.index("render")

    def test_missing_capability_raises_error(self):
        """Pipeline raises error when capability is missing."""
        from engine.pipeline.controller import Pipeline
        from engine.pipeline.core import Stage, StageError

        class RenderStage(Stage):
            name = "render"
            category = "render"

            @property
            def capabilities(self):
                return {"render.output"}

            @property
            def dependencies(self):
                return {"source.headlines"}

            def process(self, data, ctx):
                return data

        pipeline = Pipeline()
        pipeline.add_stage("render", RenderStage())

        try:
            pipeline.build(auto_inject=False)
            raise AssertionError("Should have raised StageError")
        except StageError as e:
            assert "Missing capabilities" in e.message
            assert "source.headlines" in e.message

    def test_multiple_stages_same_capability(self):
        """Pipeline uses first registered stage for capability."""
        from engine.pipeline.controller import Pipeline
        from engine.pipeline.core import Stage

        class SourceA(Stage):
            name = "headlines"
            category = "source"

            @property
            def capabilities(self):
                return {"source"}

            @property
            def dependencies(self):
                return set()

            def process(self, data, ctx):
                return "A"

        class SourceB(Stage):
            name = "poetry"
            category = "source"

            @property
            def capabilities(self):
                return {"source"}

            @property
            def dependencies(self):
                return set()

            def process(self, data, ctx):
                return "B"

        class DisplayStage(Stage):
            name = "display"
            category = "display"

            @property
            def capabilities(self):
                return {"display"}

            @property
            def dependencies(self):
                return {"source"}

            def process(self, data, ctx):
                return data

        pipeline = Pipeline()
        pipeline.add_stage("headlines", SourceA())
        pipeline.add_stage("poetry", SourceB())
        pipeline.add_stage("display", DisplayStage())
        pipeline.build(auto_inject=False)

        assert pipeline.execution_order[0] == "headlines"


class TestPipelineContext:
    """Tests for PipelineContext."""

    def test_init_empty(self):
        """PipelineContext initializes with empty services and state."""
        ctx = PipelineContext()

        assert ctx.services == {}
        assert ctx.state == {}

    def test_init_with_services(self):
        """PipelineContext accepts initial services."""
        ctx = PipelineContext(services={"display": MagicMock()})

        assert "display" in ctx.services

    def test_init_with_state(self):
        """PipelineContext accepts initial state."""
        ctx = PipelineContext(initial_state={"count": 42})

        assert ctx.get_state("count") == 42

    def test_get_set_services(self):
        """PipelineContext can get/set services."""
        ctx = PipelineContext()
        mock_service = MagicMock()

        ctx.set("test_service", mock_service)

        assert ctx.get("test_service") == mock_service

    def test_get_set_state(self):
        """PipelineContext can get/set state."""
        ctx = PipelineContext()

        ctx.set_state("counter", 100)

        assert ctx.get_state("counter") == 100

    def test_lazy_resolver(self):
        """PipelineContext resolves lazy services."""
        ctx = PipelineContext()

        config = ctx.get("config")
        assert config is not None

    def test_has_capability(self):
        """PipelineContext.has_capability checks for services."""
        ctx = PipelineContext(services={"display.output": MagicMock()})

        assert ctx.has_capability("display.output") is True
        assert ctx.has_capability("missing") is False


class TestCreateDefaultPipeline:
    """Tests for create_default_pipeline function."""

    def setup_method(self):
        """Reset registry before each test."""
        StageRegistry._discovered = False
        StageRegistry._categories.clear()
        StageRegistry._instances.clear()
        discover_stages()

    def test_create_default_pipeline(self):
        """create_default_pipeline creates a working pipeline."""
        pipeline = create_default_pipeline()

        assert pipeline is not None
        assert "display" in pipeline.stages


class TestPipelineParams:
    """Tests for PipelineParams."""

    def test_default_values(self):
        """PipelineParams has correct defaults."""
        from engine.pipeline.params import PipelineParams

        params = PipelineParams()
        assert params.source == "headlines"
        assert params.display == "terminal"
        assert params.camera_mode == "vertical"
        assert params.effect_order == ["noise", "fade", "glitch", "firehose"]

    def test_effect_config(self):
        """PipelineParams effect config methods work."""
        from engine.pipeline.params import PipelineParams

        params = PipelineParams()
        enabled, intensity = params.get_effect_config("noise")
        assert enabled is True
        assert intensity == 1.0

        params.set_effect_config("noise", False, 0.5)
        enabled, intensity = params.get_effect_config("noise")
        assert enabled is False
        assert intensity == 0.5

    def test_is_effect_enabled(self):
        """PipelineParams is_effect_enabled works."""
        from engine.pipeline.params import PipelineParams

        params = PipelineParams()
        assert params.is_effect_enabled("noise") is True

        params.effect_enabled["noise"] = False
        assert params.is_effect_enabled("noise") is False

    def test_to_dict_from_dict(self):
        """PipelineParams serialization roundtrip works."""
        from engine.pipeline.params import PipelineParams

        params = PipelineParams()
        params.viewport_width = 100
        params.viewport_height = 50

        data = params.to_dict()
        restored = PipelineParams.from_dict(data)

        assert restored.viewport_width == 100
        assert restored.viewport_height == 50

    def test_copy(self):
        """PipelineParams copy works."""
        from engine.pipeline.params import PipelineParams

        params = PipelineParams()
        params.viewport_width = 100
        params.effect_enabled["noise"] = False

        copy = params.copy()
        assert copy.viewport_width == 100
        assert copy.effect_enabled["noise"] is False


class TestPipelinePresets:
    """Tests for pipeline presets."""

    def test_presets_defined(self):
        """All expected presets are defined."""
        from engine.pipeline.presets import (
            DEMO_PRESET,
            FIREHOSE_PRESET,
            PIPELINE_VIZ_PRESET,
            POETRY_PRESET,
            UI_PRESET,
            WEBSOCKET_PRESET,
        )

        assert DEMO_PRESET.name == "demo"
        assert POETRY_PRESET.name == "poetry"
        assert FIREHOSE_PRESET.name == "firehose"
        assert PIPELINE_VIZ_PRESET.name == "pipeline"
        assert WEBSOCKET_PRESET.name == "websocket"
        assert UI_PRESET.name == "ui"

    def test_preset_to_params(self):
        """Presets convert to PipelineParams correctly."""
        from engine.pipeline.presets import DEMO_PRESET

        params = DEMO_PRESET.to_params()
        assert params.source == "headlines"
        assert params.display == "pygame"
        assert "noise" in params.effect_order

    def test_list_presets(self):
        """list_presets returns all presets."""
        from engine.pipeline.presets import list_presets

        presets = list_presets()
        assert "demo" in presets
        assert "poetry" in presets
        assert "firehose" in presets

    def test_get_preset(self):
        """get_preset returns correct preset."""
        from engine.pipeline.presets import get_preset

        preset = get_preset("demo")
        assert preset is not None
        assert preset.name == "demo"

        assert get_preset("nonexistent") is None


class TestStageAdapters:
    """Tests for pipeline stage adapters."""

    def test_display_stage_init(self):
        """DisplayStage.init initializes display."""
        from engine.display.backends.null import NullDisplay
        from engine.pipeline.adapters import DisplayStage
        from engine.pipeline.core import PipelineContext
        from engine.pipeline.params import PipelineParams

        display = NullDisplay()
        stage = DisplayStage(display, name="null")
        ctx = PipelineContext()
        ctx.params = PipelineParams()

        result = stage.init(ctx)
        assert result is True

    def test_display_stage_process(self):
        """DisplayStage.process forwards to display."""
        from engine.display.backends.null import NullDisplay
        from engine.pipeline.adapters import DisplayStage
        from engine.pipeline.core import PipelineContext
        from engine.pipeline.params import PipelineParams

        display = NullDisplay()
        stage = DisplayStage(display, name="null")
        ctx = PipelineContext()
        ctx.params = PipelineParams()

        stage.init(ctx)
        buffer = ["line1", "line2"]
        result = stage.process(buffer, ctx)
        assert result == buffer

    def test_camera_stage(self):
        """CameraStage applies camera transform."""
        from engine.camera import Camera, CameraMode
        from engine.pipeline.adapters import CameraStage
        from engine.pipeline.core import PipelineContext

        camera = Camera(mode=CameraMode.FEED)
        stage = CameraStage(camera, name="vertical")
        PipelineContext()

        assert "camera" in stage.capabilities
        assert "render.output" in stage.dependencies  # Depends on rendered content

    def test_camera_stage_does_not_error_on_process(self):
        """CameraStage.process should not error when setting viewport.

        Regression test: Previously CameraStage tried to set viewport_width
        and viewport_height as writable properties, but they are computed
        from canvas_size / zoom. This caused an AttributeError each frame.
        """
        from engine.camera import Camera, CameraMode
        from engine.pipeline.adapters import CameraStage
        from engine.pipeline.core import PipelineContext
        from engine.pipeline.params import PipelineParams

        camera = Camera(mode=CameraMode.FEED)
        stage = CameraStage(camera, name="vertical")

        ctx = PipelineContext()
        ctx.params = PipelineParams(viewport_width=80, viewport_height=24)

        buffer = ["line" + str(i) for i in range(24)]

        # This should not raise AttributeError
        result = stage.process(buffer, ctx)

        # Should return the buffer (unchanged for FEED mode)
        assert result is not None
        assert len(result) == 24


class TestDataSourceStage:
    """Tests for DataSourceStage adapter."""

    def test_datasource_stage_capabilities(self):
        """DataSourceStage declares correct capabilities."""
        from engine.data_sources.sources import HeadlinesDataSource
        from engine.pipeline.adapters import DataSourceStage

        source = HeadlinesDataSource()
        stage = DataSourceStage(source, name="headlines")

        assert "source.headlines" in stage.capabilities

    def test_datasource_stage_process(self):
        """DataSourceStage fetches from DataSource."""
        from unittest.mock import patch

        from engine.data_sources.sources import HeadlinesDataSource
        from engine.pipeline.adapters import DataSourceStage
        from engine.pipeline.core import PipelineContext

        mock_items = [
            ("Test Headline 1", "TestSource", "12:00"),
            ("Test Headline 2", "TestSource", "12:01"),
        ]

        with patch("engine.fetch.fetch_all", return_value=(mock_items, 1, 0)):
            source = HeadlinesDataSource()
            stage = DataSourceStage(source, name="headlines")

            result = stage.process(None, PipelineContext())

        assert result is not None
        assert isinstance(result, list)


class TestEffectPluginStage:
    """Tests for EffectPluginStage adapter."""

    def test_effect_stage_capabilities(self):
        """EffectPluginStage declares correct capabilities."""
        from engine.effects.types import EffectPlugin
        from engine.pipeline.adapters import EffectPluginStage

        class TestEffect(EffectPlugin):
            name = "test"

            def process(self, buf, ctx):
                return buf

            def configure(self, config):
                pass

        effect = TestEffect()
        stage = EffectPluginStage(effect, name="test")

        assert "effect.test" in stage.capabilities

    def test_effect_stage_with_sensor_bindings(self):
        """EffectPluginStage applies sensor param bindings."""
        from engine.effects.types import EffectConfig, EffectPlugin
        from engine.pipeline.adapters import EffectPluginStage
        from engine.pipeline.core import PipelineContext
        from engine.pipeline.params import PipelineParams

        class SensorDrivenEffect(EffectPlugin):
            name = "sensor_effect"
            config = EffectConfig(intensity=1.0)
            param_bindings = {
                "intensity": {"sensor": "mic", "transform": "linear"},
            }

            def process(self, buf, ctx):
                return buf

            def configure(self, config):
                pass

        effect = SensorDrivenEffect()
        stage = EffectPluginStage(effect, name="sensor_effect")

        ctx = PipelineContext()
        ctx.params = PipelineParams()
        ctx.set_state("sensor.mic", 0.5)

        result = stage.process(["test"], ctx)
        assert result == ["test"]


class TestFullPipeline:
    """End-to-end tests for the full pipeline."""

    def test_pipeline_circular_dependency_detection(self):
        """Pipeline detects circular dependencies."""
        from engine.pipeline.controller import Pipeline
        from engine.pipeline.core import Stage

        class StageA(Stage):
            name = "a"

            @property
            def capabilities(self):
                return {"a"}

            @property
            def dependencies(self):
                return {"b"}

            def process(self, data, ctx):
                return data

        class StageB(Stage):
            name = "b"

            @property
            def capabilities(self):
                return {"b"}

            @property
            def dependencies(self):
                return {"a"}

            def process(self, data, ctx):
                return data

        pipeline = Pipeline()
        pipeline.add_stage("a", StageA())
        pipeline.add_stage("b", StageB())

        try:
            pipeline.build(auto_inject=False)
            raise AssertionError("Should detect circular dependency")
        except Exception:
            pass


class TestPipelineMetrics:
    """Tests for pipeline metrics collection."""

    def test_metrics_collected(self):
        """Pipeline collects metrics when enabled."""
        from engine.pipeline.controller import Pipeline, PipelineConfig
        from engine.pipeline.core import Stage

        class DummyStage(Stage):
            name = "dummy"
            category = "test"

            def process(self, data, ctx):
                return data

        config = PipelineConfig(enable_metrics=True)
        pipeline = Pipeline(config=config)
        pipeline.add_stage("dummy", DummyStage())
        pipeline.build(auto_inject=False)

        pipeline.execute("test_data")

        summary = pipeline.get_metrics_summary()
        assert "pipeline" in summary
        assert summary["frame_count"] == 1

    def test_metrics_disabled(self):
        """Pipeline skips metrics when disabled."""
        from engine.pipeline.controller import Pipeline, PipelineConfig
        from engine.pipeline.core import Stage

        class DummyStage(Stage):
            name = "dummy"
            category = "test"

            def process(self, data, ctx):
                return data

        config = PipelineConfig(enable_metrics=False)
        pipeline = Pipeline(config=config)
        pipeline.add_stage("dummy", DummyStage())
        pipeline.build(auto_inject=False)

        pipeline.execute("test_data")

        summary = pipeline.get_metrics_summary()
        assert "error" in summary

    def test_reset_metrics(self):
        """Pipeline.reset_metrics clears collected metrics."""
        from engine.pipeline.controller import Pipeline, PipelineConfig
        from engine.pipeline.core import Stage

        class DummyStage(Stage):
            name = "dummy"
            category = "test"

            def process(self, data, ctx):
                return data

        config = PipelineConfig(enable_metrics=True)
        pipeline = Pipeline(config=config)
        pipeline.add_stage("dummy", DummyStage())
        pipeline.build(auto_inject=False)

        pipeline.execute("test1")
        pipeline.execute("test2")

        assert pipeline.get_metrics_summary()["frame_count"] == 2

        pipeline.reset_metrics()
        # After reset, metrics collection starts fresh
        pipeline.execute("test3")
        assert pipeline.get_metrics_summary()["frame_count"] == 1


class TestOverlayStages:
    """Tests for overlay stage support."""

    def test_stage_is_overlay_property(self):
        """Stage has is_overlay property defaulting to False."""
        from engine.pipeline.core import Stage

        class TestStage(Stage):
            name = "test"
            category = "effect"

            def process(self, data, ctx):
                return data

        stage = TestStage()
        assert stage.is_overlay is False

    def test_stage_render_order_property(self):
        """Stage has render_order property defaulting to 0."""
        from engine.pipeline.core import Stage

        class TestStage(Stage):
            name = "test"
            category = "effect"

            def process(self, data, ctx):
                return data

        stage = TestStage()
        assert stage.render_order == 0

    def test_stage_stage_type_property(self):
        """Stage has stage_type property defaulting to category."""
        from engine.pipeline.core import Stage

        class TestStage(Stage):
            name = "test"
            category = "effect"

            def process(self, data, ctx):
                return data

        stage = TestStage()
        assert stage.stage_type == "effect"

    def test_pipeline_get_overlay_stages(self):
        """Pipeline.get_overlay_stages returns overlay stages sorted by render_order."""
        from engine.pipeline.controller import Pipeline
        from engine.pipeline.core import Stage

        class OverlayStageA(Stage):
            name = "overlay_a"
            category = "overlay"

            @property
            def is_overlay(self):
                return True

            @property
            def render_order(self):
                return 10

            def process(self, data, ctx):
                return data

        class OverlayStageB(Stage):
            name = "overlay_b"
            category = "overlay"

            @property
            def is_overlay(self):
                return True

            @property
            def render_order(self):
                return 5

            def process(self, data, ctx):
                return data

        class RegularStage(Stage):
            name = "regular"
            category = "effect"

            def process(self, data, ctx):
                return data

        pipeline = Pipeline()
        pipeline.add_stage("overlay_a", OverlayStageA())
        pipeline.add_stage("overlay_b", OverlayStageB())
        pipeline.add_stage("regular", RegularStage())
        pipeline.build(auto_inject=False)

        overlays = pipeline.get_overlay_stages()
        assert len(overlays) == 2
        # Should be sorted by render_order
        assert overlays[0].name == "overlay_b"  # render_order=5
        assert overlays[1].name == "overlay_a"  # render_order=10

    def test_pipeline_executes_overlays_after_regular(self):
        """Pipeline executes overlays after regular stages."""
        from engine.pipeline.controller import Pipeline
        from engine.pipeline.core import Stage

        call_order = []

        class RegularStage(Stage):
            name = "regular"
            category = "effect"

            def process(self, data, ctx):
                call_order.append("regular")
                return data

        class OverlayStage(Stage):
            name = "overlay"
            category = "overlay"

            @property
            def is_overlay(self):
                return True

            @property
            def render_order(self):
                return 100

            def process(self, data, ctx):
                call_order.append("overlay")
                return data

        pipeline = Pipeline()
        pipeline.add_stage("regular", RegularStage())
        pipeline.add_stage("overlay", OverlayStage())
        pipeline.build(auto_inject=False)

        pipeline.execute("data")

        assert call_order == ["regular", "overlay"]

    def test_effect_plugin_stage_hud_is_overlay(self):
        """EffectPluginStage marks HUD as overlay."""
        from engine.effects.types import EffectConfig, EffectPlugin
        from engine.pipeline.adapters import EffectPluginStage

        class HudEffect(EffectPlugin):
            name = "hud"
            config = EffectConfig(enabled=True)

            def process(self, buf, ctx):
                return buf

            def configure(self, config):
                pass

        stage = EffectPluginStage(HudEffect(), name="hud")
        assert stage.is_overlay is True
        assert stage.stage_type == "overlay"
        assert stage.render_order == 100

    def test_effect_plugin_stage_non_hud_not_overlay(self):
        """EffectPluginStage marks non-HUD effects as not overlay."""
        from engine.effects.types import EffectConfig, EffectPlugin
        from engine.pipeline.adapters import EffectPluginStage

        class FadeEffect(EffectPlugin):
            name = "fade"
            config = EffectConfig(enabled=True)

            def process(self, buf, ctx):
                return buf

            def configure(self, config):
                pass

        stage = EffectPluginStage(FadeEffect(), name="fade")
        assert stage.is_overlay is False
        assert stage.stage_type == "effect"
        assert stage.render_order == 0

    def test_pipeline_get_stage_type(self):
        """Pipeline.get_stage_type returns stage_type for a stage."""
        from engine.pipeline.controller import Pipeline
        from engine.pipeline.core import Stage

        class TestStage(Stage):
            name = "test"
            category = "effect"

            @property
            def stage_type(self):
                return "overlay"

            def process(self, data, ctx):
                return data

        pipeline = Pipeline()
        pipeline.add_stage("test", TestStage())
        pipeline.build(auto_inject=False)

        assert pipeline.get_stage_type("test") == "overlay"

    def test_pipeline_get_render_order(self):
        """Pipeline.get_render_order returns render_order for a stage."""
        from engine.pipeline.controller import Pipeline
        from engine.pipeline.core import Stage

        class TestStage(Stage):
            name = "test"
            category = "effect"

            @property
            def render_order(self):
                return 42

            def process(self, data, ctx):
                return data

        pipeline = Pipeline()
        pipeline.add_stage("test", TestStage())
        pipeline.build(auto_inject=False)

        assert pipeline.get_render_order("test") == 42


class TestInletOutletTypeValidation:
    """Test type validation between connected stages."""

    def test_type_mismatch_raises_error(self):
        """Type mismatch between stages raises StageError."""

        class ProducerStage(Stage):
            name = "producer"
            category = "test"

            @property
            def inlet_types(self):
                return {DataType.NONE}

            @property
            def outlet_types(self):
                return {DataType.SOURCE_ITEMS}

            def process(self, data, ctx):
                return data

        class ConsumerStage(Stage):
            name = "consumer"
            category = "test"

            @property
            def dependencies(self):
                return {"test.producer"}

            @property
            def inlet_types(self):
                return {DataType.TEXT_BUFFER}  # Incompatible!

            @property
            def outlet_types(self):
                return {DataType.TEXT_BUFFER}

            def process(self, data, ctx):
                return data

        pipeline = Pipeline()
        pipeline.add_stage("producer", ProducerStage())
        pipeline.add_stage("consumer", ConsumerStage())

        with pytest.raises(StageError) as exc_info:
            pipeline.build(auto_inject=False)

        assert "Type mismatch" in str(exc_info.value)
        assert "TEXT_BUFFER" in str(exc_info.value)
        assert "SOURCE_ITEMS" in str(exc_info.value)

    def test_compatible_types_pass_validation(self):
        """Compatible types pass validation."""

        class ProducerStage(Stage):
            name = "producer"
            category = "test"

            @property
            def inlet_types(self):
                return {DataType.NONE}

            @property
            def outlet_types(self):
                return {DataType.SOURCE_ITEMS}

            def process(self, data, ctx):
                return data

        class ConsumerStage(Stage):
            name = "consumer"
            category = "test"

            @property
            def dependencies(self):
                return {"test.producer"}

            @property
            def inlet_types(self):
                return {DataType.SOURCE_ITEMS}  # Compatible!

            @property
            def outlet_types(self):
                return {DataType.TEXT_BUFFER}

            def process(self, data, ctx):
                return data

        pipeline = Pipeline()
        pipeline.add_stage("producer", ProducerStage())
        pipeline.add_stage("consumer", ConsumerStage())

        # Should not raise
        pipeline.build(auto_inject=False)

    def test_any_type_accepts_everything(self):
        """DataType.ANY accepts any upstream type."""

        class ProducerStage(Stage):
            name = "producer"
            category = "test"

            @property
            def inlet_types(self):
                return {DataType.NONE}

            @property
            def outlet_types(self):
                return {DataType.SOURCE_ITEMS}

            def process(self, data, ctx):
                return data

        class ConsumerStage(Stage):
            name = "consumer"
            category = "test"

            @property
            def dependencies(self):
                return {"test.producer"}

            @property
            def inlet_types(self):
                return {DataType.ANY}  # Accepts anything

            @property
            def outlet_types(self):
                return {DataType.TEXT_BUFFER}

            def process(self, data, ctx):
                return data

        pipeline = Pipeline()
        pipeline.add_stage("producer", ProducerStage())
        pipeline.add_stage("consumer", ConsumerStage())

        # Should not raise because consumer accepts ANY
        pipeline.build(auto_inject=False)

    def test_multiple_compatible_types(self):
        """Stage can declare multiple inlet types."""

        class ProducerStage(Stage):
            name = "producer"
            category = "test"

            @property
            def inlet_types(self):
                return {DataType.NONE}

            @property
            def outlet_types(self):
                return {DataType.SOURCE_ITEMS}

            def process(self, data, ctx):
                return data

        class ConsumerStage(Stage):
            name = "consumer"
            category = "test"

            @property
            def dependencies(self):
                return {"test.producer"}

            @property
            def inlet_types(self):
                return {DataType.SOURCE_ITEMS, DataType.TEXT_BUFFER}

            @property
            def outlet_types(self):
                return {DataType.TEXT_BUFFER}

            def process(self, data, ctx):
                return data

        pipeline = Pipeline()
        pipeline.add_stage("producer", ProducerStage())
        pipeline.add_stage("consumer", ConsumerStage())

        # Should not raise because consumer accepts SOURCE_ITEMS
        pipeline.build(auto_inject=False)

    def test_display_must_accept_text_buffer(self):
        """Display stages must accept TEXT_BUFFER type."""

        class BadDisplayStage(Stage):
            name = "display"
            category = "display"

            @property
            def inlet_types(self):
                return {DataType.SOURCE_ITEMS}  # Wrong type for display!

            @property
            def outlet_types(self):
                return {DataType.NONE}

            def process(self, data, ctx):
                return data

        pipeline = Pipeline()
        pipeline.add_stage("display", BadDisplayStage())

        with pytest.raises(StageError) as exc_info:
            pipeline.build(auto_inject=False)

        assert "display" in str(exc_info.value).lower()


class TestPipelineMutation:
    """Tests for Pipeline Mutation API - dynamic stage modification."""

    def setup_method(self):
        """Set up test fixtures."""
        StageRegistry._discovered = False
        StageRegistry._categories.clear()
        StageRegistry._instances.clear()
        discover_stages()

    def _create_mock_stage(
        self,
        name: str = "test",
        category: str = "test",
        capabilities: set | None = None,
        dependencies: set | None = None,
    ):
        """Helper to create a mock stage."""
        from engine.pipeline.core import DataType

        mock = MagicMock(spec=Stage)
        mock.name = name
        mock.category = category
        mock.stage_type = category
        mock.render_order = 0
        mock.is_overlay = False
        mock.inlet_types = {DataType.ANY}
        mock.outlet_types = {DataType.TEXT_BUFFER}
        mock.capabilities = capabilities or {f"{category}.{name}"}
        mock.dependencies = dependencies or set()
        mock.process = lambda data, ctx: data
        mock.init = MagicMock(return_value=True)
        mock.cleanup = MagicMock()
        mock.is_enabled = MagicMock(return_value=True)
        mock.set_enabled = MagicMock()
        mock._enabled = True
        return mock

    def test_add_stage_initializes_when_pipeline_initialized(self):
        """add_stage() initializes stage when pipeline already initialized."""
        pipeline = Pipeline()
        mock_stage = self._create_mock_stage("test")
        pipeline.build(auto_inject=False)
        pipeline._initialized = True

        pipeline.add_stage("test", mock_stage, initialize=True)

        mock_stage.init.assert_called_once()

    def test_add_stage_skips_initialize_when_pipeline_not_initialized(self):
        """add_stage() skips initialization when pipeline not built."""
        pipeline = Pipeline()
        mock_stage = self._create_mock_stage("test")

        pipeline.add_stage("test", mock_stage, initialize=False)

        mock_stage.init.assert_not_called()

    def test_remove_stage_returns_removed_stage(self):
        """remove_stage() returns the removed stage."""
        pipeline = Pipeline()
        mock_stage = self._create_mock_stage("test")
        pipeline.add_stage("test", mock_stage, initialize=False)

        removed = pipeline.remove_stage("test", cleanup=False)

        assert removed is mock_stage
        assert "test" not in pipeline.stages

    def test_remove_stage_calls_cleanup_when_requested(self):
        """remove_stage() calls cleanup when cleanup=True."""
        pipeline = Pipeline()
        mock_stage = self._create_mock_stage("test")
        pipeline.add_stage("test", mock_stage, initialize=False)

        pipeline.remove_stage("test", cleanup=True)

        mock_stage.cleanup.assert_called_once()

    def test_remove_stage_skips_cleanup_when_requested(self):
        """remove_stage() skips cleanup when cleanup=False."""
        pipeline = Pipeline()
        mock_stage = self._create_mock_stage("test")
        pipeline.add_stage("test", mock_stage, initialize=False)

        pipeline.remove_stage("test", cleanup=False)

        mock_stage.cleanup.assert_not_called()

    def test_remove_nonexistent_stage_returns_none(self):
        """remove_stage() returns None for nonexistent stage."""
        pipeline = Pipeline()

        result = pipeline.remove_stage("nonexistent", cleanup=False)

        assert result is None

    def test_replace_stage_preserves_state(self):
        """replace_stage() copies _enabled from old to new stage."""
        pipeline = Pipeline()
        old_stage = self._create_mock_stage("test")
        old_stage._enabled = False

        new_stage = self._create_mock_stage("test")

        pipeline.add_stage("test", old_stage, initialize=False)
        pipeline.replace_stage("test", new_stage, preserve_state=True)

        assert new_stage._enabled is False
        old_stage.cleanup.assert_called_once()
        new_stage.init.assert_called_once()

    def test_replace_stage_without_preserving_state(self):
        """replace_stage() without preserve_state doesn't copy state."""
        pipeline = Pipeline()
        old_stage = self._create_mock_stage("test")
        old_stage._enabled = False

        new_stage = self._create_mock_stage("test")
        new_stage._enabled = True

        pipeline.add_stage("test", old_stage, initialize=False)
        pipeline.replace_stage("test", new_stage, preserve_state=False)

        assert new_stage._enabled is True

    def test_replace_nonexistent_stage_returns_none(self):
        """replace_stage() returns None for nonexistent stage."""
        pipeline = Pipeline()
        mock_stage = self._create_mock_stage("test")

        result = pipeline.replace_stage("nonexistent", mock_stage)

        assert result is None

    def test_swap_stages_swaps_stages(self):
        """swap_stages() swaps two stages."""
        pipeline = Pipeline()
        stage_a = self._create_mock_stage("stage_a", "a")
        stage_b = self._create_mock_stage("stage_b", "b")

        pipeline.add_stage("a", stage_a, initialize=False)
        pipeline.add_stage("b", stage_b, initialize=False)

        result = pipeline.swap_stages("a", "b")

        assert result is True
        assert pipeline.stages["a"].name == "stage_b"
        assert pipeline.stages["b"].name == "stage_a"

    def test_swap_stages_fails_for_nonexistent(self):
        """swap_stages() fails if either stage doesn't exist."""
        pipeline = Pipeline()
        stage = self._create_mock_stage("test")

        pipeline.add_stage("test", stage, initialize=False)

        result = pipeline.swap_stages("test", "nonexistent")

        assert result is False

    def test_move_stage_after(self):
        """move_stage() moves stage after another."""
        pipeline = Pipeline()
        stage_a = self._create_mock_stage("a")
        stage_b = self._create_mock_stage("b")
        stage_c = self._create_mock_stage("c")

        pipeline.add_stage("a", stage_a, initialize=False)
        pipeline.add_stage("b", stage_b, initialize=False)
        pipeline.add_stage("c", stage_c, initialize=False)
        pipeline.build(auto_inject=False)

        result = pipeline.move_stage("a", after="c")

        assert result is True
        idx_a = pipeline.execution_order.index("a")
        idx_c = pipeline.execution_order.index("c")
        assert idx_a > idx_c

    def test_move_stage_before(self):
        """move_stage() moves stage before another."""
        pipeline = Pipeline()
        stage_a = self._create_mock_stage("a")
        stage_b = self._create_mock_stage("b")
        stage_c = self._create_mock_stage("c")

        pipeline.add_stage("a", stage_a, initialize=False)
        pipeline.add_stage("b", stage_b, initialize=False)
        pipeline.add_stage("c", stage_c, initialize=False)
        pipeline.build(auto_inject=False)

        result = pipeline.move_stage("c", before="a")

        assert result is True
        idx_a = pipeline.execution_order.index("a")
        idx_c = pipeline.execution_order.index("c")
        assert idx_c < idx_a

    def test_move_stage_fails_for_nonexistent(self):
        """move_stage() fails if stage doesn't exist."""
        pipeline = Pipeline()
        stage = self._create_mock_stage("test")

        pipeline.add_stage("test", stage, initialize=False)
        pipeline.build(auto_inject=False)

        result = pipeline.move_stage("nonexistent", after="test")

        assert result is False

    def test_move_stage_fails_when_not_initialized(self):
        """move_stage() fails if pipeline not built."""
        pipeline = Pipeline()
        stage = self._create_mock_stage("test")

        pipeline.add_stage("test", stage, initialize=False)

        result = pipeline.move_stage("test", after="other")

        assert result is False

    def test_enable_stage(self):
        """enable_stage() enables a stage."""
        pipeline = Pipeline()
        stage = self._create_mock_stage("test")

        pipeline.add_stage("test", stage, initialize=False)

        result = pipeline.enable_stage("test")

        assert result is True
        stage.set_enabled.assert_called_with(True)

    def test_enable_nonexistent_stage_returns_false(self):
        """enable_stage() returns False for nonexistent stage."""
        pipeline = Pipeline()

        result = pipeline.enable_stage("nonexistent")

        assert result is False

    def test_disable_stage(self):
        """disable_stage() disables a stage."""
        pipeline = Pipeline()
        stage = self._create_mock_stage("test")

        pipeline.add_stage("test", stage, initialize=False)

        result = pipeline.disable_stage("test")

        assert result is True
        stage.set_enabled.assert_called_with(False)

    def test_disable_nonexistent_stage_returns_false(self):
        """disable_stage() returns False for nonexistent stage."""
        pipeline = Pipeline()

        result = pipeline.disable_stage("nonexistent")

        assert result is False

    def test_get_stage_info_returns_correct_info(self):
        """get_stage_info() returns correct stage information."""
        pipeline = Pipeline()
        stage = self._create_mock_stage(
            "test_stage",
            "effect",
            capabilities={"effect.test"},
            dependencies={"source"},
        )
        stage.render_order = 5
        stage.is_overlay = False
        stage.optional = True

        pipeline.add_stage("test", stage, initialize=False)

        info = pipeline.get_stage_info("test")

        assert info is not None
        assert info["name"] == "test"  # Dict key, not stage.name
        assert info["category"] == "effect"
        assert info["stage_type"] == "effect"
        assert info["enabled"] is True
        assert info["optional"] is True
        assert info["capabilities"] == ["effect.test"]
        assert info["dependencies"] == ["source"]
        assert info["render_order"] == 5
        assert info["is_overlay"] is False

    def test_get_stage_info_returns_none_for_nonexistent(self):
        """get_stage_info() returns None for nonexistent stage."""
        pipeline = Pipeline()

        info = pipeline.get_stage_info("nonexistent")

        assert info is None

    def test_get_pipeline_info_returns_complete_info(self):
        """get_pipeline_info() returns complete pipeline state."""
        pipeline = Pipeline()
        stage1 = self._create_mock_stage("stage1")
        stage2 = self._create_mock_stage("stage2")

        pipeline.add_stage("s1", stage1, initialize=False)
        pipeline.add_stage("s2", stage2, initialize=False)
        pipeline.build(auto_inject=False)

        info = pipeline.get_pipeline_info()

        assert "stages" in info
        assert "execution_order" in info
        assert info["initialized"] is True
        assert info["stage_count"] == 2
        assert "s1" in info["stages"]
        assert "s2" in info["stages"]

    def test_rebuild_after_mutation(self):
        """_rebuild() updates execution order after mutation."""
        pipeline = Pipeline()
        source = self._create_mock_stage(
            "source", "source", capabilities={"source"}, dependencies=set()
        )
        effect = self._create_mock_stage(
            "effect", "effect", capabilities={"effect"}, dependencies={"source"}
        )
        display = self._create_mock_stage(
            "display", "display", capabilities={"display"}, dependencies={"effect"}
        )

        pipeline.add_stage("source", source, initialize=False)
        pipeline.add_stage("effect", effect, initialize=False)
        pipeline.add_stage("display", display, initialize=False)
        pipeline.build(auto_inject=False)

        assert pipeline.execution_order == ["source", "effect", "display"]

        pipeline.remove_stage("effect", cleanup=False)

        pipeline._rebuild()

        assert "effect" not in pipeline.execution_order
        assert "source" in pipeline.execution_order
        assert "display" in pipeline.execution_order

    def test_add_stage_after_build(self):
        """add_stage() can add stage after build with initialization."""
        pipeline = Pipeline()
        source = self._create_mock_stage(
            "source", "source", capabilities={"source"}, dependencies=set()
        )
        display = self._create_mock_stage(
            "display", "display", capabilities={"display"}, dependencies={"source"}
        )

        pipeline.add_stage("source", source, initialize=False)
        pipeline.add_stage("display", display, initialize=False)
        pipeline.build(auto_inject=False)

        new_stage = self._create_mock_stage(
            "effect", "effect", capabilities={"effect"}, dependencies={"source"}
        )

        pipeline.add_stage("effect", new_stage, initialize=True)

        assert "effect" in pipeline.stages
        new_stage.init.assert_called_once()

    def test_mutation_preserves_execution_for_remaining_stages(self):
        """Removing a stage doesn't break execution of remaining stages."""
        from engine.pipeline.core import DataType

        call_log = []

        class TestSource(Stage):
            name = "source"
            category = "source"

            @property
            def inlet_types(self):
                return {DataType.NONE}

            @property
            def outlet_types(self):
                return {DataType.SOURCE_ITEMS}

            @property
            def capabilities(self):
                return {"source"}

            @property
            def dependencies(self):
                return set()

            def process(self, data, ctx):
                call_log.append("source")
                return ["item"]

        class TestEffect(Stage):
            name = "effect"
            category = "effect"

            @property
            def inlet_types(self):
                return {DataType.SOURCE_ITEMS}

            @property
            def outlet_types(self):
                return {DataType.TEXT_BUFFER}

            @property
            def capabilities(self):
                return {"effect"}

            @property
            def dependencies(self):
                return {"source"}

            def process(self, data, ctx):
                call_log.append("effect")
                return data

        class TestDisplay(Stage):
            name = "display"
            category = "display"

            @property
            def inlet_types(self):
                return {DataType.TEXT_BUFFER}

            @property
            def outlet_types(self):
                return {DataType.NONE}

            @property
            def capabilities(self):
                return {"display"}

            @property
            def dependencies(self):
                return {"effect"}

            def process(self, data, ctx):
                call_log.append("display")
                return data

        pipeline = Pipeline()
        pipeline.add_stage("source", TestSource(), initialize=False)
        pipeline.add_stage("effect", TestEffect(), initialize=False)
        pipeline.add_stage("display", TestDisplay(), initialize=False)
        pipeline.build(auto_inject=False)
        pipeline.initialize()

        result = pipeline.execute(None)
        assert result.success
        assert call_log == ["source", "effect", "display"]

        call_log.clear()
        pipeline.remove_stage("effect", cleanup=True)

        pipeline._rebuild()

        result = pipeline.execute(None)
        assert result.success
        assert call_log == ["source", "display"]


class TestAutoInjection:
    """Tests for auto-injection of minimum capabilities."""

    def setup_method(self):
        """Reset registry before each test."""
        StageRegistry._discovered = False
        StageRegistry._categories.clear()
        StageRegistry._instances.clear()
        discover_stages()

    def test_auto_injection_provides_minimum_capabilities(self):
        """Pipeline with no stages gets minimum capabilities auto-injected."""
        pipeline = Pipeline()
        # Don't add any stages
        pipeline.build(auto_inject=True)

        # Should have stages for source, render, camera, display
        assert len(pipeline.stages) > 0
        assert "source" in pipeline.stages
        assert "display" in pipeline.stages

    def test_auto_injection_rebuilds_execution_order(self):
        """Auto-injection rebuilds execution order correctly."""
        pipeline = Pipeline()
        pipeline.build(auto_inject=True)

        # Execution order should be valid
        assert len(pipeline.execution_order) > 0
        # Source should come before display
        source_idx = pipeline.execution_order.index("source")
        display_idx = pipeline.execution_order.index("display")
        assert source_idx < display_idx

    def test_validation_error_after_auto_injection(self):
        """Pipeline raises error if auto-injection fails to provide capabilities."""
        from unittest.mock import patch

        pipeline = Pipeline()

        # Mock ensure_minimum_capabilities to return empty list (injection failed)
        with (
            patch.object(pipeline, "ensure_minimum_capabilities", return_value=[]),
            patch.object(
                pipeline,
                "validate_minimum_capabilities",
                return_value=(False, ["source"]),
            ),
        ):
            # Even though injection "ran", it didn't provide the capability
            # build() should raise StageError
            with pytest.raises(StageError) as exc_info:
                pipeline.build(auto_inject=True)

            assert "Auto-injection failed" in str(exc_info.value)

    def test_minimum_capability_removal_recovery(self):
        """Pipeline re-injects minimum capability if removed."""
        pipeline = Pipeline()
        pipeline.build(auto_inject=True)

        # Remove the display stage
        pipeline.remove_stage("display", cleanup=True)

        # Rebuild with auto-injection
        pipeline.build(auto_inject=True)

        # Display should be back
        assert "display" in pipeline.stages
