"""
Test the new abstract/concrete style system.

This test demonstrates how the new style system addresses the memory efficiency
concerns by using abstract styles that can be resolved to concrete styles
based on user preferences.
"""

import pytest
from pyWebLayout.style.abstract_style import (
    AbstractStyle, AbstractStyleRegistry, FontFamily, FontSize
)
from pyWebLayout.style.concrete_style import (
    ConcreteStyleRegistry, RenderingContext, StyleResolver
)
from pyWebLayout.style.fonts import FontWeight


def test_abstract_style_is_hashable():
    """Test that AbstractStyle objects are hashable and can be used as dict keys."""
    # Create two identical styles
    style1 = AbstractStyle(
        font_family=FontFamily.SERIF,
        font_size=16,
        font_weight=FontWeight.BOLD,
        color="red"
    )

    style2 = AbstractStyle(
        font_family=FontFamily.SERIF,
        font_size=16,
        font_weight=FontWeight.BOLD,
        color="red"
    )

    # They should be equal and have the same hash
    assert style1 == style2
    assert hash(style1) == hash(style2)

    # They should work as dictionary keys
    style_dict = {style1: "first", style2: "second"}
    assert len(style_dict) == 1  # Should be deduplicated
    assert style_dict[style1] == "second"  # Last value wins


def test_abstract_style_registry_deduplication():
    """Test that the registry prevents duplicate styles."""
    registry = AbstractStyleRegistry()

    # Create the same style twice
    style1 = AbstractStyle(font_size=18, font_weight=FontWeight.BOLD)
    style2 = AbstractStyle(font_size=18, font_weight=FontWeight.BOLD)

    # Register both - should get same ID
    id1, _ = registry.get_or_create_style(style1)
    id2, _ = registry.get_or_create_style(style2)

    assert id1 == id2  # Same style should get same ID
    assert registry.get_style_count() == 2  # Only default + our style


def test_style_inheritance():
    """Test that style inheritance works properly."""
    registry = AbstractStyleRegistry()

    # Create base style
    base_style = AbstractStyle(font_size=16, color="black")
    base_id, _ = registry.get_or_create_style(base_style)

    # Create derived style
    derived_id, derived_style = registry.create_derived_style(
        base_id,
        font_weight=FontWeight.BOLD,
        color="red"
    )

    # Resolve effective style
    effective = registry.resolve_effective_style(derived_id)

    assert effective.font_size == 16  # Inherited from base
    assert effective.font_weight == FontWeight.BOLD  # Overridden
    assert effective.color == "red"  # Overridden


def test_style_resolver_user_preferences():
    """Test that user preferences affect concrete style resolution."""
    # Create rendering context with larger fonts
    context = RenderingContext(
        base_font_size=20,  # Larger base size
        font_scale_factor=1.5,  # Additional scaling
        large_text=True  # Accessibility preference
    )

    resolver = StyleResolver(context)

    # Create abstract style with medium size
    abstract_style = AbstractStyle(font_size=FontSize.MEDIUM)

    # Resolve to concrete style
    concrete_style = resolver.resolve_style(abstract_style)

    # Font size should be: 20 (base) * 1.0 (medium) * 1.5 (scale) * 1.2
    # (large_text) = 36
    expected_size = int(20 * 1.0 * 1.5 * 1.2)
    assert concrete_style.font_size == expected_size


def test_style_resolver_color_resolution():
    """Test color name resolution."""
    context = RenderingContext()
    resolver = StyleResolver(context)

    # Test named colors
    red_style = AbstractStyle(color="red")
    concrete_red = resolver.resolve_style(red_style)
    assert concrete_red.color == (255, 0, 0)

    # Test hex colors
    hex_style = AbstractStyle(color="#ff0000")
    concrete_hex = resolver.resolve_style(hex_style)
    assert concrete_hex.color == (255, 0, 0)

    # Test RGB tuple (should pass through)
    rgb_style = AbstractStyle(color=(128, 64, 192))
    concrete_rgb = resolver.resolve_style(rgb_style)
    assert concrete_rgb.color == (128, 64, 192)


def test_concrete_style_caching():
    """Test that concrete styles are cached efficiently."""
    context = RenderingContext()
    registry = ConcreteStyleRegistry(StyleResolver(context))

    # Create abstract style
    abstract_style = AbstractStyle(font_size=16, color="blue")

    # Get font twice - should be cached
    font1 = registry.get_font(abstract_style)
    font2 = registry.get_font(abstract_style)

    # Should be the same object (cached)
    assert font1 is font2

    # Check cache stats
    stats = registry.get_cache_stats()
    assert stats["concrete_styles"] == 1
    assert stats["fonts"] == 1


def test_global_font_scaling():
    """Test that global font scaling affects all text."""
    # Create two contexts with different scaling
    context_normal = RenderingContext(font_scale_factor=1.0)
    context_large = RenderingContext(font_scale_factor=2.0)

    resolver_normal = StyleResolver(context_normal)
    resolver_large = StyleResolver(context_large)

    # Same abstract style
    abstract_style = AbstractStyle(font_size=16)

    # Resolve with different contexts
    concrete_normal = resolver_normal.resolve_style(abstract_style)
    concrete_large = resolver_large.resolve_style(abstract_style)

    # Large should be 2x the size
    assert concrete_large.font_size == concrete_normal.font_size * 2


def test_memory_efficiency():
    """Test that the new system is more memory efficient."""
    registry = AbstractStyleRegistry()

    # Create many "different" styles that are actually the same
    styles = []
    for i in range(100):
        # All these styles are identical
        style = AbstractStyle(
            font_size=16,
            font_weight=FontWeight.NORMAL,
            color="black"
        )
        style_id, _ = registry.get_or_create_style(style)
        styles.append(style_id)

    # All should reference the same style
    assert len(set(styles)) == 1  # All IDs are the same
    assert registry.get_style_count() == 2  # Only default + our style

    # This demonstrates that we don't create duplicate styles


def test_word_style_reference_concept():
    """Demonstrate how words would reference styles instead of storing fonts."""
    registry = AbstractStyleRegistry()

    # Create paragraph style
    para_style = AbstractStyle(font_size=16, color="black")
    para_id, _ = registry.get_or_create_style(para_style)

    # Create bold word style
    bold_style = AbstractStyle(font_size=16, color="black", font_weight=FontWeight.BOLD)
    bold_id, _ = registry.get_or_create_style(bold_style)

    # Simulate words storing style IDs instead of full Font objects
    words_data = [
        {"text": "This", "style_id": para_id},
        {"text": "is", "style_id": para_id},
        {"text": "bold", "style_id": bold_id},
        {"text": "text", "style_id": para_id},
    ]

    # To get the actual font for rendering, we resolve through registry
    context = RenderingContext()
    concrete_registry = ConcreteStyleRegistry(StyleResolver(context))

    for word_data in words_data:
        abstract_style = registry.get_style_by_id(word_data["style_id"])
        font = concrete_registry.get_font(abstract_style)

        # Now we have the actual Font object for rendering
        assert font is not None
        assert hasattr(font, 'font_size')

        # Bold word should have bold weight
        if word_data["text"] == "bold":
            assert font.weight == FontWeight.BOLD
        else:
            assert font.weight == FontWeight.NORMAL


if __name__ == "__main__":
    pytest.main([__file__])
