"""
Unit tests for build system code minification features.

These tests verify that the build process can:
1. Strip comments while preserving functionality
2. Remove docstrings except bl_info and functionally required ones
3. Optimize whitespace
4. Maintain valid Python syntax and runtime behavior
"""

import ast
import textwrap
from pathlib import Path
import sys

# Add scripts directory to path to import build_addon
sys.path.insert(0, str(Path(__file__).parent.parent.parent / "scripts"))

import pytest


def test_strips_inline_comments():
    """Test that inline comments are removed from code."""
    source = textwrap.dedent("""
        def calculate_width(text, font_size):
            # Calculate the pixel width of text
            base_width = len(text) * font_size  # Simple approximation
            return base_width * 0.6  # Adjustment factor
    """).strip()
    
    from build_addon import minify_python_code
    result = minify_python_code(source)
    
    # Verify comments are removed
    assert "# Calculate" not in result
    assert "# Simple approximation" not in result
    assert "# Adjustment factor" not in result
    
    # Verify functionality is preserved
    assert "def calculate_width(text, font_size):" in result
    assert "base_width = len(text) * font_size" in result
    assert "return base_width * 0.6" in result
    
    # Verify result is valid Python
    ast.parse(result)


def test_strips_block_comments():
    """Test that block comments at various positions are removed."""
    source = textwrap.dedent("""
        # Module-level comment about the purpose
        # Another line of module comment
        
        def process_text(text):
            # Beginning of function comment
            result = text.upper()
            # Middle comment
            result = result.strip()
            # End comment
            return result
    """).strip()
    
    from build_addon import minify_python_code
    result = minify_python_code(source)
    
    # All comments should be gone
    assert "# Module-level" not in result
    assert "# Another line" not in result
    assert "# Beginning of function" not in result
    assert "# Middle comment" not in result
    assert "# End comment" not in result
    
    # But code should remain
    assert "def process_text(text):" in result
    assert "result = text.upper()" in result
    assert "result = result.strip()" in result
    assert "return result" in result


def test_preserves_bl_info_dict():
    """Test that bl_info dictionary is preserved (required by Blender)."""
    source = textwrap.dedent('''
        """
        Addon docstring that should be removed.
        """
        
        bl_info = {
            "name": "Text Texture Generator",
            "author": "Developer",
            "version": (1, 0, 0),
            "blender": (4, 0, 0),
            "description": "Generate text textures",
        }
        
        def some_function():
            """Regular docstring to remove."""
            return "value"
    ''').strip()
    
    from build_addon import minify_python_code
    result = minify_python_code(source)
    
    # bl_info must be preserved exactly
    assert "bl_info = {" in result
    assert '"name": "Text Texture Generator"' in result
    assert '"author": "Developer"' in result
    assert '"version": (1, 0, 0)' in result
    
    # Module docstring should be removed
    assert "Addon docstring that should be removed" not in result
    
    # Function docstring should be removed
    assert "Regular docstring to remove" not in result
    
    # Code should remain
    assert "def some_function():" in result
    assert 'return "value"' in result


def test_removes_function_docstrings():
    """Test that function and method docstrings are removed."""
    source = textwrap.dedent('''
        class TextGenerator:
            """Class docstring to remove."""
            
            def generate(self, text):
                """
                Generate a texture from text.
                
                Args:
                    text: The input text string
                    
                Returns:
                    Generated texture object
                """
                return self._process(text)
            
            def _process(self, text):
                """Internal processing method."""
                return text.upper()
    ''').strip()
    
    from build_addon import minify_python_code
    result = minify_python_code(source)
    
    # All docstrings should be removed
    assert "Class docstring to remove" not in result
    assert "Generate a texture from text" not in result
    assert "Args:" not in result
    assert "Returns:" not in result
    assert "Internal processing method" not in result
    
    # Code structure should remain
    assert "class TextGenerator:" in result
    assert "def generate(self, text):" in result
    assert "return self._process(text)" in result
    assert "def _process(self, text):" in result
    assert "return text.upper()" in result


def test_preserves_string_literals():
    """Test that string literals used as data are not removed."""
    source = textwrap.dedent('''
        def get_error_messages():
            """Function docstring to remove."""
            # Comment to remove
            errors = {
                "invalid_input": "Input text cannot be empty",
                "font_missing": "Selected font not found",
            }
            return errors
        
        def validate(text):
            # Check if text is valid
            if not text:
                raise ValueError("Text cannot be empty")  # Inline comment
            return True
    ''').strip()
    
    from build_addon import minify_python_code
    result = minify_python_code(source)
    
    # Docstrings and comments should be removed
    assert "Function docstring to remove" not in result
    assert "# Comment to remove" not in result
    assert "# Check if text is valid" not in result
    assert "# Inline comment" not in result
    
    # String literals in code must be preserved
    assert '"invalid_input": "Input text cannot be empty"' in result
    assert '"font_missing": "Selected font not found"' in result
    assert 'raise ValueError("Text cannot be empty")' in result
    
    # Code structure preserved
    assert "def get_error_messages():" in result
    assert "def validate(text):" in result


def test_optimizes_whitespace():
    """Test that excessive blank lines and inconsistent indentation are cleaned up."""
    source = textwrap.dedent("""
        def function_one():
            result = 1
            
            
            
            return result
        
        
        
        def function_two():
            # Comment
            
            
            value = 2
            
            return value
        
        
        class MyClass:
            
            
            def method(self):
                pass
    """).strip()
    
    from build_addon import minify_python_code
    result = minify_python_code(source)
    
    # Should not have excessive blank lines (max 1 between definitions)
    assert "\n\n\n" not in result
    
    # Functions should be separated by at most one blank line
    lines = result.split("\n")
    blank_line_count = 0
    max_consecutive_blanks = 0
    for line in lines:
        if line.strip() == "":
            blank_line_count += 1
            max_consecutive_blanks = max(max_consecutive_blanks, blank_line_count)
        else:
            blank_line_count = 0
    
    assert max_consecutive_blanks <= 1, f"Found {max_consecutive_blanks} consecutive blank lines"
    
    # Code should still be present
    assert "def function_one():" in result
    assert "def function_two():" in result
    assert "class MyClass:" in result


