"""Tests for HierarchicalHelpFormatter functionality."""

import argparse
from unittest.mock import Mock, patch

from freyja.help.help_formatter import HierarchicalHelpFormatter
from freyja.theme import create_default_theme


class TestHierarchicalHelpFormatter:
  """Test HierarchicalHelpFormatter class functionality."""

  def setup_method(self):
    """Set up test formatter."""
    # Create minimal parser for testing
    self.parser = argparse.ArgumentParser(
      prog='test_cli',
      description='Test FreyjaCLI for formatter testing'
    )

    # Create formatter without theme initially
    self.formatter = HierarchicalHelpFormatter(
      prog='test_cli'
    )

  def test_formatter_initialization_no_theme(self):
    """Test formatter initialization without theme."""
    formatter = HierarchicalHelpFormatter(prog='test_cli')

    assert formatter._theme is None
    assert formatter._color_formatter is None
    assert formatter._cmd_indent == 2
    assert formatter._arg_indent == 4
    assert formatter._desc_indent == 8

  def test_formatter_initialization_with_theme(self):
    """Test formatter initialization with theme."""
    theme = create_default_theme()
    formatter = HierarchicalHelpFormatter(prog='test_cli', theme=theme)

    assert formatter._theme == theme
    assert formatter._color_formatter is not None

  def test_console_width_detection(self):
    """Test console width detection and fallback."""
    with patch('os.get_terminal_size') as mock_get_size:
      # Test normal case
      mock_get_size.return_value = Mock(columns=120)
      formatter = HierarchicalHelpFormatter(prog='test_cli')
      assert formatter._console_width == 120

      # Test fallback to environment variable
      mock_get_size.side_effect = OSError()
      with patch.dict('os.environ', {'COLUMNS': '100'}):
        formatter = HierarchicalHelpFormatter(prog='test_cli')
        assert formatter._console_width == 100

      # Test default fallback
      mock_get_size.side_effect = OSError()
      with patch.dict('os.environ', {}, clear=True):
        formatter = HierarchicalHelpFormatter(prog='test_cli')
        assert formatter._console_width == 80

  def test_apply_style_no_theme(self):
    """Test _apply_style method without theme."""
    formatter = HierarchicalHelpFormatter(prog='test_cli')
    result = formatter._apply_style("test text", "command_name")
    assert result == "test text"  # No styling applied

  def test_apply_style_with_theme(self):
    """Test _apply_style method with theme."""
    theme = create_default_theme()
    formatter = HierarchicalHelpFormatter(prog='test_cli', theme=theme)

    # Test that styling is applied (result should contain ANSI codes)
    result = formatter._apply_style("test text", "command_name")

    # Check if colors are enabled in the formatter
    if formatter._color_formatter and formatter._color_formatter.colors_enabled:
      assert result != "test text"  # Should be different due to ANSI codes
      assert "test text" in result  # Original text should be in result
    else:
      # If colors are disabled, result should be unchanged
      assert result == "test text"

  def test_get_display_width_plain_text(self):
    """Test _get_display_width with plain text."""
    formatter = HierarchicalHelpFormatter(prog='test_cli')
    assert formatter._get_display_width("hello") == 5
    assert formatter._get_display_width("") == 0

  def test_get_display_width_with_ansi_codes(self):
    """Test _get_display_width strips ANSI codes correctly."""
    formatter = HierarchicalHelpFormatter(prog='test_cli')

    # Text with ANSI color codes should report correct width
    ansi_text = "\x1b[32mhello\x1b[0m"  # Green "hello"
    assert formatter._get_display_width(ansi_text) == 5

    # More complex ANSI codes
    complex_ansi = "\x1b[1;32;48;5;231mhello world\x1b[0m"
    assert formatter._get_display_width(complex_ansi) == 11

  def test_wrap_text_basic(self):
    """Test _wrap_text method with basic text."""
    formatter = HierarchicalHelpFormatter(prog='test_cli')

    text = "This is a test string that should be wrapped properly."
    lines = formatter._wrap_text(text, indent=4, width=40)

    assert len(lines) > 1  # Should wrap
    assert all(line.startswith("    ") for line in lines)  # All lines indented

  def test_wrap_text_empty(self):
    """Test _wrap_text with empty text."""
    formatter = HierarchicalHelpFormatter(prog='test_cli')
    lines = formatter._wrap_text("", indent=4, width=80)
    assert lines == []

  def test_wrap_text_minimum_width(self):
    """Test _wrap_text respects minimum width."""
    formatter = HierarchicalHelpFormatter(prog='test_cli')

    # Very small width should still use minimum
    text = "This is a test string."
    lines = formatter._wrap_text(text, indent=70, width=80)

    # Should still wrap despite small available width
    assert len(lines) >= 1

  def test_analyze_arguments_empty_parser(self):
    """Test _analyze_arguments with empty parser."""
    formatter = HierarchicalHelpFormatter(prog='test_cli')
    required, optional = formatter._analyze_arguments(None)

    assert required == []
    assert optional == []

  def test_analyze_arguments_with_options(self):
    """Test _analyze_arguments with various option types."""
    formatter = HierarchicalHelpFormatter(prog='test_cli')

    # Create parser with different argument types
    parser = argparse.ArgumentParser()
    parser.add_argument('--required-arg', required=True, help='Required argument')
    parser.add_argument('--optional-arg', help='Optional argument')
    parser.add_argument('--flag', action='store_true', help='Boolean flag')
    parser.add_argument('--with-metavar', metavar='VALUE', help='Arg with metavar')

    required, optional = formatter._analyze_arguments(parser)

    # Check required args (returned as list of tuples: (name, help))
    assert len(required) == 1
    required_names = [name for name, _ in required]
    assert '--required-arg REQUIRED_ARG' in required_names

    # Check optional args (should have 3: optional-arg, flag, with-metavar)
    assert len(optional) == 3

    # Find specific optional args
    optional_names = [name for name, _ in optional]
    assert '--optional-arg OPTIONAL_ARG' in optional_names
    assert '--flag' in optional_names
    assert '--with-metavar VALUE' in optional_names

  def test_format_inline_description_no_description(self):
    """Test _format_inline_description with no description."""
    formatter = HierarchicalHelpFormatter(prog='test_cli')

    lines = formatter._format_inline_description(
      name="command",
      description="",
      name_indent=2,
      description_column=20,
      style_name="command_name",
      style_description="command_description"
    )

    assert len(lines) == 1
    assert lines[0] == "  command"

  def test_format_inline_description_with_colon(self):
    """Test _format_inline_description with colon."""
    formatter = HierarchicalHelpFormatter(prog='test_cli')

    lines = formatter._format_inline_description(
      name="command",
      description="Test description",
      name_indent=2,
      description_column=0,  # Not used for colons
      style_name="command_name",
      style_description="command_description",
      add_colon=True
    )

    assert len(lines) == 1
    # Command descriptions now align at same column as options (not colon + 1 space)
    assert "command:" in lines[0]
    assert "Test description" in lines[0]

  def test_format_inline_description_wrapping(self):
    """Test _format_inline_description with long text that needs wrapping."""
    formatter = HierarchicalHelpFormatter(prog='test_cli')
    formatter._console_width = 40  # Force small width for testing

    long_description = "This is a very long description that should definitely wrap to multiple lines when displayed in the help text."

    lines = formatter._format_inline_description(
      name="cmd",
      description=long_description,
      name_indent=2,
      description_column=10,
      style_name="command_name",
      style_description="command_description"
    )

    # Should wrap to multiple lines
    assert len(lines) > 1
    # First line should contain command name
    assert "cmd" in lines[0]

  def test_format_inline_description_alignment(self):
    """Test _format_inline_description column alignment."""
    formatter = HierarchicalHelpFormatter(prog='test_cli')

    lines = formatter._format_inline_description(
      name="short",
      description="Description",
      name_indent=2,
      description_column=20,
      style_name="command_name",
      style_description="command_description"
    )

    assert len(lines) == 1
    line = lines[0]

    # Check that description starts at approximately the right column
    # (accounting for ANSI codes if theme is enabled)
    assert "short" in line
    assert "Description" in line

  def test_find_subparser_exists(self):
    """Test _find_subparser when subparser exists."""
    formatter = HierarchicalHelpFormatter(prog='test_cli')

    # Create parser with subparsers
    parser = argparse.ArgumentParser()
    subparsers = parser.add_subparsers()
    sub = subparsers.add_parser('test-cmd')

    # Find the subparser
    found = formatter._find_subparser(parser, 'test-cmd')
    assert found == sub

  def test_find_subparser_not_exists(self):
    """Test _find_subparser when subparser doesn't exist."""
    formatter = HierarchicalHelpFormatter(prog='test_cli')

    parser = argparse.ArgumentParser()
    subparsers = parser.add_subparsers()
    subparsers.add_parser('existing-cmd')

    # Try to find non-existent subparser
    found = formatter._find_subparser(parser, 'nonexistent')
    assert found is None

  def test_find_subparser_no_subparsers(self):
    """Test _find_subparser with parser that has no subparsers."""
    formatter = HierarchicalHelpFormatter(prog='test_cli')

    parser = argparse.ArgumentParser()
    found = formatter._find_subparser(parser, 'any-cmd')
    assert found is None


