"""
Tests for colors module.
"""

import os
import sys
from unittest.mock import patch

import pytest

from gattopy.colors import Colors, colorize


class TestColors:
    """Test cases for Colors class."""

    def test_colors_defined(self):
        """Test that all color codes are defined."""
        assert hasattr(Colors, "RED")
        assert hasattr(Colors, "GREEN")
        assert hasattr(Colors, "BLUE")
        assert hasattr(Colors, "YELLOW")
        assert hasattr(Colors, "CYAN")
        assert hasattr(Colors, "MAGENTA")
        assert hasattr(Colors, "WHITE")
        assert hasattr(Colors, "BLACK")

    def test_bright_colors_defined(self):
        """Test that bright color codes are defined."""
        assert hasattr(Colors, "BRIGHT_RED")
        assert hasattr(Colors, "BRIGHT_GREEN")
        assert hasattr(Colors, "BRIGHT_BLUE")
        assert hasattr(Colors, "BRIGHT_YELLOW")
        assert hasattr(Colors, "BRIGHT_CYAN")
        assert hasattr(Colors, "BRIGHT_MAGENTA")

    def test_styles_defined(self):
        """Test that style codes are defined."""
        assert hasattr(Colors, "BOLD")
        assert hasattr(Colors, "DIM")
        assert hasattr(Colors, "ITALIC")
        assert hasattr(Colors, "UNDERLINE")

    def test_reset_defined(self):
        """Test that reset code is defined."""
        assert hasattr(Colors, "RESET")

    def test_color_codes_are_strings(self):
        """Test that color codes are strings."""
        assert isinstance(Colors.RED, str)
        assert isinstance(Colors.GREEN, str)
        assert isinstance(Colors.BOLD, str)
        assert isinstance(Colors.RESET, str)

    def test_disable_colors(self):
        """Test that disable() sets all colors to empty strings."""
        # Save original values
        original_red = Colors.RED
        original_bold = Colors.BOLD

        # Disable colors
        Colors.disable()

        assert Colors.RED == ""
        assert Colors.GREEN == ""
        assert Colors.BOLD == ""
        assert Colors.RESET == ""

        # Restore (for other tests)
        Colors.RED = original_red
        Colors.BOLD = original_bold


class TestIsSupported:
    """Test cases for Colors.is_supported()."""

    def test_no_color_env_disables_colors(self):
        """Test that NO_COLOR environment variable disables colors."""
        with patch.dict(os.environ, {"NO_COLOR": "1"}):
            assert Colors.is_supported() is False

    def test_force_color_env_enables_colors(self):
        """Test that FORCE_COLOR environment variable enables colors."""
        with patch.dict(os.environ, {"FORCE_COLOR": "1"}, clear=True):
            with patch("sys.stdout.isatty", return_value=True):
                assert Colors.is_supported() is True

    def test_no_tty_disables_colors(self):
        """Test that non-TTY stdout disables colors."""
        with patch.dict(os.environ, {}, clear=True):
            with patch("sys.stdout.isatty", return_value=False):
                assert Colors.is_supported() is False

    def test_tty_enables_colors_on_unix(self):
        """Test that TTY stdout enables colors on Unix systems."""
        with patch.dict(os.environ, {}, clear=True):
            with patch("sys.stdout.isatty", return_value=True):
                with patch("sys.platform", "linux"):
                    assert Colors.is_supported() is True

    def test_windows_color_support(self):
        """Test that Windows 10+ supports colors."""
        with patch.dict(os.environ, {}, clear=True):
            with patch("sys.stdout.isatty", return_value=True):
                with patch("sys.platform", "win32"):
                    with patch("platform.version", return_value="10.0.19041"):
                        assert Colors.is_supported() is True

    def test_windows_old_version(self):
        """Test that old Windows versions may not support colors."""
        with patch.dict(os.environ, {}, clear=True):
            with patch("sys.stdout.isatty", return_value=True):
                with patch("sys.platform", "win32"):
                    with patch("platform.version", return_value="6.1.7601"):
                        assert Colors.is_supported() is False

    def test_no_isatty_attribute(self):
        """Test behavior when stdout has no isatty attribute."""
        with patch.dict(os.environ, {}, clear=True):
            # Create a mock stdout without isatty
            class FakeStdout:
                pass

            with patch("sys.stdout", FakeStdout()):
                assert Colors.is_supported() is False


