"""
Unit tests for EventRegistry (Section 4.1 of TESTING_PLAN.md)

Tests the O(1) indexing optimization and condition-based filtering
for game events.
"""

import pytest
import sys
from pathlib import Path

# Add ws/ to Python path
ws_path = Path(__file__).parent.parent.parent / 'ws'
sys.path.insert(0, str(ws_path))

from event_registry import EventRegistry

# Import from ws/tests/utils
sys.path.insert(0, str(ws_path / 'tests'))
from utils.player_factory import create_minimal_player


# ============================================================================
# Helper Functions
# ============================================================================

def dummy_event_func(player, mode='check'):
    """Dummy event function for testing"""
    return {'id': 'test_event', 'type': 'message', 'title': 'Test Event'}


def another_event_func(player, mode='check'):
    """Another dummy event function"""
    return {'id': 'another_event', 'type': 'message', 'title': 'Another Event'}


# ============================================================================
# Event Registration Tests
# ============================================================================

def test_register_event_adds_to_registry():
    """
    Test that registering an event adds it to the registry.

    Arrange: Create empty EventRegistry
    Act: Register an event
    Assert: Event appears in registry
    """
    # Arrange
    registry = EventRegistry()

    # Act
    registry.register('test_event', dummy_event_func)

    # Assert
    assert registry.count() == 1
    assert 'test_event' in registry.list_events()


def test_register_event_with_age_range():
    """
    Test that events with age ranges are indexed by age.

    Arrange: Create EventRegistry
    Act: Register event with age_range
    Assert: Event indexed correctly and returned for matching age
    """
    # Arrange
    registry = EventRegistry()
    player = create_minimal_player(age=10)

    # Act
    registry.register('child_event', dummy_event_func, age_range=(5, 12))

    # Assert
    assert registry.count() == 1
    applicable = registry.get_applicable_events(player)
    assert len(applicable) == 1
    assert applicable[0]['id'] == 'child_event'


def test_register_event_with_conditions():
    """
    Test that events are indexed by conditions.

    Tests multiple condition types:
    - requires_relationship
    - requires_job
    - day_of_year

    Arrange: Create EventRegistry
    Act: Register events with various conditions
    Assert: Events stored with correct conditions
    """
    # Arrange
    registry = EventRegistry()

    # Act
    registry.register(
        'relationship_event',
        dummy_event_func,
        age_range=(18, 65),
        requires_relationship=True
    )
    registry.register(
        'job_event',
        another_event_func,
        age_range=(16, 70),
        requires_job=True
    )
    registry.register(
        'holiday_event',
        dummy_event_func,
        day_of_year=1
    )

    # Assert
    assert registry.count() == 3
    events = registry.list_events()
    assert 'relationship_event' in events
    assert 'job_event' in events
    assert 'holiday_event' in events


def test_register_duplicate_event_raises_error():
    """
    Test that registering the same event_id twice is handled gracefully.

    Arrange: Create EventRegistry and register event
    Act: Try to register same event_id again
    Assert: Duplicate registration is logged but doesn't error
    """
    # Arrange
    registry = EventRegistry()
    registry.register('test_event', dummy_event_func)

    # Act - register duplicate (should not raise error, just log)
    registry.register('test_event', another_event_func)

    # Assert - count should still be 1 (duplicate ignored)
    assert registry.count() == 1


def test_register_event_without_conditions():
    """
    Test that events without conditions are added to _no_conditions list.

    Arrange: Create EventRegistry
    Act: Register event without any conditions
    Assert: Event added to _no_conditions list and returned for all players
    """
    # Arrange
    registry = EventRegistry()
    player = create_minimal_player(age=25)

    # Act
    registry.register('universal_event', dummy_event_func)

    # Assert
    assert registry.count() == 1
    applicable = registry.get_applicable_events(player)
    assert len(applicable) == 1
    assert applicable[0]['id'] == 'universal_event'


# ============================================================================
# Event Filtering Tests
# ============================================================================

