"""Unit tests for progress tracker service."""

from uuid import uuid4

import pytest

from src.services.progress_tracker import ProcessingStage, ProgressTracker


@pytest.fixture
async def progress_tracker():
    """Create a progress tracker instance for testing."""
    tracker = ProgressTracker("redis://localhost:6379")
    yield tracker
    await tracker.close()


class TestSetProgress:
    """Test set_progress functionality."""

    @pytest.mark.asyncio
    async def test_set_progress_with_enum(self, progress_tracker: ProgressTracker):
        """Should set progress using ProcessingStage enum."""
        document_id = uuid4()

        await progress_tracker.set_progress(
            document_id, ProcessingStage.EXTRACTING_TABLES, 5, 10
        )

        # Verify progress was set
        progress = await progress_tracker.get_progress(document_id)
        assert progress is not None
        assert progress["stage"] == "extracting_tables"
        assert progress["sheets_processed"] == 5
        assert progress["sheets_total"] == 10

        # Cleanup
        await progress_tracker.clear_progress(document_id)

    @pytest.mark.asyncio
    async def test_set_progress_with_string(self, progress_tracker: ProgressTracker):
        """Should set progress using string stage."""
        document_id = uuid4()

        await progress_tracker.set_progress(
            document_id, "validating", 0, 0
        )

        progress = await progress_tracker.get_progress(document_id)
        assert progress is not None
        assert progress["stage"] == "validating"
        assert progress["sheets_processed"] == 0
        assert progress["sheets_total"] == 0

        # Cleanup
        await progress_tracker.clear_progress(document_id)

    @pytest.mark.asyncio
    async def test_set_progress_updates_existing(self, progress_tracker: ProgressTracker):
        """Should update progress for the same document."""
        document_id = uuid4()

        # Set initial progress
        await progress_tracker.set_progress(
            document_id, ProcessingStage.VALIDATING, 0, 10
        )

        # Update progress
        await progress_tracker.set_progress(
            document_id, ProcessingStage.EXTRACTING_TABLES, 3, 10
        )

        # Should return latest progress
        progress = await progress_tracker.get_progress(document_id)
        assert progress["stage"] == "extracting_tables"
        assert progress["sheets_processed"] == 3

        # Cleanup
        await progress_tracker.clear_progress(document_id)

    @pytest.mark.asyncio
    async def test_set_progress_multiple_documents(self, progress_tracker: ProgressTracker):
        """Should track progress for multiple documents independently."""
        doc1 = uuid4()
        doc2 = uuid4()

        await progress_tracker.set_progress(
            doc1, ProcessingStage.VALIDATING, 0, 5
        )
        await progress_tracker.set_progress(
            doc2, ProcessingStage.EXTRACTING_TABLES, 3, 8
        )

        progress1 = await progress_tracker.get_progress(doc1)
        progress2 = await progress_tracker.get_progress(doc2)

        assert progress1["stage"] == "validating"
        assert progress1["sheets_total"] == 5

        assert progress2["stage"] == "extracting_tables"
        assert progress2["sheets_total"] == 8

        # Cleanup
        await progress_tracker.clear_progress(doc1)
        await progress_tracker.clear_progress(doc2)


class TestGetProgress:
    """Test get_progress functionality."""

    @pytest.mark.asyncio
    async def test_get_progress_returns_none_for_nonexistent(
        self, progress_tracker: ProgressTracker
    ):
        """Should return None for nonexistent progress."""
        nonexistent_id = uuid4()
        progress = await progress_tracker.get_progress(nonexistent_id)
        assert progress is None

    @pytest.mark.asyncio
    async def test_get_progress_returns_dict_with_correct_types(
        self, progress_tracker: ProgressTracker
    ):
        """Should return dict with stage as string and counts as int."""
        document_id = uuid4()

        await progress_tracker.set_progress(
            document_id, ProcessingStage.RESOLVING_SYMBOLS, 7, 10
        )

        progress = await progress_tracker.get_progress(document_id)

        assert isinstance(progress, dict)
        assert isinstance(progress["stage"], str)
        assert isinstance(progress["sheets_processed"], int)
        assert isinstance(progress["sheets_total"], int)

        # Cleanup
        await progress_tracker.clear_progress(document_id)


class TestClearProgress:
    """Test clear_progress functionality."""

    @pytest.mark.asyncio
    async def test_clear_progress_removes_data(self, progress_tracker: ProgressTracker):
        """Should remove progress data from Redis."""
        document_id = uuid4()

        # Set progress
        await progress_tracker.set_progress(
            document_id, ProcessingStage.INDEXING, 10, 10
        )

        # Verify it exists
        progress = await progress_tracker.get_progress(document_id)
        assert progress is not None

        # Clear progress
        await progress_tracker.clear_progress(document_id)

        # Verify it's gone
        progress_after_clear = await progress_tracker.get_progress(document_id)
        assert progress_after_clear is None

    @pytest.mark.asyncio
    async def test_clear_progress_idempotent(self, progress_tracker: ProgressTracker):
        """Should not error when clearing nonexistent progress."""
        nonexistent_id = uuid4()

        # Should not raise
        await progress_tracker.clear_progress(nonexistent_id)


class TestProcessingStageEnum:
    """Test ProcessingStage enum."""

    def test_all_stages_defined(self):
        """Should have all required processing stages."""
        expected_stages = [
            "validating",
            "extracting_tables",
            "resolving_symbols",
            "indexing",
        ]

        actual_stages = [stage.value for stage in ProcessingStage]

        assert set(actual_stages) == set(expected_stages)

    def test_enum_values_are_strings(self):
        """Should have string values for all stages."""
        for stage in ProcessingStage:
            assert isinstance(stage.value, str)

    def test_enum_can_be_used_as_string(self):
        """Should be usable as string in comparisons."""
        stage = ProcessingStage.EXTRACTING_TABLES
        assert stage == "extracting_tables"
        assert stage.value == "extracting_tables"


class TestProgressTrackerGracefulDegradation:
    """Test graceful degradation when Redis is unavailable."""

    @pytest.mark.asyncio
    async def test_set_progress_handles_redis_errors_gracefully(self):
        """Should not raise when Redis is unavailable."""
        # Create tracker with invalid Redis URL
        tracker = ProgressTracker("redis://invalid-host:9999")
        document_id = uuid4()

        # Should not raise - just log error
        try:
            await tracker.set_progress(
                document_id, ProcessingStage.VALIDATING, 0, 0
            )
        finally:
            await tracker.close()

    @pytest.mark.asyncio
    async def test_get_progress_returns_none_on_redis_errors(self):
        """Should return None when Redis is unavailable."""
        # Create tracker with invalid Redis URL
        tracker = ProgressTracker("redis://invalid-host:9999")
        document_id = uuid4()

        try:
            progress = await tracker.get_progress(document_id)
            assert progress is None
        finally:
            await tracker.close()

    @pytest.mark.asyncio
    async def test_clear_progress_handles_redis_errors_gracefully(self):
        """Should not raise when Redis is unavailable."""
        # Create tracker with invalid Redis URL
        tracker = ProgressTracker("redis://invalid-host:9999")
        document_id = uuid4()

        # Should not raise - just log error
        try:
            await tracker.clear_progress(document_id)
        finally:
            await tracker.close()
