"""Unit tests for OutputFormatter."""

from __future__ import annotations

import json
import os
from io import StringIO
from typing import TYPE_CHECKING
from unittest.mock import patch

import pytest
from rich.console import Console
from unittest.mock import MagicMock

from teams_phone.infrastructure.output_formatter import (
    PLACEHOLDER_ASCII,
    PLACEHOLDER_UNICODE,
    OutputFormatter,
    _can_encode_unicode,
)

if TYPE_CHECKING:
    pass


class TestOutputFormatterInit:
    """Tests for OutputFormatter initialization."""

    @pytest.mark.unit
    def test_default_initialization(self) -> None:
        """OutputFormatter creates Console with default settings."""
        formatter = OutputFormatter()
        assert formatter.json_mode is False
        assert formatter.console is not None

    @pytest.mark.unit
    def test_json_mode_enabled(self) -> None:
        """OutputFormatter sets json_mode when enabled."""
        formatter = OutputFormatter(json_mode=True)
        assert formatter.json_mode is True

    @pytest.mark.unit
    def test_no_color_flag(self) -> None:
        """OutputFormatter respects no_color flag."""
        formatter = OutputFormatter(no_color=True)
        assert formatter.no_color is True

    @pytest.mark.unit
    def test_no_color_env_var(self) -> None:
        """OutputFormatter respects NO_COLOR environment variable."""
        with patch.dict(os.environ, {"NO_COLOR": "1"}):
            formatter = OutputFormatter()
            assert formatter.no_color is True

    @pytest.mark.unit
    def test_no_color_env_var_empty_value(self) -> None:
        """NO_COLOR env var with empty value still disables color."""
        with patch.dict(os.environ, {"NO_COLOR": ""}):
            formatter = OutputFormatter()
            assert formatter.no_color is True

    @pytest.mark.unit
    def test_no_color_not_set(self) -> None:
        """OutputFormatter enables color when NO_COLOR is not set."""
        with patch.dict(os.environ, {}, clear=True):
            # Remove NO_COLOR if it exists
            os.environ.pop("NO_COLOR", None)
            formatter = OutputFormatter()
            assert formatter.no_color is False

    @pytest.mark.unit
    def test_custom_console(self) -> None:
        """OutputFormatter uses provided Console instance."""
        custom_console = Console(file=StringIO())
        formatter = OutputFormatter(console=custom_console)
        assert formatter.console is custom_console


class TestTableOutput:
    """Tests for table() method."""

    @pytest.mark.unit
    def test_table_renders_data(self) -> None:
        """Table renders data with columns."""
        output = StringIO()
        console = Console(file=output, force_terminal=True, width=80)
        formatter = OutputFormatter(console=console)

        data = [
            {"name": "Alice", "age": "30"},
            {"name": "Bob", "age": "25"},
        ]
        formatter.table(data, columns=["name", "age"])

        result = output.getvalue()
        assert "Alice" in result
        assert "Bob" in result
        assert "30" in result
        assert "25" in result

    @pytest.mark.unit
    def test_table_with_title(self) -> None:
        """Table includes title when provided."""
        output = StringIO()
        console = Console(file=output, force_terminal=True, width=80)
        formatter = OutputFormatter(console=console)

        data = [{"name": "Test"}]
        formatter.table(data, columns=["name"], title="My Table")

        result = output.getvalue()
        assert "My Table" in result

    @pytest.mark.unit
    def test_table_missing_column_data(self) -> None:
        """Table handles missing column data gracefully."""
        output = StringIO()
        console = Console(file=output, force_terminal=True, width=80)
        formatter = OutputFormatter(console=console)

        data = [{"name": "Alice"}]  # Missing 'age' column
        formatter.table(data, columns=["name", "age"])

        result = output.getvalue()
        assert "Alice" in result

    @pytest.mark.unit
    def test_table_in_json_mode_outputs_json(self) -> None:
        """Table outputs JSON when json_mode is enabled."""
        formatter = OutputFormatter(json_mode=True)

        data = [{"name": "Alice", "age": "30"}]
        with patch("sys.stdout", new_callable=StringIO) as mock_stdout:
            formatter.table(data, columns=["name", "age"])
            result = mock_stdout.getvalue()

        parsed = json.loads(result)
        assert parsed == data


