"""
Unit tests for validator helper functions.

Tests for _strip_ansi_codes() and _parse_claude_doctor_output() functions
that process terminal output from claude doctor.
"""

import pytest

from persuade.validators import _parse_claude_doctor_output, _strip_ansi_codes


class TestStripAnsiCodes:
    """Tests for _strip_ansi_codes() function."""

    def test_removes_color_codes(self) -> None:
        """Test that standard color codes are removed."""
        colored = "\x1b[32mgreen text\x1b[0m"
        assert _strip_ansi_codes(colored) == "green text"

    def test_removes_bold_and_reset(self) -> None:
        """Test that bold and reset codes are removed."""
        bold = "\x1b[1mbold\x1b[22m normal"
        assert _strip_ansi_codes(bold) == "bold normal"

    def test_removes_cursor_positioning(self) -> None:
        """Test that cursor positioning codes are removed."""
        cursor = "\x1b[2K\x1b[1A\x1b[Gtext"
        assert _strip_ansi_codes(cursor) == "text"

    def test_removes_private_csi_sequences(self) -> None:
        """Test that private CSI sequences (cursor visibility, etc.) are removed."""
        private = "\x1b[?2026htext\x1b[?2026l"
        assert _strip_ansi_codes(private) == "text"

    def test_removes_osc_sequences(self) -> None:
        """Test that OSC sequences (window titles, hyperlinks) are removed."""
        osc = "\x1b]0;Window Title\x07text"
        assert _strip_ansi_codes(osc) == "text"

    def test_handles_multiple_codes(self) -> None:
        """Test stripping multiple different ANSI codes."""
        complex_text = "\x1b[?2026h\x1b[32m\x1b[1mDiagnostics\x1b[22m\x1b[0m\x1b[?2026l"
        assert _strip_ansi_codes(complex_text) == "Diagnostics"

    def test_handles_empty_string(self) -> None:
        """Test that empty string returns empty string."""
        assert _strip_ansi_codes("") == ""

    def test_handles_no_ansi_codes(self) -> None:
        """Test that text without ANSI codes is unchanged."""
        plain = "plain text without codes"
        assert _strip_ansi_codes(plain) == plain

    def test_real_claude_doctor_output_sample(self) -> None:
        """Test with actual claude doctor output format."""
        # Sample from real claude doctor output
        sample = (
            "\x1b[?2026h\x1b[25l\n"
            " \x1b[38;2;153;153;153mChecking installation status…\x1b[39m\n"
            "\x1b[?2026l\x1b[25l\x1b[2004h\x1b[?2026h"
            "\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[G\n"
            " \x1b[1mDiagnostics\x1b[22m\n"
            " └ Currently running: native (2.0.67)\n"
        )
        result = _strip_ansi_codes(sample)
        assert "Diagnostics" in result
        assert "Currently running" in result
        assert "\x1b" not in result  # No escape codes remain

    def test_handles_malformed_ansi_sequences(self) -> None:
        """Test that partial/malformed ANSI sequences don't cause issues.

        Malformed sequences (incomplete escapes, wrong terminators) should
        be handled gracefully - either stripped or left as-is without crashing.
        """
        # Incomplete CSI (missing terminator letter)
        incomplete = "text\x1b[32"
        result = _strip_ansi_codes(incomplete)
        assert "text" in result  # Text preserved

        # Bare escape without sequence
        bare_escape = "before\x1bafter"
        result = _strip_ansi_codes(bare_escape)
        assert "before" in result
        assert "after" in result

        # Multiple escapes in a row
        double_escape = "\x1b\x1b[32mtext\x1b[0m"
        result = _strip_ansi_codes(double_escape)
        assert "text" in result

    def test_handles_long_input(self) -> None:
        """Test that very long strings are processed efficiently.

        Verifies the regex doesn't have catastrophic backtracking issues.
        """
        # 100KB of text with interspersed ANSI codes
        base = "line with \x1b[32mcolor\x1b[0m text\n"
        long_input = base * 3000  # ~100KB
        result = _strip_ansi_codes(long_input)
        assert len(result) > 0
        assert "\x1b" not in result
        # Verify content preserved
        assert "line with" in result
        assert "color" in result


class TestParseClaudeDoctorOutput:
    """Tests for _parse_claude_doctor_output() function."""

    def test_extracts_diagnostic_lines(self) -> None:
        """Test extraction of lines starting with └."""
        output = """
 Diagnostics
 └ Currently running: native (2.0.67)
 └ Path: /Users/test/.local/bin/claude
 └ Auto-updates: enabled
 Press Enter to continue...
"""
        details = _parse_claude_doctor_output(output)
        assert len(details) == 3
        assert "Currently running: native (2.0.67)" in details[0]
        assert "Path:" in details[1]
        assert "Auto-updates: enabled" in details[2]

    def test_handles_empty_string(self) -> None:
        """Test that empty string returns empty list."""
        assert _parse_claude_doctor_output("") == []

    def test_handles_no_diagnostic_lines(self) -> None:
        """Test output without any └ characters."""
        output = "Some text\nwithout diagnostics\n"
        assert _parse_claude_doctor_output(output) == []

    def test_strips_ansi_codes_before_parsing(self) -> None:
        """Test that ANSI codes are stripped before parsing."""
        output = "\x1b[32m └ \x1b[1mColored diagnostic\x1b[0m"
        details = _parse_claude_doctor_output(output)
        assert len(details) == 1
        assert "\x1b" not in details[0]
        assert "Colored diagnostic" in details[0]

    def test_strips_leading_whitespace(self) -> None:
        """Test that leading whitespace is stripped from lines."""
        output = "   └ Indented diagnostic"
        details = _parse_claude_doctor_output(output)
        assert len(details) == 1
        assert details[0].startswith("└")

    def test_full_claude_doctor_output(self) -> None:
        """Test with full realistic claude doctor output."""
        output = """spawn claude doctor
\x1b[?2026h\x1b[25l
 \x1b[38;2;153;153;153mChecking installation status…\x1b[39m
\x1b[?2026l\x1b[25l\x1b[2004h\x1b[?2026h\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[G
 \x1b[1mDiagnostics\x1b[22m
 └ Currently running: native (2.0.67)
 └ Path: /Users/stavxyz/.local/bin/claude
 └ Invoked: /Users/stavxyz/.local/share/claude/versions/2.0.67
 └ Config install method: native
 └ Auto-updates: enabled
 └ Search: OK (bundled)

 \x1b[38;2;177;185;249mPress \x1b[1mEnter\x1b[22m to continue…\x1b[39m
\x1b[?2026l"""
        details = _parse_claude_doctor_output(output)
        assert len(details) == 6
        assert "Currently running" in details[0]
        assert "Path" in details[1]
        assert "Invoked" in details[2]
        assert "Config install method" in details[3]
        assert "Auto-updates" in details[4]
        assert "Search: OK" in details[5]

    def test_multiline_with_various_content(self) -> None:
        """Test parsing output with mixed content types."""
        output = """
Header text
 └ First item
Some other text
 └ Second item
Footer
 └ Third item
"""
        details = _parse_claude_doctor_output(output)
        assert len(details) == 3
        assert "First item" in details[0]
        assert "Second item" in details[1]
        assert "Third item" in details[2]


# Allow running tests directly
if __name__ == "__main__":
    pytest.main([__file__, "-v"])
