"""Tests for StreamMarkdownRenderer lifecycle in talk_markdown.py.

Spec: streaming-markdown.verify-streaming-engine-fix
Tests renderer lifecycle: start()/feed()/finish() happy path, empty feed, and finish without start.
"""

from __future__ import annotations

import io
from unittest.mock import MagicMock

import pytest
from rich.console import Console

from anima.shell.cli_commands.talk_markdown import StreamMarkdownRenderer


class TestStreamMarkdownRendererLifecycle:
    """Tests for StreamMarkdownRenderer start/feed/finish lifecycle."""

    def _make_renderer(self) -> StreamMarkdownRenderer:
        """Create a StreamMarkdownRenderer with a captured console."""
        console = Console(file=io.StringIO(), force_terminal=True)
        renderer = StreamMarkdownRenderer(console)
        return renderer

    def test_start_feed_finish_happy_path(self) -> None:
        """Test complete lifecycle: start -> feed -> finish.

        This is the happy path where:
        1. start() transitions to STREAMING
        2. feed() processes chunks, printing completed blocks
        3. finish() flushes remaining buffer
        """
        renderer = self._make_renderer()

        # start() - should transition to STREAMING
        renderer.start()
        assert renderer._state == StreamMarkdownRenderer._STREAMING

        # feed() - "# Hello\n\n" is a completed block → printed immediately.
        # "This is a paragraph." remains in buffer.
        renderer.feed("# Hello\n\nThis is a paragraph.")

        # feed() - "This is a paragraph.\n\n" completes → printed.
        # "Another paragraph." remains in buffer.
        renderer.feed("\n\nAnother paragraph.")

        # finish() - should flush remaining buffer and transition to FINISHED
        renderer.finish()
        assert renderer._state == StreamMarkdownRenderer._FINISHED

    def test_finish_prints_remaining_buffer(self) -> None:
        """Remaining buffer content is printed when finish() is called."""
        renderer = self._make_renderer()
        renderer._console.print = MagicMock()
        renderer.start()
        renderer.feed("single paragraph without block boundary")

        renderer.finish()

        # The remaining buffer should be printed once via console.print
        assert renderer._console.print.call_count == 1

    def test_empty_feed_is_noop(self) -> None:
        """Test that feed("") is a no-op.

        Empty string chunks should not affect the buffer.
        """
        renderer = self._make_renderer()
        renderer.start()

        # Call feed with empty string
        renderer.feed("")

        # Buffer should remain empty
        assert renderer._buffer == ""

        renderer.finish()

    def test_finish_without_start_raises(self) -> None:
        """Test that finish() without prior start() raises RuntimeError.

        Calling finish() before start() is invalid because state is NOT_STARTED.
        """
        renderer = self._make_renderer()

        # finish() without start() should raise RuntimeError
        with pytest.raises(RuntimeError, match="Invalid state for finish"):
            renderer.finish()

        # State should remain NOT_STARTED
        assert renderer._state == StreamMarkdownRenderer._NOT_STARTED

    def test_double_start_raises(self) -> None:
        """Test that calling start() twice raises RuntimeError.

        Starting an already streaming renderer is invalid state transition.
        """
        renderer = self._make_renderer()
        renderer.start()

        # Second start() should raise
        with pytest.raises(RuntimeError, match="Invalid state transition"):
            renderer.start()

        renderer.finish()

    def test_feed_before_start_raises(self) -> None:
        """Test that feed() without prior start() raises RuntimeError.

        Feeding before starting the renderer is invalid.
        """
        renderer = self._make_renderer()

        # feed() without start() should raise
        with pytest.raises(RuntimeError, match="Invalid state for feed"):
            renderer.feed("some content")

    def test_double_finish_raises(self) -> None:
        """Test that calling finish() twice raises RuntimeError.

        Finishing an already finished renderer is invalid state.
        """
        renderer = self._make_renderer()
        renderer.start()
        renderer.finish()

        # Second finish() should raise
        with pytest.raises(RuntimeError, match="Invalid state for finish"):
            renderer.finish()
