"""Tests for the hybrid preset-graph configuration system."""

import pytest
from pathlib import Path

from engine.effects.plugins import discover_plugins
from engine.pipeline.hybrid_config import (
    PipelineConfig,
    CameraConfig,
    EffectConfig,
    DisplayConfig,
    load_hybrid_config,
    parse_hybrid_config,
)


class TestHybridConfigCreation:
    """Tests for creating hybrid config objects."""

    def test_create_minimal_config(self):
        """Can create minimal hybrid config."""
        config = PipelineConfig()
        assert config.source == "headlines"
        assert config.camera is None
        assert len(config.effects) == 0
        assert config.display is None

    def test_create_full_config(self):
        """Can create full hybrid config with all options."""
        config = PipelineConfig(
            source="poetry",
            camera=CameraConfig(mode="scroll", speed=1.5),
            effects=[
                EffectConfig(name="noise", intensity=0.3),
                EffectConfig(name="fade", intensity=0.5),
            ],
            display=DisplayConfig(backend="terminal", positioning="mixed"),
        )
        assert config.source == "poetry"
        assert config.camera.mode == "scroll"
        assert len(config.effects) == 2
        assert config.display.backend == "terminal"


class TestHybridConfigParsing:
    """Tests for parsing hybrid config from TOML/dict."""

    def test_parse_minimal_dict(self):
        """Can parse minimal config from dict."""
        data = {
            "pipeline": {
                "source": "headlines",
            }
        }
        config = parse_hybrid_config(data)
        assert config.source == "headlines"
        assert config.camera is None
        assert len(config.effects) == 0

    def test_parse_full_dict(self):
        """Can parse full config from dict."""
        data = {
            "pipeline": {
                "source": "poetry",
                "camera": {"mode": "scroll", "speed": 1.5},
                "effects": [
                    {"name": "noise", "intensity": 0.3},
                    {"name": "fade", "intensity": 0.5},
                ],
                "display": {"backend": "terminal", "positioning": "mixed"},
                "viewport_width": 100,
                "viewport_height": 30,
            }
        }
        config = parse_hybrid_config(data)
        assert config.source == "poetry"
        assert config.camera.mode == "scroll"
        assert config.camera.speed == 1.5
        assert len(config.effects) == 2
        assert config.effects[0].name == "noise"
        assert config.effects[0].intensity == 0.3
        assert config.effects[1].name == "fade"
        assert config.effects[1].intensity == 0.5
        assert config.display.backend == "terminal"
        assert config.viewport_width == 100
        assert config.viewport_height == 30

    def test_parse_effect_as_string(self):
        """Can parse effect specified as string."""
        data = {
            "pipeline": {
                "source": "headlines",
                "effects": ["noise", "fade"],
            }
        }
        config = parse_hybrid_config(data)
        assert len(config.effects) == 2
        assert config.effects[0].name == "noise"
        assert config.effects[0].intensity == 1.0
        assert config.effects[1].name == "fade"

    def test_parse_camera_as_string(self):
        """Can parse camera specified as string."""
        data = {
            "pipeline": {
                "source": "headlines",
                "camera": "scroll",
            }
        }
        config = parse_hybrid_config(data)
        assert config.camera.mode == "scroll"
        assert config.camera.speed == 1.0

    def test_parse_display_as_string(self):
        """Can parse display specified as string."""
        data = {
            "pipeline": {
                "source": "headlines",
                "display": "terminal",
            }
        }
        config = parse_hybrid_config(data)
        assert config.display.backend == "terminal"