def test_maintains_valid_python_syntax():
    """Test that minified code is syntactically valid Python."""
    source = textwrap.dedent('''
        """Module docstring."""
        
        # Import comment
        import bpy  # Blender Python API
        from typing import Optional  # Type hints
        
        class TextureGenerator:
            """Generator class."""
            
            def __init__(self, resolution=1024):
                """Initialize generator."""
                # Store resolution
                self.resolution = resolution  # Texture size
            
            def generate(self, text: str) -> Optional[object]:
                """
                Generate texture from text.
                
                Args:
                    text: Input text string
                
                Returns:
                    Texture object or None
                """
                if not text:  # Validate input
                    return None
                
                # Create texture
                texture = bpy.data.images.new(
                    "TextTexture",
                    self.resolution,
                    self.resolution
                )
                
                return texture  # Return result
    ''').strip()
    
    from build_addon import minify_python_code
    result = minify_python_code(source)
    
    # Must be valid Python
    try:
        ast.parse(result)
    except SyntaxError as e:
        pytest.fail(f"Minified code has syntax error: {e}")
    
    # Should not have docstrings or comments
    assert '"""Module docstring."""' not in result
    assert '"""Generator class."""' not in result
    assert '"""Initialize generator."""' not in result
    assert "# Import comment" not in result
    assert "# Blender Python API" not in result
    assert "# Store resolution" not in result
    
    # Code should be present
    assert "import bpy" in result
    assert "from typing import Optional" in result
    assert "class TextureGenerator:" in result
    assert "def __init__(self, resolution=1024):" in result
    assert "self.resolution = resolution" in result


def test_preserves_strings_with_hash_symbols():
    """Test that strings containing hash symbols are not treated as comments."""
    source = textwrap.dedent('''
        def create_id(text):
            """Generate hash ID."""  # Function comment
            # Create the ID with a hash prefix
            return f"#{hash(text)}"  # Hash prefix
        
        def parse_color(color_string):
            # Check if it's a hex color
            if color_string.startswith("#"):  # Hex colors start with #
                return color_string
            return None
    ''').strip()
    
    from build_addon import minify_python_code
    result = minify_python_code(source)
    
    # Comments should be removed
    assert "Function comment" not in result
    assert "# Create the ID" not in result
    assert "# Hash prefix" not in result
    assert "# Check if it's a hex color" not in result
    assert "# Hex colors start with #" not in result
    
    # String literals with # should be preserved
    assert 'f"#{hash(text)}"' in result or 'f"#{hash(text)}"' in result
    assert 'startswith("#")' in result or 'startswith("#")' in result
    
    # Code structure preserved
    assert "def create_id(text):" in result
    assert "def parse_color(color_string):" in result


def test_preserves_functionality_complex_example():
    """Test minification preserves functionality in complex real-world code."""
    source = textwrap.dedent('''
        """
        Text processing module for the addon.
        
        This module handles text wrapping and fitting.
        """
        
        import re  # Regular expressions
        from typing import List, Tuple  # Type hints
        
        class TextProcessor:
            """Process text for texture generation."""
            
            def __init__(self, max_width: int = 100):
                """
                Initialize processor.
                
                Args:
                    max_width: Maximum line width in pixels
                """
                # Store configuration
                self.max_width = max_width  # Width limit
                self._cache = {}  # Results cache
            
            def wrap_text(self, text: str) -> List[str]:
                """
                Wrap text to fit within max width.
                
                Args:
                    text: Input text to wrap
                
                Returns:
                    List of wrapped lines
                """
                # Check cache first
                if text in self._cache:  # Cache hit
                    return self._cache[text]
                
                # Split into words
                words = re.split(r'\\s+', text)  # Word boundaries
                lines = []
                current_line = ""
                
                # Process each word
                for word in words:
                    # Check if adding word exceeds width
                    test_line = f"{current_line} {word}".strip()
                    if len(test_line) <= self.max_width:  # Fits
                        current_line = test_line
                    else:  # Doesn't fit
                        if current_line:  # Save current line
                            lines.append(current_line)
                        current_line = word  # Start new line
                
                # Add final line
                if current_line:  # Not empty
                    lines.append(current_line)
                
                # Cache result
                self._cache[text] = lines  # Store for reuse
                return lines
    ''').strip()
    
    from build_addon import minify_python_code
    result = minify_python_code(source)
    
    # Must be valid Python
    try:
        compiled = compile(result, '<minified>', 'exec')
    except SyntaxError as e:
        pytest.fail(f"Minified code won't compile: {e}")
    
    # Verify it's executable (can define the class)
    namespace = {}
    exec(compiled, namespace)
    assert 'TextProcessor' in namespace
    
    # Verify functionality works
    processor = namespace['TextProcessor'](max_width=20)
    result_lines = processor.wrap_text("hello world test")
    assert isinstance(result_lines, list)
    assert len(result_lines) > 0
    
    # No docstrings or comments should remain
    minified_lines = result.split('\n')
    for line in minified_lines:
        stripped = line.strip()
        if stripped:
            assert not stripped.startswith('#'), f"Found comment line: {line}"
            assert '"""' not in line, f"Found docstring in: {line}"