def test_get_applicable_events_filters_by_age():
    """
    Test that only events matching player's age are returned.

    Arrange: Register events for different age ranges
    Act: Get applicable events for specific age
    Assert: Only age-appropriate events returned
    """
    # Arrange
    registry = EventRegistry()
    registry.register('child_event', dummy_event_func, age_range=(0, 12))
    registry.register('teen_event', another_event_func, age_range=(13, 19))
    registry.register('adult_event', dummy_event_func, age_range=(20, 65))

    # Act - teenager player
    teen_player = create_minimal_player(age=16)
    applicable = registry.get_applicable_events(teen_player)

    # Assert - only teen event should be returned
    assert len(applicable) == 1
    assert applicable[0]['id'] == 'teen_event'


def test_get_applicable_events_requires_relationship():
    """
    Test that events requiring relationships only return if player has them.

    Arrange: Register event requiring relationships
    Act: Get applicable events for player without relationships
    Assert: Event not returned
    """
    # Arrange
    registry = EventRegistry()
    registry.register(
        'date_event',
        dummy_event_func,
        age_range=(16, 65),
        requires_relationship=True
    )

    # Act - player without relationships
    player = create_minimal_player(age=20)
    player.r = []  # No relationships
    applicable = registry.get_applicable_events(player)

    # Assert - event should not be returned
    assert len(applicable) == 0

    # Act - player WITH relationships
    from types import SimpleNamespace
    player.r = [SimpleNamespace(id='friend1', affinity=50)]
    applicable = registry.get_applicable_events(player)

    # Assert - event should now be returned
    assert len(applicable) == 1
    assert applicable[0]['id'] == 'date_event'


def test_get_applicable_events_requires_job():
    """
    Test that events requiring jobs only return if player has one.

    Arrange: Register event requiring job
    Act: Get applicable events for player without job
    Assert: Event not returned
    """
    # Arrange
    registry = EventRegistry()
    registry.register(
        'promotion_event',
        dummy_event_func,
        age_range=(18, 65),
        requires_job=True
    )

    # Act - player without job
    player = create_minimal_player(age=25)
    player.c.occupation = None
    applicable = registry.get_applicable_events(player)

    # Assert - event should not be returned
    assert len(applicable) == 0

    # Act - player WITH job
    player.c.occupation = 'Software Developer'
    applicable = registry.get_applicable_events(player)

    # Assert - event should now be returned
    assert len(applicable) == 1
    assert applicable[0]['id'] == 'promotion_event'


def test_get_applicable_events_checks_day_of_year():
    """
    Test that events are filtered by day_of_year when combined with age_range.

    NOTE: Current implementation limitation - events with ONLY day_of_year
    (no age_range) won't be checked. They must have an age_range to be indexed.

    Arrange: Register event for specific day WITH age range
    Act: Get applicable events for different days
    Assert: Event only returned on correct day
    """
    # Arrange
    registry = EventRegistry()
    # Must include age_range for day_of_year filtering to work
    registry.register(
        'new_years_event',
        dummy_event_func,
        age_range=(0, 120),  # All ages
        day_of_year=1
    )

    # Act - wrong day
    player = create_minimal_player(age=25)
    player.dayOfYear = 180  # Mid-year
    applicable = registry.get_applicable_events(player)

    # Assert - event should not be returned
    assert len(applicable) == 0

    # Act - correct day
    player.dayOfYear = 1  # January 1st
    applicable = registry.get_applicable_events(player)

    # Assert - event should be returned
    assert len(applicable) == 1
    assert applicable[0]['id'] == 'new_years_event'