class TestHierarchicalFormatterWithCommandGroups:
  """Test HierarchicalHelpFormatter with CommandGroup descriptions."""

  def setup_method(self):
    """Set up test with mock parser that has CommandGroup description."""
    self.formatter = HierarchicalHelpFormatter(prog='test_cli')

    # Create mock parser with CommandGroup description
    self.parser = argparse.ArgumentParser()
    self.parser._command_group_description = "Custom group description from decorator"

  def test_format_group_with_command_group_description(self):
    """Test that CommandGroup descriptions are used in formatting."""
    # Create mock group parser
    group_parser = Mock()
    group_parser._command_group_description = "Database operations and management"
    group_parser._commands = {'create': 'Create database', 'migrate': 'Run migrations'}
    group_parser.description = "Default description"
    group_parser._actions = []  # Add empty actions list to avoid iteration error

    # Mock _find_subparser to return mock subparsers
    def mock_find_subparser(parser, name):
      mock_sub = Mock()
      mock_sub._actions = []  # Empty actions list
      return mock_sub

    self.formatter._find_subparser = mock_find_subparser

    # Mock other required methods
    self.formatter._calculate_group_dynamic_columns = Mock(return_value=(20, 30))
    self.formatter._format_command_with_args_global_command = Mock(return_value=['  cmd_group: description'])

    # Test the formatting
    lines = self.formatter._format_group_with_command_groups_global(
      name="db",
      parser=group_parser,
      base_indent=2,
      unified_cmd_desc_column=25,  # Add unified command description column
      global_option_column=40
    )

    # Should use the CommandGroup description in formatting
    assert len(lines) > 0
    # The first line should contain the formatted group name with description
    formatted_line = lines[0]
    # Command descriptions now align at column 25, not colon + 1 space
    assert "db:" in formatted_line
    assert "Database operations and management" in formatted_line

  def test_format_group_without_command_group_description(self):
    """Test formatting falls back to default when no CommandGroup description."""
    # Create mock group parser without CommandGroup description
    group_parser = Mock()
    # No _command_group_description attribute - set to None to avoid getattr returning Mock
    group_parser._command_group_description = None
    group_parser.description = "Default group description"
    group_parser.help = ""  # Ensure help is a string, not a Mock
    group_parser._commands = {}
    group_parser._actions = []  # Add empty actions list to avoid iteration error

    lines = self.formatter._format_group_with_command_groups_global(
      name="admin",
      parser=group_parser,
      base_indent=2,
      unified_cmd_desc_column=25,  # Add unified command description column
      global_option_column=40
    )

    # Should use default formatting
    assert len(lines) > 0