class TestJsonOutput:
    """Tests for json() method."""

    @pytest.mark.unit
    def test_json_outputs_valid_json(self) -> None:
        """json() outputs valid JSON."""
        formatter = OutputFormatter()

        data = {"key": "value", "number": 42}
        with patch("sys.stdout", new_callable=StringIO) as mock_stdout:
            formatter.json(data)
            result = mock_stdout.getvalue()

        parsed = json.loads(result)
        assert parsed == data

    @pytest.mark.unit
    def test_json_outputs_list(self) -> None:
        """json() outputs list data correctly."""
        formatter = OutputFormatter()

        data = [{"id": 1}, {"id": 2}]
        with patch("sys.stdout", new_callable=StringIO) as mock_stdout:
            formatter.json(data)
            result = mock_stdout.getvalue()

        parsed = json.loads(result)
        assert parsed == data

    @pytest.mark.unit
    def test_json_is_indented(self) -> None:
        """json() output is indented for readability."""
        formatter = OutputFormatter()

        data = {"key": "value"}
        with patch("sys.stdout", new_callable=StringIO) as mock_stdout:
            formatter.json(data)
            result = mock_stdout.getvalue()

        # Indented JSON has newlines
        assert "\n" in result
        assert "  " in result  # 2-space indent

    @pytest.mark.unit
    def test_json_no_ansi_codes(self) -> None:
        """json() output contains no ANSI escape codes."""
        formatter = OutputFormatter()

        data = {"key": "value"}
        with patch("sys.stdout", new_callable=StringIO) as mock_stdout:
            formatter.json(data)
            result = mock_stdout.getvalue()

        # ANSI codes start with escape character
        assert "\x1b" not in result


class TestSuccessMessage:
    """Tests for success() method."""

    @pytest.mark.unit
    def test_success_displays_message(self) -> None:
        """success() displays the message."""
        output = StringIO()
        console = Console(file=output, force_terminal=True)
        formatter = OutputFormatter(console=console)

        formatter.success("Operation completed")
        result = output.getvalue()

        assert "Operation completed" in result

    @pytest.mark.unit
    def test_success_includes_checkmark(self) -> None:
        """success() includes checkmark symbol."""
        output = StringIO()
        console = Console(file=output, force_terminal=True, no_color=True)
        formatter = OutputFormatter(console=console)

        formatter.success("Done")
        result = output.getvalue()

        assert "✓" in result

    @pytest.mark.unit
    def test_success_silent_in_json_mode(self) -> None:
        """success() outputs nothing in json_mode."""
        output = StringIO()
        console = Console(file=output, force_terminal=True)
        formatter = OutputFormatter(console=console, json_mode=True)

        formatter.success("Operation completed")
        result = output.getvalue()

        assert result == ""


class TestErrorMessage:
    """Tests for error() method."""

    @pytest.mark.unit
    def test_error_displays_message(self) -> None:
        """error() displays the error message."""
        output = StringIO()
        console = Console(file=output, force_terminal=True)
        formatter = OutputFormatter(console=console)

        formatter.error("Something went wrong")
        result = output.getvalue()

        assert "Something went wrong" in result
        assert "Error:" in result

    @pytest.mark.unit
    def test_error_with_remediation(self) -> None:
        """error() displays remediation guidance when provided."""
        output = StringIO()
        console = Console(file=output, force_terminal=True)
        formatter = OutputFormatter(console=console)

        formatter.error("Token expired", remediation="Run: teams-phone auth login")
        result = output.getvalue()

        assert "Token expired" in result
        assert "To fix:" in result
        assert "teams-phone auth login" in result

    @pytest.mark.unit
    def test_error_without_remediation(self) -> None:
        """error() works without remediation."""
        output = StringIO()
        console = Console(file=output, force_terminal=True)
        formatter = OutputFormatter(console=console)

        formatter.error("Unknown error")
        result = output.getvalue()

        assert "Unknown error" in result
        assert "To fix:" not in result

    @pytest.mark.unit
    def test_error_silent_in_json_mode(self) -> None:
        """error() outputs nothing in json_mode."""
        output = StringIO()
        console = Console(file=output, force_terminal=True)
        formatter = OutputFormatter(console=console, json_mode=True)

        formatter.error("Something went wrong")
        result = output.getvalue()

        assert result == ""