class TestHybridConfigToGraph:
    """Tests for converting hybrid config to Graph."""

    def test_minimal_config_to_graph(self):
        """Can convert minimal config to graph."""
        config = PipelineConfig(source="headlines")
        graph = config.to_graph()
        assert "source" in graph.nodes
        assert "display" in graph.nodes
        assert len(graph.connections) == 1  # source -> display

    def test_full_config_to_graph(self):
        """Can convert full config to graph."""
        config = PipelineConfig(
            source="headlines",
            camera=CameraConfig(mode="scroll"),
            effects=[EffectConfig(name="noise", intensity=0.3)],
            display=DisplayConfig(backend="terminal"),
        )
        graph = config.to_graph()
        assert "source" in graph.nodes
        assert "camera" in graph.nodes
        assert "noise" in graph.nodes
        assert "display" in graph.nodes
        assert len(graph.connections) == 3  # source -> camera -> noise -> display

    def test_graph_node_config(self):
        """Graph nodes have correct configuration."""
        config = PipelineConfig(
            source="headlines",
            effects=[EffectConfig(name="noise", intensity=0.7)],
        )
        graph = config.to_graph()
        noise_node = graph.nodes["noise"]
        assert noise_node.config["effect"] == "noise"
        assert noise_node.config["intensity"] == 0.7


class TestHybridConfigToPipeline:
    """Tests for converting hybrid config to Pipeline."""

    @pytest.fixture(autouse=True)
    def setup(self):
        """Setup before each test."""
        discover_plugins()

    def test_minimal_config_to_pipeline(self):
        """Can convert minimal config to pipeline."""
        config = PipelineConfig(source="headlines")
        pipeline = config.to_pipeline(viewport_width=80, viewport_height=24)
        assert pipeline is not None
        assert "source" in pipeline._stages
        assert "display" in pipeline._stages

    def test_full_config_to_pipeline(self):
        """Can convert full config to pipeline."""
        config = PipelineConfig(
            source="headlines",
            camera=CameraConfig(mode="scroll"),
            effects=[
                EffectConfig(name="noise", intensity=0.3),
                EffectConfig(name="fade", intensity=0.5),
            ],
            display=DisplayConfig(backend="null"),
        )
        pipeline = config.to_pipeline(viewport_width=80, viewport_height=24)
        assert pipeline is not None
        assert "source" in pipeline._stages
        assert "camera" in pipeline._stages
        assert "noise" in pipeline._stages
        assert "fade" in pipeline._stages
        assert "display" in pipeline._stages

    def test_pipeline_execution(self):
        """Pipeline can execute and produce output."""
        config = PipelineConfig(
            source="headlines",
            display=DisplayConfig(backend="null"),
        )
        pipeline = config.to_pipeline(viewport_width=80, viewport_height=24)
        pipeline.initialize()
        result = pipeline.execute([])
        assert result.success
        assert len(result.data) > 0


class TestHybridConfigLoading:
    """Tests for loading hybrid config from TOML file."""

    @pytest.fixture(autouse=True)
    def setup(self):
        """Setup before each test."""
        discover_plugins()

    def test_load_hybrid_config_file(self):
        """Can load hybrid config from TOML file."""
        toml_path = Path("examples/hybrid_config.toml")
        if toml_path.exists():
            config = load_hybrid_config(toml_path)
            assert config.source == "headlines"
            assert config.camera is not None
            assert len(config.effects) == 4
            assert config.display is not None


class TestVerbosityComparison:
    """Compare verbosity of different configuration formats."""

    def test_hybrid_vs_verbose_dsl(self):
        """Hybrid config is significantly more compact."""
        # Hybrid config uses 4 lines for effects vs 16 lines in verbose DSL
        # Plus no connection string needed
        # Total: ~20 lines vs ~39 lines (50% reduction)

        hybrid_lines = 20  # approximate from hybrid_config.toml
        verbose_lines = 39  # approximate from default_visualization.toml

        assert hybrid_lines < verbose_lines
        assert hybrid_lines <= verbose_lines * 0.6  # At least 40% smaller


class TestFromPreset:
    """Test converting from preset to PipelineConfig."""

    def test_from_preset_upstream_default(self):
        """Can create PipelineConfig from upstream-default preset."""
        config = PipelineConfig.from_preset("upstream-default")
        assert config.source == "headlines"
        assert config.camera.mode == "scroll"
        assert len(config.effects) == 4  # noise, fade, glitch, firehose
        assert config.display.backend == "terminal"
        assert config.display.positioning == "mixed"

    def test_from_preset_not_found(self):
        """Raises error for non-existent preset."""
        with pytest.raises(ValueError, match="Preset 'nonexistent' not found"):
            PipelineConfig.from_preset("nonexistent")