class TestHierarchicalFormatterIntegration:
  """Integration tests for HierarchicalHelpFormatter with actual parsers."""

  def test_format_action_with_subparsers(self):
    """Test _format_action with SubParsersAction."""
    formatter = HierarchicalHelpFormatter(prog='test_cli')

    # Create parser with command groups
    parser = argparse.ArgumentParser(formatter_class=lambda *args, **kwargs: formatter)
    subparsers = parser.add_subparsers(dest='command')

    # Add a simple command group
    sub = subparsers.add_parser('test-cmd', help='Test command')
    sub.add_argument('--option', help='Test option')

    # Find the SubParsersAction
    subparsers_action = None
    for action in parser._actions:
      if isinstance(action, argparse._SubParsersAction):
        subparsers_action = action
        break

    assert subparsers_action is not None

    # Test formatting (should not raise exceptions)
    formatted = formatter._format_action(subparsers_action)
    assert isinstance(formatted, str)
    assert 'test-cmd' in formatted

  def test_format_global_option_aligned(self):
    """Test _format_global_option_aligned with actual arguments."""
    formatter = HierarchicalHelpFormatter(prog='test_cli')

    # Create an argument action
    parser = argparse.ArgumentParser()
    parser.add_argument('--verbose', '-v', action='store_true', help='Enable verbose output')

    # Get the action
    verbose_action = None
    for action in parser._actions:
      if action.dest == 'verbose':
        verbose_action = action
        break

    assert verbose_action is not None

    # Mock the global column calculation
    formatter._ensure_global_column_calculated = Mock(return_value=30)

    # Test formatting
    formatted = formatter._format_global_option_aligned(verbose_action)
    assert isinstance(formatted, str)
    # Check for either --verbose or -v (argparse may prefer the short form)
    assert ('--verbose' in formatted or '-v' in formatted)
    assert 'Enable verbose output' in formatted

  def test_full_help_formatting_integration(self):
    """Test complete help formatting with real parser structure."""
    # Create a parser similar to what FreyjaCLI would create
    parser = argparse.ArgumentParser(
      prog='test_cli',
      description='Test FreyjaCLI Application',
      formatter_class=lambda *args, **kwargs: HierarchicalHelpFormatter(*args, **kwargs)
    )

    # Add global options
    parser.add_argument('--verbose', action='store_true', help='Enable verbose output')
    parser.add_argument('--config', metavar='FILE', help='Configuration file')

    # Add command groups
    subparsers = parser.add_subparsers(title='COMMANDS', dest='command')

    # Flat command
    hello_cmd = subparsers.add_parser('hello', help='Greet someone')
    hello_cmd.add_argument('--name', default='World', help='Name to greet')

    # Group command (simulate what FreyjaCLI creates)
    user_group = subparsers.add_parser('user', help='User management operations')
    user_group._command_type = 'group'
    user_group._commands = {'create': 'Create user', 'delete': 'Delete user'}

    # Test that help can be generated without errors
    help_text = parser.format_help()

    # Basic sanity checks
    assert 'Test FreyjaCLI Application' in help_text
    assert 'COMMANDS:' in help_text
    assert 'hello' in help_text
    assert 'user' in help_text
    assert '--verbose' in help_text
    assert '--config' in help_text


