"""Integration tests for ViewportFilterStage with realistic data volumes.

These tests verify that the ViewportFilterStage effectively reduces the number
of items processed by FontStage, preventing the 10+ second hangs observed with
large headline sources.
"""

from engine.data_sources.sources import SourceItem
from engine.pipeline.adapters import ViewportFilterStage
from engine.pipeline.core import PipelineContext


class MockParams:
    """Mock parameters object for testing."""

    def __init__(self, viewport_width: int = 80, viewport_height: int = 24):
        self.viewport_width = viewport_width
        self.viewport_height = viewport_height


class TestViewportFilterStage:
    """Test ViewportFilterStage filtering behavior."""

    def test_filter_stage_exists(self):
        """Verify ViewportFilterStage can be instantiated."""
        stage = ViewportFilterStage()
        assert stage is not None
        assert stage.name == "viewport-filter"

    def test_filter_stage_properties(self):
        """Verify ViewportFilterStage has correct type properties."""
        stage = ViewportFilterStage()
        from engine.pipeline.core import DataType

        assert DataType.SOURCE_ITEMS in stage.inlet_types
        assert DataType.SOURCE_ITEMS in stage.outlet_types

    def test_filter_large_item_count_to_viewport(self):
        """Test filtering 1438 items (like real headlines) to viewport size."""
        # Create 1438 test items (matching real headline source)
        test_items = [
            SourceItem(f"Headline {i}", f"source-{i % 5}", str(i)) for i in range(1438)
        ]

        stage = ViewportFilterStage()
        ctx = PipelineContext()
        ctx.params = MockParams(viewport_width=80, viewport_height=24)

        # Filter items
        filtered = stage.process(test_items, ctx)

        # Verify filtering reduced item count significantly
        assert len(filtered) < len(test_items)
        assert len(filtered) <= 5  # 24 height / 6 lines per item + 1
        assert len(filtered) > 0  # Must return at least 1 item

    def test_filter_respects_viewport_height(self):
        """Test that filter respects different viewport heights."""
        test_items = [SourceItem(f"Headline {i}", "source", str(i)) for i in range(100)]

        stage = ViewportFilterStage()

        # Test with different viewport heights
        for height in [12, 24, 48]:
            ctx = PipelineContext()
            ctx.params = MockParams(viewport_height=height)

            filtered = stage.process(test_items, ctx)
            expected_max = max(1, height // 6 + 1)

            assert len(filtered) <= expected_max
            assert len(filtered) > 0

    def test_filter_handles_empty_list(self):
        """Test filter handles empty input gracefully."""
        stage = ViewportFilterStage()
        ctx = PipelineContext()
        ctx.params = MockParams()

        result = stage.process([], ctx)

        assert result == []

    def test_filter_handles_none(self):
        """Test filter handles None input gracefully."""
        stage = ViewportFilterStage()
        ctx = PipelineContext()
        ctx.params = MockParams()

        result = stage.process(None, ctx)

        assert result is None

    def test_filter_performance_improvement(self):
        """Verify significant performance improvement (288x reduction)."""
        # With 1438 items and 24-line viewport:
        # - Without filter: FontStage renders all 1438 items
        # - With filter: FontStage renders only ~5 items
        # - Improvement: 1438 / 3 = ~479x fewer items to render
        #   (layout-based filtering is more precise than old estimate)

        test_items = [
            SourceItem(f"Headline {i}", "source", str(i)) for i in range(1438)
        ]

        stage = ViewportFilterStage()
        ctx = PipelineContext()
        ctx.params = MockParams(viewport_height=24)

        filtered = stage.process(test_items, ctx)
        improvement_factor = len(test_items) / len(filtered)

        # Verify we get significant improvement (360x with 4 items vs 1438)
        assert improvement_factor > 300
        assert 300 < improvement_factor < 500


class TestViewportFilterIntegration:
    """Test ViewportFilterStage in pipeline context."""

    def test_filter_output_is_source_items(self):
        """Verify filter output can be consumed by FontStage."""
        from engine.pipeline.adapters import FontStage

        test_items = [
            SourceItem("Test Headline", "test-source", "123") for _ in range(10)
        ]

        filter_stage = ViewportFilterStage()
        font_stage = FontStage()

        ctx = PipelineContext()
        ctx.params = MockParams()

        # Filter items
        filtered = filter_stage.process(test_items, ctx)

        # Verify filtered output is compatible with FontStage
        assert isinstance(filtered, list)
        assert all(isinstance(item, SourceItem) for item in filtered)

        # FontStage should accept the filtered items
        # (This would throw if types were incompatible)
        result = font_stage.process(filtered, ctx)
        assert result is not None

    def test_filter_preserves_item_order(self):
        """Verify filter preserves order of first N items."""
        test_items = [SourceItem(f"Headline {i}", "source", str(i)) for i in range(20)]

        stage = ViewportFilterStage()
        ctx = PipelineContext()
        ctx.params = MockParams(viewport_height=24)

        filtered = stage.process(test_items, ctx)

        # Verify we kept the first N items in order
        for i, item in enumerate(filtered):
            assert item.content == f"Headline {i}"


class TestViewportResize:
    """Test ViewportFilterStage handles viewport resize correctly."""

    def test_layout_recomputes_on_width_change(self):
        """Test that layout is recomputed when viewport_width changes."""
        stage = ViewportFilterStage()
        # Use long headlines that will wrap differently at different widths
        items = [
            SourceItem(
                f"This is a very long headline number {i} that will definitely wrap at narrow widths",
                "test",
                str(i),
            )
            for i in range(50)
        ]

        # Initial render at 80 cols
        ctx = PipelineContext()
        ctx.params = MockParams(viewport_width=80, viewport_height=24)
        ctx.set("camera_y", 0)

        stage.process(items, ctx)
        cached_layout_80 = stage._layout.copy()

        # Resize to 40 cols - layout should recompute
        ctx.params.viewport_width = 40
        stage.process(items, ctx)
        cached_layout_40 = stage._layout.copy()

        # With narrower viewport, items wrap to more lines
        # So the cumulative heights should be different
        assert cached_layout_40 != cached_layout_80, (
            "Layout should recompute when viewport_width changes"
        )

    def test_layout_recomputes_on_height_change(self):
        """Test that visible items change when viewport_height changes."""
        stage = ViewportFilterStage()
        items = [SourceItem(f"Headline {i}", "test", str(i)) for i in range(100)]

        ctx = PipelineContext()
        ctx.set("camera_y", 0)

        # Small viewport - fewer items visible
        ctx.params = MockParams(viewport_width=80, viewport_height=12)
        result_small = stage.process(items, ctx)

        # Larger viewport - more items visible
        ctx.params.viewport_height = 48
        result_large = stage.process(items, ctx)

        # With larger viewport, more items should be visible
        assert len(result_large) >= len(result_small)

    def test_camera_y_propagates_to_filter(self):
        """Test that camera_y is read from context."""
        stage = ViewportFilterStage()
        items = [SourceItem(f"Headline {i}", "test", str(i)) for i in range(100)]

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

        # Camera at y=0
        ctx.set("camera_y", 0)
        result_at_0 = stage.process(items, ctx)

        # Camera at y=100
        ctx.set("camera_y", 100)
        result_at_100 = stage.process(items, ctx)

        # With different camera positions, different items should be visible
        # (unless items are very short)
        first_item_at_0 = result_at_0[0].content if result_at_0 else None
        first_item_at_100 = result_at_100[0].content if result_at_100 else None

        # The items at different positions should be different
        assert first_item_at_0 != first_item_at_100 or first_item_at_0 is None

    def test_resize_handles_edge_case_small_width(self):
        """Test that very narrow viewport doesn't crash."""
        stage = ViewportFilterStage()
        items = [SourceItem("Short", "test", "1")]

        ctx = PipelineContext()
        ctx.params = MockParams(viewport_width=10, viewport_height=5)
        ctx.set("camera_y", 0)

        # Should not crash with very narrow viewport
        result = stage.process(items, ctx)
        assert result is not None
        assert len(result) > 0
