"""Tests for text extraction from various output formats."""

from terminaluse.lib.utils.output import extract_text


class TestExtractText:
    """Tests for extract_text singledispatch function."""

    def test_plain_string(self) -> None:
        """Plain string is returned as-is."""
        assert extract_text("hello world") == "hello world"

    def test_empty_string(self) -> None:
        """Empty string is returned as-is."""
        assert extract_text("") == ""

    def test_list_of_text_parts(self) -> None:
        """List of {'type': 'text', 'text': '...'} parts extracts text."""
        output = [
            {"type": "text", "text": "Hello"},
            {"type": "text", "text": "World"},
        ]
        assert extract_text(output) == "Hello\nWorld"

    def test_list_with_non_text_parts(self) -> None:
        """Non-text parts in list are skipped."""
        output = [
            {"type": "text", "text": "Hello"},
            {"type": "image", "url": "http://example.com/img.png"},
            {"type": "text", "text": "World"},
        ]
        assert extract_text(output) == "Hello\nWorld"

    def test_single_dict_with_text(self) -> None:
        """Single dict with 'text' key extracts text."""
        output = {"type": "text", "text": "Hello"}
        assert extract_text(output) == "Hello"

    def test_dict_without_text_key(self) -> None:
        """Dict without 'text' key falls back to str()."""
        output = {"unknown": "format"}
        assert extract_text(output) == "{'unknown': 'format'}"

    def test_list_of_strings(self) -> None:
        """List of plain strings is joined with newlines."""
        output = ["line1", "line2", "line3"]
        assert extract_text(output) == "line1\nline2\nline3"

    def test_mixed_list(self) -> None:
        """Mixed list of dicts and strings extracts both."""
        output = [
            {"type": "text", "text": "From dict"},
            "Plain string",
        ]
        assert extract_text(output) == "From dict\nPlain string"

    def test_empty_list(self) -> None:
        """Empty list returns empty string."""
        assert extract_text([]) == ""

    def test_fallback_to_str_for_int(self) -> None:
        """Unknown types fall back to str()."""
        assert extract_text(123) == "123"

    def test_fallback_to_str_for_float(self) -> None:
        """Float falls back to str()."""
        assert extract_text(3.14) == "3.14"

    def test_none_fallback(self) -> None:
        """None falls back to str()."""
        assert extract_text(None) == "None"

    def test_nested_text_in_list(self) -> None:
        """Handles nested text values that need string conversion."""
        output = [
            {"type": "text", "text": 123},  # text is int
        ]
        assert extract_text(output) == "123"

    def test_real_world_agent_output(self) -> None:
        """Tests with real-world agent output format."""
        output = [
            {
                "type": "text",
                "text": "Hey there! Welcome to Claude Code!",
            },
            {
                "type": "text",
                "text": "agentId: a280d3b (for resuming)\n<usage>total_tokens: 11428</usage>",
            },
        ]
        result = extract_text(output)
        assert "Hey there!" in result
        assert "agentId:" in result  # Filtering happens in FilterPipeline, not extraction

    # =========================================================================
    # Codex output format tests
    # =========================================================================

    def test_codex_command_execution_output(self) -> None:
        """Codex command_execution tool-result extracts aggregated_output."""
        output = {
            "command": "/usr/bin/bash -lc 'cat /workspace/bar/baz.txt'",
            "status": "completed",
            "aggregated_output": "HELLO CODEX!!!",
            "exit_code": 0,
        }
        assert extract_text(output) == "HELLO CODEX!!!"

    def test_codex_command_execution_empty_output(self) -> None:
        """Codex command with empty aggregated_output."""
        output = {
            "command": "/usr/bin/bash -lc 'true'",
            "status": "completed",
            "aggregated_output": "",
            "exit_code": 0,
        }
        assert extract_text(output) == ""

    def test_codex_mcp_tool_call_string_result(self) -> None:
        """Codex mcp_tool_call with string result."""
        output = {
            "result": "Tool execution successful",
        }
        assert extract_text(output) == "Tool execution successful"

    def test_codex_mcp_tool_call_nested_result(self) -> None:
        """Codex mcp_tool_call with nested dict result containing text."""
        output = {
            "result": {"type": "text", "text": "Nested result text"},
        }
        assert extract_text(output) == "Nested result text"

    def test_generic_output_field_string(self) -> None:
        """Dict with generic 'output' field containing string."""
        output = {
            "output": "Some output text",
        }
        assert extract_text(output) == "Some output text"

    def test_generic_output_field_nested(self) -> None:
        """Dict with generic 'output' field containing nested dict."""
        output = {
            "output": {"text": "Nested output"},
        }
        assert extract_text(output) == "Nested output"

    def test_text_key_takes_priority(self) -> None:
        """'text' key takes priority over other fields."""
        output = {
            "text": "Primary text",
            "aggregated_output": "Should not be used",
            "result": "Should not be used",
        }
        assert extract_text(output) == "Primary text"

    def test_aggregated_output_over_result(self) -> None:
        """'aggregated_output' takes priority over 'result'."""
        output = {
            "aggregated_output": "Command output",
            "result": "Should not be used",
        }
        assert extract_text(output) == "Command output"