class TestHierarchicalFormatterEdgeCases:
  """Test edge cases and error handling for HierarchicalHelpFormatter."""

  def test_format_with_very_long_names(self):
    """Test formatting with very long command/option names."""
    formatter = HierarchicalHelpFormatter(prog='test_cli')
    formatter._console_width = 40  # Small console for testing

    long_name = "very-long-command-name-that-exceeds-normal-length"
    long_description = "This is a very long description that should wrap properly even with long command names."

    lines = formatter._format_inline_description(
      name=long_name,
      description=long_description,
      name_indent=2,
      description_column=20,  # Will be exceeded by long name
      style_name="command_name",
      style_description="command_description"
    )

    # Should handle gracefully
    assert len(lines) >= 1
    assert long_name in lines[0]

  def test_format_with_empty_console_width(self):
    """Test behavior with minimal console width."""
    formatter = HierarchicalHelpFormatter(prog='test_cli')
    formatter._console_width = 10  # Very small

    lines = formatter._format_inline_description(
      name="cmd",
      description="Description text",
      name_indent=2,
      description_column=15,
      style_name="command_name",
      style_description="command_description"
    )

    # Should still produce output
    assert len(lines) >= 1

  def test_analyze_arguments_with_complex_actions(self):
    """Test _analyze_arguments with complex argument configurations."""
    formatter = HierarchicalHelpFormatter(prog='test_cli')

    parser = argparse.ArgumentParser()

    # Add arguments with various configurations
    parser.add_argument('--choices', choices=['a', 'b', 'c'], help='Argument with choices')
    parser.add_argument('--count', type=int, action='append', help='Repeatable integer argument')
    parser.add_argument('--store-const', action='store_const', const='value', help='Store constant')

    required, optional = formatter._analyze_arguments(parser)

    # All should be optional since none marked as required
    assert len(required) == 0
    assert len(optional) == 3

    # Check that different action types are handled
    optional_names = [name for name, _ in optional]
    assert any('--choices' in name for name in optional_names)
    assert any('--count' in name for name in optional_names)
    assert any('--store-const' in name for name in optional_names)