class TestWarningMessage:
    """Tests for warning() method."""

    @pytest.mark.unit
    def test_warning_displays_message(self) -> None:
        """warning() displays the warning message."""
        output = StringIO()
        console = Console(file=output, force_terminal=True)
        formatter = OutputFormatter(console=console)

        formatter.warning("This is deprecated")
        result = output.getvalue()

        assert "This is deprecated" in result
        assert "Warning:" in result

    @pytest.mark.unit
    def test_warning_silent_in_json_mode(self) -> None:
        """warning() outputs nothing in json_mode."""
        output = StringIO()
        console = Console(file=output, force_terminal=True)
        formatter = OutputFormatter(console=console, json_mode=True)

        formatter.warning("This is deprecated")
        result = output.getvalue()

        assert result == ""


class TestInfoMessage:
    """Tests for info() method."""

    @pytest.mark.unit
    def test_info_displays_message(self) -> None:
        """info() displays the message."""
        output = StringIO()
        console = Console(file=output, force_terminal=True)
        formatter = OutputFormatter(console=console)

        formatter.info("Processing items")
        result = output.getvalue()

        assert "Processing items" in result

    @pytest.mark.unit
    def test_info_silent_in_json_mode(self) -> None:
        """info() outputs nothing in json_mode."""
        output = StringIO()
        console = Console(file=output, force_terminal=True)
        formatter = OutputFormatter(console=console, json_mode=True)

        formatter.info("Processing items")
        result = output.getvalue()

        assert result == ""


class TestProgressBar:
    """Tests for progress() method."""

    @pytest.mark.unit
    def test_progress_returns_context_manager(self) -> None:
        """progress() returns a context manager."""
        output = StringIO()
        console = Console(file=output, force_terminal=True)
        formatter = OutputFormatter(console=console)

        with formatter.progress(total=10, description="Testing") as progress:
            assert progress is not None

    @pytest.mark.unit
    def test_progress_works_in_json_mode(self) -> None:
        """progress() returns context manager in json_mode (disabled)."""
        output = StringIO()
        console = Console(file=output, force_terminal=True)
        formatter = OutputFormatter(console=console, json_mode=True)

        with formatter.progress(total=10, description="Testing") as progress:
            assert progress is not None

    @pytest.mark.unit
    def test_progress_default_description(self) -> None:
        """progress() uses default description."""
        output = StringIO()
        console = Console(file=output, force_terminal=True)
        formatter = OutputFormatter(console=console)

        with formatter.progress(total=5):
            pass
        # Just verify no exception is raised


class TestColorControl:
    """Tests for color control behavior."""

    @pytest.mark.unit
    def test_no_color_removes_ansi_codes_from_success(self) -> None:
        """no_color=True removes ANSI codes from success message."""
        output = StringIO()
        console = Console(file=output, force_terminal=True, no_color=True)
        formatter = OutputFormatter(console=console, no_color=True)

        formatter.success("Done")
        result = output.getvalue()

        # No ANSI escape codes
        assert "\x1b" not in result

    @pytest.mark.unit
    def test_no_color_removes_ansi_codes_from_error(self) -> None:
        """no_color=True removes ANSI codes from error message."""
        output = StringIO()
        console = Console(file=output, force_terminal=True, no_color=True)
        formatter = OutputFormatter(console=console, no_color=True)

        formatter.error("Failed")
        result = output.getvalue()

        # No ANSI escape codes
        assert "\x1b" not in result

    @pytest.mark.unit
    def test_no_color_removes_ansi_codes_from_warning(self) -> None:
        """no_color=True removes ANSI codes from warning message."""
        output = StringIO()
        console = Console(file=output, force_terminal=True, no_color=True)
        formatter = OutputFormatter(console=console, no_color=True)

        formatter.warning("Deprecated")
        result = output.getvalue()

        # No ANSI escape codes
        assert "\x1b" not in result


