"""
Tests for engine/data_sources/sources.py - data source implementations.

Tests HeadlinesDataSource, PoetryDataSource, EmptyDataSource, and the
base DataSource class functionality.
"""

from unittest.mock import patch

import pytest

from engine.data_sources.sources import (
    EmptyDataSource,
    HeadlinesDataSource,
    PoetryDataSource,
    SourceItem,
)


class TestSourceItem:
    """Test SourceItem dataclass."""

    def test_source_item_creation(self):
        """SourceItem can be created with required fields."""
        item = SourceItem(
            content="Test headline",
            source="test_source",
            timestamp="2024-01-01",
        )
        assert item.content == "Test headline"
        assert item.source == "test_source"
        assert item.timestamp == "2024-01-01"
        assert item.metadata is None

    def test_source_item_with_metadata(self):
        """SourceItem can include optional metadata."""
        metadata = {"author": "John", "category": "tech"}
        item = SourceItem(
            content="Test",
            source="test",
            timestamp="2024-01-01",
            metadata=metadata,
        )
        assert item.metadata == metadata


class TestEmptyDataSource:
    """Test EmptyDataSource."""

    def test_empty_source_name(self):
        """EmptyDataSource has correct name."""
        source = EmptyDataSource()
        assert source.name == "empty"

    def test_empty_source_is_not_dynamic(self):
        """EmptyDataSource is static, not dynamic."""
        source = EmptyDataSource()
        assert source.is_dynamic is False

    def test_empty_source_fetch_returns_blank_content(self):
        """EmptyDataSource.fetch() returns blank lines."""
        source = EmptyDataSource(width=80, height=24)
        items = source.fetch()

        assert len(items) == 1
        assert isinstance(items[0], SourceItem)
        assert items[0].source == "empty"
        # Content should be 24 lines of 80 spaces
        lines = items[0].content.split("\n")
        assert len(lines) == 24
        assert all(len(line) == 80 for line in lines)

    def test_empty_source_get_items_caches_result(self):
        """EmptyDataSource.get_items() caches the result."""
        source = EmptyDataSource()
        items1 = source.get_items()
        items2 = source.get_items()
        # Should return same cached items (same object reference)
        assert items1 is items2


class TestHeadlinesDataSource:
    """Test HeadlinesDataSource."""

    def test_headlines_source_name(self):
        """HeadlinesDataSource has correct name."""
        source = HeadlinesDataSource()
        assert source.name == "headlines"

    def test_headlines_source_is_static(self):
        """HeadlinesDataSource is static."""
        source = HeadlinesDataSource()
        assert source.is_dynamic is False

    def test_headlines_fetch_returns_source_items(self):
        """HeadlinesDataSource.fetch() returns SourceItem list."""
        mock_items = [
            ("Test Article 1", "source1", "10:30"),
            ("Test Article 2", "source2", "11:45"),
        ]
        with patch("engine.fetch.fetch_all") as mock_fetch_all:
            mock_fetch_all.return_value = (mock_items, 2, 0)

            source = HeadlinesDataSource()
            items = source.fetch()

            assert len(items) == 2
            assert all(isinstance(item, SourceItem) for item in items)
            assert items[0].content == "Test Article 1"
            assert items[0].source == "source1"
            assert items[0].timestamp == "10:30"

    def test_headlines_fetch_with_empty_feed(self):
        """HeadlinesDataSource handles empty feeds gracefully."""
        with patch("engine.fetch.fetch_all") as mock_fetch_all:
            mock_fetch_all.return_value = ([], 0, 1)

            source = HeadlinesDataSource()
            items = source.fetch()

            # Should return empty list
            assert isinstance(items, list)
            assert len(items) == 0

    def test_headlines_get_items_caches_result(self):
        """HeadlinesDataSource.get_items() caches the result."""
        mock_items = [("Test Article", "source", "12:00")]
        with patch("engine.fetch.fetch_all") as mock_fetch_all:
            mock_fetch_all.return_value = (mock_items, 1, 0)

            source = HeadlinesDataSource()
            items1 = source.get_items()
            items2 = source.get_items()

            # Should only call fetch once (cached)
            assert mock_fetch_all.call_count == 1
            assert items1 is items2

    def test_headlines_refresh_clears_cache(self):
        """HeadlinesDataSource.refresh() clears cache and refetches."""
        mock_items = [("Test Article", "source", "12:00")]
        with patch("engine.fetch.fetch_all") as mock_fetch_all:
            mock_fetch_all.return_value = (mock_items, 1, 0)

            source = HeadlinesDataSource()
            source.get_items()
            source.refresh()
            source.get_items()

            # Should call fetch twice (once for initial, once for refresh)
            assert mock_fetch_all.call_count == 2


class TestPoetryDataSource:
    """Test PoetryDataSource."""

    def test_poetry_source_name(self):
        """PoetryDataSource has correct name."""
        source = PoetryDataSource()
        assert source.name == "poetry"

    def test_poetry_source_is_static(self):
        """PoetryDataSource is static."""
        source = PoetryDataSource()
        assert source.is_dynamic is False

    def test_poetry_fetch_returns_source_items(self):
        """PoetryDataSource.fetch() returns SourceItem list."""
        mock_items = [
            ("Poetry line 1", "Poetry Source 1", ""),
            ("Poetry line 2", "Poetry Source 2", ""),
        ]
        with patch("engine.fetch.fetch_poetry") as mock_fetch_poetry:
            mock_fetch_poetry.return_value = (mock_items, 2, 0)

            source = PoetryDataSource()
            items = source.fetch()

            assert len(items) == 2
            assert all(isinstance(item, SourceItem) for item in items)
            assert items[0].content == "Poetry line 1"
            assert items[0].source == "Poetry Source 1"

    def test_poetry_get_items_caches_result(self):
        """PoetryDataSource.get_items() caches result."""
        mock_items = [("Poetry line", "Poetry Source", "")]
        with patch("engine.fetch.fetch_poetry") as mock_fetch_poetry:
            mock_fetch_poetry.return_value = (mock_items, 1, 0)

            source = PoetryDataSource()
            items1 = source.get_items()
            items2 = source.get_items()

            # Should only fetch once (cached)
            assert mock_fetch_poetry.call_count == 1
            assert items1 is items2


class TestDataSourceInterface:
    """Test DataSource base class interface."""

    def test_data_source_stream_not_implemented(self):
        """DataSource.stream() raises NotImplementedError."""
        source = EmptyDataSource()
        with pytest.raises(NotImplementedError):
            source.stream()

    def test_data_source_is_dynamic_defaults_false(self):
        """DataSource.is_dynamic defaults to False."""
        source = EmptyDataSource()
        assert source.is_dynamic is False

    def test_data_source_refresh_updates_cache(self):
        """DataSource.refresh() updates internal cache."""
        source = EmptyDataSource()
        source.get_items()
        items_refreshed = source.refresh()

        # refresh() should return new items
        assert isinstance(items_refreshed, list)