def test_get_applicable_events_combines_conditions():
    """
    Test that multiple conditions use AND logic.

    Arrange: Register event with multiple conditions
    Act: Test with player missing one condition
    Assert: Event not returned until all conditions met
    """
    # Arrange
    registry = EventRegistry()
    registry.register(
        'complex_event',
        dummy_event_func,
        age_range=(20, 40),
        requires_job=True,
        requires_relationship=True
    )

    # Act - player meets age only
    player = create_minimal_player(age=25)
    player.c.occupation = None
    player.r = []
    applicable = registry.get_applicable_events(player)

    # Assert - event not returned
    assert len(applicable) == 0

    # Act - player meets age and job
    player.c.occupation = 'Engineer'
    applicable = registry.get_applicable_events(player)

    # Assert - event still not returned (missing relationship)
    assert len(applicable) == 0

    # Act - player meets all conditions
    from types import SimpleNamespace
    player.r = [SimpleNamespace(id='partner', affinity=80)]
    applicable = registry.get_applicable_events(player)

    # Assert - event now returned
    assert len(applicable) == 1
    assert applicable[0]['id'] == 'complex_event'


def test_get_applicable_events_excludes_completed():
    """
    Test that events in player.events are excluded.

    Arrange: Register event and add to player.events
    Act: Get applicable events
    Assert: Completed event not returned
    """
    # Arrange
    registry = EventRegistry()
    registry.register('one_time_event', dummy_event_func, age_range=(10, 100))

    player = create_minimal_player(age=25)

    # Act - event not yet completed
    applicable = registry.get_applicable_events(player)

    # Assert - event should be returned
    assert len(applicable) == 1
    assert applicable[0]['id'] == 'one_time_event'

    # Act - mark event as completed
    player.events.add('one_time_event')
    applicable = registry.get_applicable_events(player)

    # Assert - event should not be returned
    assert len(applicable) == 0


# ============================================================================
# Performance Tests
# ============================================================================

def test_get_applicable_events_performance_1000_events():
    """
    Test O(1) filtering with large registry (1000 events).

    Arrange: Register 1000 events with different age ranges
    Act: Get applicable events
    Assert: Performance is acceptable (O(1) not O(n))
    """
    # Arrange
    import time
    registry = EventRegistry()

    # Register 1000 events across different age ranges
    for i in range(1000):
        age_min = i % 100
        age_max = age_min + 10
        registry.register(
            f'event_{i}',
            dummy_event_func,
            age_range=(age_min, age_max)
        )

    player = create_minimal_player(age=25)

    # Act - measure time to get applicable events
    start_time = time.time()
    applicable = registry.get_applicable_events(player)
    elapsed_time = time.time() - start_time

    # Assert - should complete in < 10ms (O(1) filtering)
    assert elapsed_time < 0.01  # 10ms

    # Assert - should return only events for age 25
    assert len(applicable) > 0
    for event in applicable:
        event_data = registry._events[event['id']]
        if event_data['age_range']:
            min_age, max_age = event_data['age_range']
            assert min_age <= 25 <= max_age


# ============================================================================
# Edge Cases Tests
# ============================================================================

def test_register_event_with_partial_age_range():
    """
    Test registering event with only min_age or only max_age.

    Note: Current implementation requires tuple, but this tests the design.

    Arrange: Create EventRegistry
    Act: Register event with age range boundaries
    Assert: Event handles edge cases correctly
    """
    # Arrange
    registry = EventRegistry()
    player_young = create_minimal_player(age=5)
    player_old = create_minimal_player(age=90)

    # Act - register events with full range
    registry.register('young_event', dummy_event_func, age_range=(0, 18))
    registry.register('old_event', another_event_func, age_range=(65, 120))

    # Assert - young player
    applicable = registry.get_applicable_events(player_young)
    assert len(applicable) == 1
    assert applicable[0]['id'] == 'young_event'

    # Assert - old player
    applicable = registry.get_applicable_events(player_old)
    assert len(applicable) == 1
    assert applicable[0]['id'] == 'old_event'


def test_get_applicable_events_empty_registry():
    """
    Test that empty registry returns empty list.

    Arrange: Create empty EventRegistry
    Act: Get applicable events
    Assert: Empty list returned
    """
    # Arrange
    registry = EventRegistry()
    player = create_minimal_player(age=25)

    # Act
    applicable = registry.get_applicable_events(player)

    # Assert
    assert len(applicable) == 0
    assert isinstance(applicable, list)