class TestAsciiFallback:
    """Tests for ASCII fallback on Windows legacy console."""

    @pytest.mark.unit
    def test_can_encode_unicode_returns_true_for_utf8(self) -> None:
        """_can_encode_unicode returns True when stdout supports UTF-8."""
        # Mock stdout with UTF-8 encoding using MagicMock
        mock_stdout = MagicMock()
        mock_stdout.encoding = "utf-8"

        with patch("sys.stdout", mock_stdout):
            result = _can_encode_unicode()

        assert result is True

    @pytest.mark.unit
    def test_can_encode_unicode_returns_false_for_cp1252(self) -> None:
        """_can_encode_unicode returns False for Windows cp1252 encoding."""
        # Mock stdout with cp1252 encoding (Windows legacy)
        mock_stdout = MagicMock()
        mock_stdout.encoding = "cp1252"

        with patch("sys.stdout", mock_stdout):
            result = _can_encode_unicode()

        assert result is False

    @pytest.mark.unit
    def test_can_encode_unicode_handles_missing_encoding(self) -> None:
        """_can_encode_unicode handles stdout without encoding attribute."""
        # Mock stdout without encoding attribute - getattr returns None
        mock_stdout = MagicMock(spec=[])  # Empty spec means no encoding attr

        with patch("sys.stdout", mock_stdout):
            # Should default to utf-8 and return True
            result = _can_encode_unicode()

        assert result is True

    @pytest.mark.unit
    def test_use_ascii_flag_set_correctly(self) -> None:
        """OutputFormatter sets _use_ascii based on encoding detection."""
        mock_stdout = MagicMock()
        mock_stdout.encoding = "cp1252"

        with patch("sys.stdout", mock_stdout):
            formatter = OutputFormatter()

        assert formatter._use_ascii is True

    @pytest.mark.unit
    def test_use_ascii_flag_false_for_utf8(self) -> None:
        """OutputFormatter sets _use_ascii=False for UTF-8 terminals."""
        mock_stdout = MagicMock()
        mock_stdout.encoding = "utf-8"

        with patch("sys.stdout", mock_stdout):
            formatter = OutputFormatter()

        assert formatter._use_ascii is False

    @pytest.mark.unit
    def test_get_spinner_name_returns_line_for_ascii(self) -> None:
        """get_spinner_name returns 'line' for ASCII-only mode."""
        mock_stdout = MagicMock()
        mock_stdout.encoding = "cp1252"

        with patch("sys.stdout", mock_stdout):
            formatter = OutputFormatter()

        assert formatter.get_spinner_name() == "line"

    @pytest.mark.unit
    def test_get_spinner_name_returns_dots_for_unicode(self) -> None:
        """get_spinner_name returns 'dots' for Unicode-capable terminals."""
        mock_stdout = MagicMock()
        mock_stdout.encoding = "utf-8"

        with patch("sys.stdout", mock_stdout):
            formatter = OutputFormatter()

        assert formatter.get_spinner_name() == "dots"

    @pytest.mark.unit
    def test_placeholder_returns_hyphen_for_ascii(self) -> None:
        """placeholder returns '-' for ASCII-only mode."""
        mock_stdout = MagicMock()
        mock_stdout.encoding = "cp1252"

        with patch("sys.stdout", mock_stdout):
            formatter = OutputFormatter()

        assert formatter.placeholder == PLACEHOLDER_ASCII
        assert formatter.placeholder == "-"

    @pytest.mark.unit
    def test_placeholder_returns_emdash_for_unicode(self) -> None:
        """placeholder returns em-dash for Unicode-capable terminals."""
        mock_stdout = MagicMock()
        mock_stdout.encoding = "utf-8"

        with patch("sys.stdout", mock_stdout):
            formatter = OutputFormatter()

        assert formatter.placeholder == PLACEHOLDER_UNICODE
        assert formatter.placeholder == "—"

    @pytest.mark.unit
    def test_success_uses_ok_marker_for_ascii(self) -> None:
        """success() uses OK: marker for ASCII-only mode."""
        mock_stdout = MagicMock()
        mock_stdout.encoding = "cp1252"

        with patch("sys.stdout", mock_stdout):
            output = StringIO()
            console = Console(file=output, force_terminal=True, no_color=True)
            formatter = OutputFormatter(console=console)

        formatter.success("Done")
        result = output.getvalue()

        assert "OK:" in result
        assert "✓" not in result

    @pytest.mark.unit
    def test_success_uses_checkmark_for_unicode(self) -> None:
        """success() uses checkmark for Unicode-capable terminals."""
        mock_stdout = MagicMock()
        mock_stdout.encoding = "utf-8"

        with patch("sys.stdout", mock_stdout):
            output = StringIO()
            console = Console(file=output, force_terminal=True, no_color=True)
            formatter = OutputFormatter(console=console)

        formatter.success("Done")
        result = output.getvalue()

        assert "✓" in result
        assert "[OK]" not in result

    @pytest.mark.unit
    def test_placeholder_constants_exported(self) -> None:
        """Placeholder constants are properly exported."""
        assert PLACEHOLDER_ASCII == "-"
        assert PLACEHOLDER_UNICODE == "—"
