"""
Tests for wittrans

SPDX-FileCopyrightText: 2024 Jiri Grönroos <jiri.gronroos@iki.fi>

SPDX-License-Identifier: LGPL-3.0-or-later
"""

from pathlib import Path
from unittest.mock import MagicMock, patch

import pytest

from wittrans.models import FileResult, TranslationMatch
from wittrans.output import display_results


@pytest.fixture
def mock_console():
    """Create mocked Console object to capture output."""
    console = MagicMock()
    console.print = MagicMock()
    return console


class TestDisplayResults:
    """Tests for the display_results function."""

    def test_display_results_empty(self, mock_console):
        """Test display_results with no matches."""
        results: list[FileResult] = []

        with patch("wittrans.output.Console", return_value=mock_console):
            display_results(results, "test", "fi", "System")

        # Print a Panel with "No matches found"
        assert mock_console.print.called
        # Verify something is printed
        assert mock_console.print.call_args[0][0] is not None

    def test_display_results_single_match(self, mock_console):
        """Test display_results with a single match."""
        file_path = Path("/test/file.mo")
        results = [FileResult(file_path, [TranslationMatch("Hello", "Terve", None)])]

        with patch("wittrans.output.Console", return_value=mock_console):
            display_results(results, "Hello", "fi", "System")

        # Print Panel and Table
        assert mock_console.print.call_count == 2

        # Verify both calls received objects
        assert mock_console.print.call_args_list[0][0][0] is not None
        assert mock_console.print.call_args_list[1][0][0] is not None

    def test_display_results_multiple_matches(self, mock_console):
        """Test display_results with multiple matches."""
        file1 = Path("/test/file1.mo")
        file2 = Path("/test/file2.mo")
        results = [
            FileResult(
                file1,
                [
                    TranslationMatch("Hello", "Terve", None),
                    TranslationMatch("Goodbye", "Näkemiin", "formal"),
                ],
            ),
            FileResult(file2, [TranslationMatch("World", "Maailma", None)]),
        ]

        with patch("wittrans.output.Console", return_value=mock_console):
            display_results(results, "test", "fi", "System")

        # Print Panel and Table
        assert mock_console.print.call_count == 2

        # Verify both calls received objects
        assert mock_console.print.call_args_list[0][0][0] is not None
        assert mock_console.print.call_args_list[1][0][0] is not None

    def test_display_results_with_context(self, mock_console):
        """Test display_results with context field."""
        file_path = Path("/test/file.mo")
        results = [
            FileResult(
                file_path, [TranslationMatch("File", "Tiedosto", "menu context")]
            )
        ]

        with patch("wittrans.output.Console", return_value=mock_console):
            display_results(results, "File", "fi", "System")

        # Handle context properly
        assert mock_console.print.call_count == 2

    def test_display_results_without_context(self, mock_console):
        """Test display_results without context (None)."""
        file_path = Path("/test/file.mo")
        results = [FileResult(file_path, [TranslationMatch("File", "Tiedosto", None)])]

        with patch("wittrans.output.Console", return_value=mock_console):
            display_results(results, "File", "fi", "System")

        # Handle None context properly
        assert mock_console.print.call_count == 2

    def test_display_results_with_indication(self, mock_console):
        """Test display_results with indicate=True for highlighting."""
        file_path = Path("/test/file.mo")
        results = [
            FileResult(
                file_path, [TranslationMatch("Hello World", "Terve maailma", None)]
            )
        ]

        with patch("wittrans.output.Console", return_value=mock_console):
            display_results(results, "Hello", "fi", "System", indicate=True)

        # Print Panel and Table with highlighting
        assert mock_console.print.call_count == 2

    def test_display_results_without_indication(self, mock_console):
        """Test display_results with indicate=False (default)."""
        file_path = Path("/test/file.mo")
        results = [
            FileResult(
                file_path, [TranslationMatch("Hello World", "Terve maailma", None)]
            )
        ]

        with patch("wittrans.output.Console", return_value=mock_console):
            display_results(results, "Hello", "fi", "System", indicate=False)

        # Print Panel and Table without highlighting
        assert mock_console.print.call_count == 2

    def test_display_results_different_source_types(self, mock_console):
        """Test display_results with different source types."""
        file_path = Path("/test/file.mo")
        results = [FileResult(file_path, [TranslationMatch("Hello", "Terve", None)])]

        source_types = ["System", "Flatpak", "User GNOME Extensions"]

        for source_type in source_types:
            mock_console.reset_mock()

            with patch("wittrans.output.Console", return_value=mock_console):
                display_results(results, "Hello", "fi", source_type)

            # Verify display was called
            assert mock_console.print.call_count == 2

    def test_display_results_different_languages(self, mock_console):
        """Test display_results with different language codes."""
        file_path = Path("/test/file.mo")
        results = [FileResult(file_path, [TranslationMatch("Hello", "Hola", None)])]

        language_codes = ["fi", "es", "de", "zh_CN"]

        for lang_code in language_codes:
            mock_console.reset_mock()

            with patch("wittrans.output.Console", return_value=mock_console):
                display_results(results, "Hello", lang_code, "System")

            # Display results for each language
            assert mock_console.print.call_count == 2

    def test_display_results_special_characters(self, mock_console):
        """Test display_results with special characters in text."""
        file_path = Path("/test/file.mo")
        results = [
            FileResult(
                file_path,
                [
                    TranslationMatch("Text with <tags>", "Teksti <tageilla>", None),
                    TranslationMatch(
                        "Text with 'quotes'", "Teksti 'lainausmerkeillä'", None
                    ),
                    TranslationMatch(
                        "Text with & ampersand", "Teksti & ja-merkillä", None
                    ),
                    TranslationMatch("⚡", "⚡", None),
                ],
            )
        ]

        with patch("wittrans.output.Console", return_value=mock_console):
            display_results(results, "Text", "fi", "System")

        # Handle special characters properly (escaped)
        assert mock_console.print.call_count == 2

    def test_display_results_long_file_paths(self, mock_console):
        """Test display_results with long file paths."""
        long_path = Path(
            "/very/long/path/to/some/deeply/nested/directory/structure/file.mo"
        )
        results = [FileResult(long_path, [TranslationMatch("Hello", "Terve", None)])]

        with patch("wittrans.output.Console", return_value=mock_console):
            display_results(results, "Hello", "fi", "System")

        # Handle long paths
        assert mock_console.print.call_count == 2

    def test_display_results_unicode_text(self, mock_console):
        """Test display_results with Unicode characters."""
        file_path = Path("/test/file.mo")
        results = [
            FileResult(
                file_path,
                [
                    TranslationMatch("Day", "päivä", None),  # Finnish with ä
                    TranslationMatch("Night", "éjszaka", None),  # Hungarian with é
                    TranslationMatch("Hello", "你好", None),  # Chinese
                    TranslationMatch("World", "世界", None),  # Chinese
                ],
            )
        ]

        with patch("wittrans.output.Console", return_value=mock_console):
            display_results(results, "Hello", "zh_CN", "System")

        # Handle Unicode properly
        assert mock_console.print.call_count == 2

    def test_display_results_empty_strings(self, mock_console):
        """Test display_results with empty translation strings."""
        file_path = Path("/test/file.mo")
        results = [FileResult(file_path, [TranslationMatch("Hello", "", None)])]

        with patch("wittrans.output.Console", return_value=mock_console):
            display_results(results, "Hello", "fi", "System")

        # Handle empty strings
        assert mock_console.print.call_count == 2

    def test_display_results_match_count_calculation(self, mock_console):
        """Test that match count is calculated correctly."""
        file1 = Path("/test/file1.mo")
        file2 = Path("/test/file2.mo")
        results = [
            FileResult(
                file1,
                [
                    TranslationMatch("A", "a", None),
                    TranslationMatch("B", "b", None),
                    TranslationMatch("C", "c", None),
                ],
            ),
            FileResult(
                file2,
                [
                    TranslationMatch("D", "d", None),
                    TranslationMatch("E", "e", None),
                ],
            ),
        ]

        with patch("wittrans.output.Console", return_value=mock_console):
            display_results(results, "test", "fi", "System")

        # Should print Panel and Table
        assert mock_console.print.call_count == 2

    def test_display_results_highlight_case_insensitive(self, mock_console):
        """Test that highlighting works case-insensitively."""
        file_path = Path("/test/file.mo")
        results = [
            FileResult(
                file_path,
                [
                    TranslationMatch("HELLO world", "TERVE maailma", None),
                    TranslationMatch("hello WORLD", "terve MAAILMA", None),
                ],
            )
        ]

        with patch("wittrans.output.Console", return_value=mock_console):
            # Search for lowercase "hello" but should match uppercase too
            display_results(results, "hello", "fi", "System", indicate=True)

        # Should apply highlighting
        assert mock_console.print.call_count == 2

    def test_display_results_highlight_partial_match(self, mock_console):
        """Test highlighting with partial matches."""
        file_path = Path("/test/file.mo")
        results = [
            FileResult(file_path, [TranslationMatch("Monday", "Maanantai", None)])
        ]

        with patch("wittrans.output.Console", return_value=mock_console):
            # Search for "day" which is part of "Monday"
            display_results(results, "day", "fi", "System", indicate=True)

        # Should highlight the "day" part
        assert mock_console.print.call_count == 2

    def test_display_results_no_highlight_when_not_found(self, mock_console):
        """Test that no highlighting occurs when search term not in text."""
        file_path = Path("/test/file.mo")
        results = [FileResult(file_path, [TranslationMatch("Hello", "Terve", None)])]

        with patch("wittrans.output.Console", return_value=mock_console):
            # Search term "xyz" won't be found in either text
            display_results(results, "xyz", "fi", "System", indicate=True)

        # Should not crash, just display without highlighting
        assert mock_console.print.call_count == 2