class TestColorize:
    """Test cases for colorize() function."""

    def test_colorize_basic(self):
        """Test basic colorization."""
        with patch.object(Colors, "is_supported", return_value=True):
            result = colorize("test", Colors.RED)
            assert Colors.RED in result
            assert Colors.RESET in result
            assert "test" in result

    def test_colorize_with_bold(self):
        """Test colorization with bold."""
        with patch.object(Colors, "is_supported", return_value=True):
            result = colorize("test", Colors.GREEN, bold=True)
            assert Colors.BOLD in result
            assert Colors.GREEN in result
            assert Colors.RESET in result
            assert "test" in result

    def test_colorize_without_support(self):
        """Test that colorize returns plain text when colors not supported."""
        with patch.object(Colors, "is_supported", return_value=False):
            result = colorize("test", Colors.RED, bold=True)
            assert result == "test"
            # When colors are not supported, colorize just returns the text
            assert len(result) == 4  # Just "test"

    def test_colorize_empty_string(self):
        """Test colorizing empty string."""
        with patch.object(Colors, "is_supported", return_value=True):
            result = colorize("", Colors.BLUE)
            assert Colors.BLUE in result
            assert Colors.RESET in result

    def test_colorize_multiline(self):
        """Test colorizing multiline text."""
        text = "line1\nline2\nline3"
        with patch.object(Colors, "is_supported", return_value=True):
            result = colorize(text, Colors.YELLOW)
            assert Colors.YELLOW in result
            assert Colors.RESET in result
            assert "line1" in result
            assert "line2" in result

    def test_colorize_different_colors(self):
        """Test colorizing with different colors."""
        colors_to_test = [
            Colors.RED,
            Colors.GREEN,
            Colors.BLUE,
            Colors.YELLOW,
            Colors.CYAN,
            Colors.MAGENTA,
            Colors.WHITE,
            Colors.BRIGHT_RED,
            Colors.BRIGHT_GREEN,
        ]

        with patch.object(Colors, "is_supported", return_value=True):
            for color in colors_to_test:
                result = colorize("test", color)
                assert color in result
                assert Colors.RESET in result

    def test_colorize_preserves_content(self):
        """Test that colorization preserves original content."""
        text = "Hello, World! 123 @#$%"
        with patch.object(Colors, "is_supported", return_value=True):
            result = colorize(text, Colors.CYAN)
            # Remove ANSI codes to check content
            plain = result.replace(Colors.CYAN, "").replace(Colors.RESET, "")
            assert plain == text


class TestColorsIntegration:
    """Integration tests for colors module."""

    def test_colors_in_sequence(self):
        """Test applying multiple colors in sequence."""
        with patch.object(Colors, "is_supported", return_value=True):
            red_text = colorize("red", Colors.RED)
            green_text = colorize("green", Colors.GREEN)
            blue_text = colorize("blue", Colors.BLUE)

            combined = f"{red_text} {green_text} {blue_text}"

            assert "red" in combined
            assert "green" in combined
            assert "blue" in combined
            assert Colors.RED in combined
            assert Colors.GREEN in combined
            assert Colors.BLUE in combined

    def test_bold_and_color_combination(self):
        """Test combining bold with various colors."""
        with patch.object(Colors, "is_supported", return_value=True):
            result = colorize("important", Colors.RED, bold=True)
            assert Colors.BOLD in result
            assert Colors.RED in result
            assert "important" in result

    def test_disabled_colors_return_plain_text(self):
        """Test that disabled colors return plain text."""
        # Save original
        original_red = Colors.RED

        # Disable
        Colors.disable()

        result = colorize("test", Colors.RED, bold=True)
        assert result == "test"

        # Restore
        Colors.RED = original_red

    def test_unicode_text_colorization(self):
        """Test colorizing Unicode text."""
        unicode_text = "Héllo Wörld 你好 مرحبا"
        with patch.object(Colors, "is_supported", return_value=True):
            result = colorize(unicode_text, Colors.MAGENTA)
            assert unicode_text in result
            assert Colors.MAGENTA in result

    def test_special_characters_colorization(self):
        """Test colorizing text with special characters."""
        special_text = "!@#$%^&*()[]{}|\\;:'\"<>,.?/~`"
        with patch.object(Colors, "is_supported", return_value=True):
            result = colorize(special_text, Colors.YELLOW)
            assert special_text in result


class TestColorsEdgeCases:
    """Test edge cases for colors module."""

    def test_very_long_text(self):
        """Test colorizing very long text."""
        long_text = "a" * 10000
        with patch.object(Colors, "is_supported", return_value=True):
            result = colorize(long_text, Colors.BLUE)
            assert Colors.BLUE in result
            assert Colors.RESET in result
            assert long_text in result

    def test_text_with_existing_ansi_codes(self):
        """Test colorizing text that already has ANSI codes."""
        text_with_codes = f"{Colors.RED}already colored{Colors.RESET}"
        with patch.object(Colors, "is_supported", return_value=True):
            result = colorize(text_with_codes, Colors.GREEN)
            # Should wrap the entire thing in green
            assert Colors.GREEN in result
            assert text_with_codes in result

    def test_none_color_code(self):
        """Test behavior with None as color code."""
        with patch.object(Colors, "is_supported", return_value=True):
            # This should handle gracefully
            result = colorize("test", "")
            assert "test" in result

    def test_numeric_text(self):
        """Test colorizing numeric strings."""
        with patch.object(Colors, "is_supported", return_value=True):
            result = colorize("12345.67890", Colors.CYAN)
            assert "12345.67890" in result
            assert Colors.CYAN in result