def test_get_applicable_events_all_filtered_out():
    """
    Test that all events filtered out returns empty list.

    Arrange: Register events that won't match player
    Act: Get applicable events
    Assert: Empty list returned
    """
    # Arrange
    registry = EventRegistry()
    registry.register('child_only', dummy_event_func, age_range=(0, 12))
    registry.register('senior_only', another_event_func, age_range=(65, 120))
    registry.register('job_required', dummy_event_func, age_range=(18, 65), requires_job=True)

    # Act - young adult without job
    player = create_minimal_player(age=25)
    player.c.occupation = None
    applicable = registry.get_applicable_events(player)

    # Assert
    assert len(applicable) == 0


# ============================================================================
# Additional Tests for Complete Coverage
# ============================================================================

def test_event_info_structure():
    """
    Test that event info returned has correct structure.

    Arrange: Register event with all fields
    Act: Get applicable events
    Assert: Event info has all required fields
    """
    # Arrange
    registry = EventRegistry()
    registry.register(
        'full_event',
        dummy_event_func,
        age_range=(20, 30),
        requires_job=True,
        requires_relationship=True,
        day_of_year=100
    )

    player = create_minimal_player(age=25)
    player.c.occupation = 'Engineer'
    from types import SimpleNamespace
    player.r = [SimpleNamespace(id='friend', affinity=50)]
    player.dayOfYear = 100

    # Act
    applicable = registry.get_applicable_events(player)

    # Assert
    assert len(applicable) == 1
    event_info = applicable[0]

    # Check all expected fields
    assert 'id' in event_info
    assert 'func' in event_info
    assert 'age_range' in event_info
    assert 'requires_relationship' in event_info
    assert 'requires_job' in event_info
    assert 'day_of_year' in event_info

    # Verify values
    assert event_info['id'] == 'full_event'
    assert callable(event_info['func'])
    assert event_info['age_range'] == (20, 30)
    assert event_info['requires_relationship'] is True
    assert event_info['requires_job'] is True
    assert event_info['day_of_year'] == 100


def test_list_events_returns_all_ids():
    """
    Test that list_events() returns all registered event IDs.

    Arrange: Register multiple events
    Act: Call list_events()
    Assert: All event IDs returned
    """
    # Arrange
    registry = EventRegistry()
    registry.register('event_1', dummy_event_func)
    registry.register('event_2', another_event_func)
    registry.register('event_3', dummy_event_func, age_range=(10, 20))

    # Act
    event_ids = registry.list_events()

    # Assert
    assert len(event_ids) == 3
    assert 'event_1' in event_ids
    assert 'event_2' in event_ids
    assert 'event_3' in event_ids


def test_multiple_events_same_age_range():
    """
    Test that multiple events can share the same age range.

    Arrange: Register multiple events with same age range
    Act: Get applicable events
    Assert: All matching events returned
    """
    # Arrange
    registry = EventRegistry()
    registry.register('teen_event_1', dummy_event_func, age_range=(13, 19))
    registry.register('teen_event_2', another_event_func, age_range=(13, 19))

    player = create_minimal_player(age=16)

    # Act
    applicable = registry.get_applicable_events(player)

    # Assert
    assert len(applicable) == 2
    event_ids = [e['id'] for e in applicable]
    assert 'teen_event_1' in event_ids
    assert 'teen_event_2' in event_ids


def test_global_registry_functions():
    """
    Test the global registry wrapper functions.

    Arrange: Use global registry functions
    Act: Register and retrieve events using global functions
    Assert: Global registry works correctly
    """
    # Import global functions
    from event_registry import register_event, get_applicable_events, event_count

    # Clear any existing events from global registry
    from event_registry import _registry
    initial_count = _registry.count()

    # Act - register event using global function
    register_event('global_test_event', dummy_event_func, age_range=(20, 30))

    # Assert - count increased
    assert event_count() == initial_count + 1

    # Act - get applicable events using global function
    player = create_minimal_player(age=25)
    applicable = get_applicable_events(player)

    # Assert - our event is in the results
    event_ids = [e['id'] for e in applicable]
    assert 'global_test_event' in event_ids