class TestDisplayResultsIntegration:
    """Integration tests for display_results with actual Rich components."""

    def test_display_results_creates_valid_panel(self):
        """Test that display_results creates a valid Rich Panel."""
        from rich.panel import Panel

        file_path = Path("/test/file.mo")
        results = [FileResult(file_path, [TranslationMatch("Hello", "Terve", None)])]

        with patch("wittrans.output.Console") as MockConsole:
            mock_console = MagicMock()
            MockConsole.return_value = mock_console

            display_results(results, "Hello", "fi", "System")

            # First call should be a Panel
            first_call = mock_console.print.call_args_list[0][0][0]
            assert isinstance(first_call, Panel)

    def test_display_results_creates_valid_table(self):
        """Test that display_results creates a valid Rich Table."""
        from rich.table import Table

        file_path = Path("/test/file.mo")
        results = [FileResult(file_path, [TranslationMatch("Hello", "Terve", None)])]

        with patch("wittrans.output.Console") as MockConsole:
            mock_console = MagicMock()
            MockConsole.return_value = mock_console

            display_results(results, "Hello", "fi", "System")

            # Second call should be a Table
            second_call = mock_console.print.call_args_list[1][0][0]
            assert isinstance(second_call, Table)

    def test_display_results_table_has_correct_columns(self):
        """Test that the table has the expected columns."""
        from rich.table import Table

        file_path = Path("/test/file.mo")
        results = [FileResult(file_path, [TranslationMatch("Hello", "Terve", None)])]

        with patch("wittrans.output.Console") as MockConsole:
            mock_console = MagicMock()
            MockConsole.return_value = mock_console

            display_results(results, "Hello", "fi", "System")

            table = mock_console.print.call_args_list[1][0][0]
            assert isinstance(table, Table)

            # Table should have 4 columns: File, Context, Original Text, Translation
            assert len(table.columns) == 4
            assert table.columns[0].header == "File"
            assert table.columns[1].header == "Context"
            assert table.columns[2].header == "Original Text"
            # Fourth column header includes language name
            assert "Translation" in table.columns[3].header
