"""
Unit tests for education events.

Tests education-related events including school life, activities, and college events
according to TESTING_PLAN.md Section 4.4.

Test Coverage:
- Elementary school events (ages 5-12)
- Middle/High school events (ages 13-18)
- College events (ages 18+)
- Extracurricular activities
- Academic performance events
"""
import sys
import os
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../..')))

import pytest
import random
from unittest.mock import Mock, patch
from types import SimpleNamespace


# ============================================================================
# Test Fixtures
# ============================================================================

@pytest.fixture
def child_player_school():
    """Create a child player enrolled in elementary school (age 8)."""
    from tests.utils.player_factory import create_minimal_player
    player = create_minimal_player(age=8, name='Test Child', sex='Female', occupation='student')
    player.c.education = 'elementary'
    player.c.location = 'school'
    player.hourOfDay = 10
    return player


@pytest.fixture
def teen_player_highschool():
    """Create a teenage player in high school (age 16)."""
    from tests.utils.player_factory import create_minimal_player
    player = create_minimal_player(age=16, name='Test Teen', sex='Male', occupation='student', money=500)
    player.c.education = 'high_school'
    player.c.location = 'school'
    player.c.actScore = 25
    player.hourOfDay = 14
    return player


@pytest.fixture
def college_player():
    """Create a college student player (age 19)."""
    from tests.utils.player_factory import create_minimal_player
    player = create_minimal_player(age=19, name='Test College', sex='Female', occupation='student', money=1000)
    player.c.education = 'college'
    player.c.location = 'school'
    player.c.actScore = 30
    return player


@pytest.fixture
def kindergarten_player():
    """Create a kindergarten-age player (age 5)."""
    from tests.utils.player_factory import create_minimal_player
    player = create_minimal_player(age=5, name='Test Kinder', sex='Male', occupation='student')
    player.c.education = 'kindergarten'
    player.c.location = 'school'
    return player


# ============================================================================
# School Life Events - Elementary/Middle School
# ============================================================================

class TestLikeSchoolEvent:
    """Tests for likeSchool event (kindergarten reaction)."""

    def test_like_school_triggers_at_correct_age(self, kindergarten_player):
        """Event triggers for kindergarten age (4-7)."""
        from events.education.school_life import likeSchool

        # Set random to always trigger
        with patch('random.random', return_value=0.0001):
            event = likeSchool(kindergarten_player, type='message')
            assert event is not None
            assert event.type == 'questionEvent'

    def test_like_school_age_range_enforcement(self):
        """Event does not trigger outside age range."""
        from events.education.school_life import likeSchool
        from tests.utils.player_factory import create_minimal_player

        # Too young (age 3)
        young_player = create_minimal_player(age=3, occupation='student')
        young_player.c.education = 'kindergarten'
        with patch('random.random', return_value=0.0001):
            event = likeSchool(young_player, type='message')
            assert event is None

        # Too old (age 10)
        old_player = create_minimal_player(age=10, occupation='student')
        old_player.c.education = 'elementary'
        with patch('random.random', return_value=0.0001):
            event = likeSchool(old_player, type='message')
            assert event is None

    def test_like_school_not_duplicate(self, kindergarten_player):
        """Event does not trigger if already asked."""
        from events.education.school_life import likeSchool

        kindergarten_player.askedQuestions.add('likeSchool')
        with patch('random.random', return_value=0.0001):
            event = likeSchool(kindergarten_player, type='message')
            assert event is None

    def test_like_school_positive_response(self, kindergarten_player):
        """Positive response increases happiness."""
        from events.education.school_life import likeSchool

        initial_happiness = kindergarten_player.c.happiness
        response = {'option': 'Yes'}
        likeSchool(kindergarten_player, type='answer', response=response)
        assert kindergarten_player.c.happiness == initial_happiness + 10

    def test_like_school_negative_response(self, kindergarten_player):
        """Negative response decreases happiness."""
        from events.education.school_life import likeSchool

        initial_happiness = kindergarten_player.c.happiness
        response = {'option': 'No'}
        likeSchool(kindergarten_player, type='answer', response=response)
        assert kindergarten_player.c.happiness == initial_happiness - 10


class TestDropBooksEvent:
    """Tests for dropBooks event (helping classmate)."""

    def test_drop_books_requires_school_location(self, child_player_school):
        """Event only triggers at school location."""
        from events.education.school_life import dropBooks

        # At school - should trigger
        child_player_school.c.location = 'school'
        with patch('random.random', return_value=0.0001):
            event = dropBooks(child_player_school, type='message')
            assert event is not None

        # At home - should not trigger
        child_player_school.c.location = 'home'
        with patch('random.random', return_value=0.0001):
            event = dropBooks(child_player_school, type='message')
            assert event is None

    def test_drop_books_not_duplicate(self, child_player_school):
        """Event fname not in askedQuestions."""
        from events.education.school_life import dropBooks

        child_player_school.askedQuestions.add('dropBooks')
        with patch('random.random', return_value=0.0001):
            event = dropBooks(child_player_school, type='message')
            assert event is None

    def test_drop_books_apply_costs(self, child_player_school):
        """Event answer options have correct costs."""
        from events.education.school_life import dropBooks

        with patch('random.random', return_value=0.0001):
            event = dropBooks(child_player_school, type='message')
            assert event is not None

            # First option costs energy
            assert event.answers[0].energyCost == 5
            # Fourth option costs diamonds
            assert event.answers[3].diamondCost == 5

    # Note: Answer consequence testing requires integration tests due to
    # object identity comparison in event handlers. See integration tests.


class TestFieldTripEvent:
    """Tests for fieldTrip event (field trip destination choice)."""

    def test_field_trip_triggers_at_correct_age(self, child_player_school):
        """Event triggers for ages 8-12."""
        from events.education.school_life import fieldTrip

        child_player_school.c.ageYears = 10
        with patch('random.random', return_value=0.001):
            event = fieldTrip(child_player_school, type='message')
            assert event is not None
            assert event.type == 'questionEvent'

    def test_field_trip_age_boundaries(self):
        """Event respects age boundaries."""
        from events.education.school_life import fieldTrip
        from tests.utils.player_factory import create_minimal_player

        # Too young (age 7)
        young = create_minimal_player(age=7, occupation='student')
        with patch('random.random', return_value=0.001):
            assert fieldTrip(young, type='message') is None

        # Too old (age 13)
        old = create_minimal_player(age=13, occupation='student')
        with patch('random.random', return_value=0.001):
            assert fieldTrip(old, type='message') is None

    def test_field_trip_costs_money(self, child_player_school):
        """All field trip options have money costs."""
        from events.education.school_life import fieldTrip

        with patch('random.random', return_value=0.001):
            event = fieldTrip(child_player_school, type='message')
            assert event is not None

            # All options should cost money
            for answer in event.answers:
                assert answer.moneyCost > 0

    # Note: Answer consequence testing requires integration tests due to
    # object identity comparison in event handlers. See integration tests.


# ============================================================================
# School Life Events - High School
# ============================================================================

class TestSchoolFightEvent:
    """Tests for schoolFight event (fight at school)."""

    def test_school_fight_requires_age_and_location(self, teen_player_highschool):
        """Event requires age 14-18 and school location."""
        from events.education.school_life import schoolFight

        teen_player_highschool.c.ageYears = 16
        teen_player_highschool.c.location = 'school'
        with patch('random.random', return_value=0.001):
            event = schoolFight(teen_player_highschool, type='message')
            assert event is not None

    def test_school_fight_age_restriction(self):
        """Event only triggers for teens (14-18)."""
        from events.education.school_life import schoolFight
        from tests.utils.player_factory import create_minimal_player

        # Too young (age 13)
        young = create_minimal_player(age=13, occupation='student')
        young.c.location = 'school'
        with patch('random.random', return_value=0.001):
            assert schoolFight(young, type='message') is None

        # Too old (age 19)
        adult = create_minimal_player(age=19, occupation='student')
        adult.c.location = 'school'
        with patch('random.random', return_value=0.001):
            assert schoolFight(adult, type='message') is None

    # Note: Answer consequence testing requires integration tests due to
    # object identity comparison in event handlers. See integration tests.


class TestSchoolLunchEvent:
    """Tests for schoolLunch event (lunch choice)."""

    def test_school_lunch_triggers_at_noon(self, child_player_school):
        """Event triggers at hour 12 (noon)."""
        from events.education.school_life import schoolLunch

        child_player_school.hourOfDay = 12
        child_player_school.c.location = 'school'
        with patch('random.random', return_value=0.001):
            event = schoolLunch(child_player_school, type='message')
            assert event is not None

    def test_school_lunch_not_at_other_hours(self, child_player_school):
        """Event does not trigger outside noon hour."""
        from events.education.school_life import schoolLunch

        child_player_school.hourOfDay = 10
        child_player_school.c.location = 'school'
        with patch('random.random', return_value=0.001):
            event = schoolLunch(child_player_school, type='message')
            assert event is None

    def test_school_lunch_requires_school_location(self, child_player_school):
        """Event requires school location."""
        from events.education.school_life import schoolLunch

        child_player_school.hourOfDay = 12
        child_player_school.c.location = 'home'
        with patch('random.random', return_value=0.001):
            event = schoolLunch(child_player_school, type='message')
            assert event is None


# ============================================================================
# Quick Win Events - Pop Quiz & Academic Performance
# ============================================================================

class TestPopQuizEvent:
    """Tests for popQuiz event (surprise quiz)."""

    def test_pop_quiz_triggers_at_school(self, teen_player_highschool):
        """Event triggers at school for ages 10-18."""
        from events.education.quick_wins import popQuiz

        teen_player_highschool.c.ageYears = 16
        teen_player_highschool.c.location = 'school'
        with patch('random.random', return_value=0.001):
            event = popQuiz(teen_player_highschool, type='message')
            assert event is not None
            assert event.type == 'questionEvent'

    def test_pop_quiz_age_range(self):
        """Event respects age range 10-18."""
        from events.education.quick_wins import popQuiz
        from tests.utils.player_factory import create_minimal_player

        # Too young
        young = create_minimal_player(age=9, occupation='student')
        young.c.location = 'school'
        with patch('random.random', return_value=0.001):
            assert popQuiz(young, type='message') is None

        # In range
        valid = create_minimal_player(age=15, occupation='student')
        valid.c.location = 'school'
        with patch('random.random', return_value=0.001):
            event = popQuiz(valid, type='message')
            assert event is not None

        # Too old
        old = create_minimal_player(age=19, occupation='student')
        old.c.location = 'school'
        with patch('random.random', return_value=0.001):
            assert popQuiz(old, type='message') is None

    def test_pop_quiz_not_duplicate(self, teen_player_highschool):
        """Event does not trigger twice."""
        from events.education.quick_wins import popQuiz

        teen_player_highschool.c.location = 'school'
        teen_player_highschool.askedQuestions.add('popQuiz')
        with patch('random.random', return_value=0.001):
            event = popQuiz(teen_player_highschool, type='message')
            assert event is None

    def test_pop_quiz_try_best_choice(self, teen_player_highschool):
        """Trying best increases happiness."""
        from events.education.quick_wins import popQuiz
        from events.base import answerOption

        initial_happiness = teen_player_highschool.c.happiness
        response = {'option': 'Try your best'}
        popQuiz(teen_player_highschool, type='answer', response=response)

        assert teen_player_highschool.c.happiness == initial_happiness + 5

    def test_pop_quiz_cheat_choice(self, teen_player_highschool):
        """Cheating decreases social and happiness."""
        from events.education.quick_wins import popQuiz

        initial_happiness = teen_player_highschool.c.happiness
        initial_social = teen_player_highschool.c.social
        response = {'option': "Glance at neighbor's paper"}
        popQuiz(teen_player_highschool, type='answer', response=response)

        assert teen_player_highschool.c.happiness == initial_happiness - 10
        assert teen_player_highschool.c.social == initial_social - 5


class TestLostHomeworkEvent:
    """Tests for lostHomework event (can't find homework)."""

    def test_lost_homework_triggers_at_school_or_home(self, child_player_school):
        """Event triggers at school or home."""
        from events.education.quick_wins import lostHomework

        # At school
        child_player_school.c.location = 'school'
        child_player_school.c.ageYears = 10
        with patch('random.random', return_value=0.001):
            event = lostHomework(child_player_school, type='message')
            assert event is not None

        # At home
        child_player_school.askedQuestions.clear()
        child_player_school.c.location = 'home'
        with patch('random.random', return_value=0.001):
            event = lostHomework(child_player_school, type='message')
            assert event is not None

    def test_lost_homework_redo_costs_energy(self, child_player_school):
        """Redoing homework costs 20 energy."""
        from events.education.quick_wins import lostHomework

        child_player_school.c.location = 'school'
        with patch('random.random', return_value=0.001):
            event = lostHomework(child_player_school, type='message')
            assert event.answers[0].energyCost == 20

    def test_lost_homework_explain_to_teacher(self, child_player_school):
        """Explaining increases social and happiness."""
        from events.education.quick_wins import lostHomework

        initial_happiness = child_player_school.c.happiness
        initial_social = child_player_school.c.social
        response = {'option': 'Explain to the teacher'}
        lostHomework(child_player_school, type='answer', response=response)

        assert child_player_school.c.happiness == initial_happiness + 5
        assert child_player_school.c.social == initial_social + 5


class TestPresentationNervesEvent:
    """Tests for presentationNerves event (class presentation)."""

    def test_presentation_nerves_age_range(self):
        """Event triggers for ages 10-18."""
        from events.education.quick_wins import presentationNerves
        from tests.utils.player_factory import create_minimal_player

        valid_player = create_minimal_player(age=14, occupation='student')
        valid_player.c.location = 'school'
        with patch('random.random', return_value=0.001):
            event = presentationNerves(valid_player, type='message')
            assert event is not None

    def test_presentation_nerves_deep_breaths_choice(self, teen_player_highschool):
        """Deep breaths option increases social and happiness."""
        from events.education.quick_wins import presentationNerves

        initial_happiness = teen_player_highschool.c.happiness
        initial_social = teen_player_highschool.c.social
        response = {'option': 'Take deep breaths and go'}
        presentationNerves(teen_player_highschool, type='answer', response=response)

        assert teen_player_highschool.c.happiness == initial_happiness + 10
        assert teen_player_highschool.c.social == initial_social + 5


# ============================================================================
# Extracurricular & College Events
# ============================================================================

class TestStudyGroupInviteEvent:
    """Tests for studyGroupInvite event (study group invitation)."""

    def test_study_group_age_range(self):
        """Event triggers for ages 14-22."""
        from events.education.quick_wins import studyGroupInvite
        from tests.utils.player_factory import create_minimal_player

        # High school student
        teen = create_minimal_player(age=16, occupation='student')
        teen.c.location = 'school'
        with patch('random.random', return_value=0.001):
            event = studyGroupInvite(teen, type='message')
            assert event is not None

        # College student
        college = create_minimal_player(age=20, occupation='student')
        college.c.location = 'school'
        college.askedQuestions.clear()
        with patch('random.random', return_value=0.001):
            event = studyGroupInvite(college, type='message')
            assert event is not None

    def test_study_group_join_choice(self, teen_player_highschool):
        """Joining study group increases social and happiness."""
        from events.education.quick_wins import studyGroupInvite

        initial_happiness = teen_player_highschool.c.happiness
        initial_social = teen_player_highschool.c.social
        response = {'option': 'Yes, study together'}
        studyGroupInvite(teen_player_highschool, type='answer', response=response)

        assert teen_player_highschool.c.happiness == initial_happiness + 5
        assert teen_player_highschool.c.social == initial_social + 10

    def test_study_group_solo_choice(self, teen_player_highschool):
        """Declining decreases social."""
        from events.education.quick_wins import studyGroupInvite

        initial_social = teen_player_highschool.c.social
        response = {'option': 'No, prefer solo studying'}
        studyGroupInvite(teen_player_highschool, type='answer', response=response)

        assert teen_player_highschool.c.social == initial_social - 5


class TestExtracurricularBurnoutEvent:
    """Tests for extracurricularBurnout event (too many activities)."""

    def test_burnout_requires_activities(self, teen_player_highschool):
        """Event requires more than 2 activities."""
        from events.education.quick_wins import extracurricularBurnout

        # Not enough activities
        teen_player_highschool.c.activities = ['activity1', 'activity2']
        with patch('random.random', return_value=0.001):
            event = extracurricularBurnout(teen_player_highschool, type='message')
            assert event is None

        # Enough activities
        teen_player_highschool.c.activities = ['activity1', 'activity2', 'activity3']
        teen_player_highschool.askedQuestions.clear()
        with patch('random.random', return_value=0.001):
            event = extracurricularBurnout(teen_player_highschool, type='message')
            assert event is not None

    def test_burnout_quit_activity_choice(self, teen_player_highschool):
        """Quitting activity increases happiness and removes activity."""
        from events.education.quick_wins import extracurricularBurnout

        teen_player_highschool.c.activities = ['activity1', 'activity2', 'activity3']
        initial_activities = len(teen_player_highschool.c.activities)
        initial_happiness = teen_player_highschool.c.happiness

        response = {'option': 'Quit an activity'}
        extracurricularBurnout(teen_player_highschool, type='answer', response=response)

        assert teen_player_highschool.c.happiness == initial_happiness + 10
        assert len(teen_player_highschool.c.activities) == initial_activities - 1

    def test_burnout_power_through_choice(self, teen_player_highschool):
        """Powering through decreases happiness."""
        from events.education.quick_wins import extracurricularBurnout

        teen_player_highschool.c.activities = ['activity1', 'activity2', 'activity3']
        initial_happiness = teen_player_highschool.c.happiness

        response = {'option': 'Power through it'}
        extracurricularBurnout(teen_player_highschool, type='answer', response=response)

        assert teen_player_highschool.c.happiness == initial_happiness - 10


class TestCollegeAllNighterEvent:
    """Tests for collegeAllNighter event (all-night studying)."""

    def test_all_nighter_requires_college(self, college_player):
        """Event requires college education."""
        from events.education.quick_wins import collegeAllNighter

        college_player.c.education = 'college'
        college_player.c.ageYears = 20
        with patch('random.random', return_value=0.001):
            event = collegeAllNighter(college_player, type='message')
            assert event is not None

    def test_all_nighter_not_for_high_school(self, teen_player_highschool):
        """Event does not trigger for high school students."""
        from events.education.quick_wins import collegeAllNighter

        teen_player_highschool.c.education = 'high_school'
        with patch('random.random', return_value=0.001):
            event = collegeAllNighter(teen_player_highschool, type='message')
            assert event is None

    def test_all_nighter_study_all_night_choice(self, college_player):
        """Studying all night costs energy and decreases happiness."""
        from events.education.quick_wins import collegeAllNighter

        initial_happiness = college_player.c.happiness
        response = {'option': 'Yes, study all night'}
        collegeAllNighter(college_player, type='answer', response=response)

        assert college_player.c.happiness == initial_happiness - 10

    def test_all_nighter_sleep_choice(self, college_player):
        """Choosing sleep increases happiness and energy."""
        from events.education.quick_wins import collegeAllNighter

        initial_happiness = college_player.c.happiness
        initial_energy = college_player.c.energy
        response = {'option': 'No, get sleep instead'}
        collegeAllNighter(college_player, type='answer', response=response)

        assert college_player.c.happiness == initial_happiness + 5
        assert college_player.c.energy == initial_energy + 10


# ============================================================================
# Message Events (No Choices)
# ============================================================================

class TestMessageEvents:
    """Tests for message-only education events."""

    def test_raised_hand_not_called_triggers(self, child_player_school):
        """raisedHandNotCalled triggers at school for ages 8-18."""
        from events.education.quick_wins import raisedHandNotCalled

        child_player_school.c.ageYears = 10
        child_player_school.c.location = 'school'
        with patch('random.random', return_value=0.001):
            event = raisedHandNotCalled(child_player_school, type='message')
            assert event is not None
            assert event.type == 'messageEvent'

    def test_teacher_favorite_triggers(self, child_player_school):
        """teacherFavorite triggers at school for ages 8-17."""
        from events.education.quick_wins import teacherFavorite

        child_player_school.c.ageYears = 12
        child_player_school.c.location = 'school'
        with patch('random.random', return_value=0.001):
            event = teacherFavorite(child_player_school, type='message')
            assert event is not None
            assert event.type == 'messageEvent'

    def test_cafeteria_food_poisoning_triggers(self, teen_player_highschool):
        """cafeteriaFoodPoisoning triggers and costs energy."""
        from events.education.quick_wins import cafeteriaFoodPoisoning

        teen_player_highschool.c.location = 'school'
        with patch('random.random', return_value=0.001):
            event = cafeteriaFoodPoisoning(teen_player_highschool, type='message')
            assert event is not None
            assert event.energyCost == 15

    def test_substitute_teacher_triggers(self, child_player_school):
        """substituteTeacher triggers at school."""
        from events.education.quick_wins import substituteTeacher

        child_player_school.c.location = 'school'
        with patch('random.random', return_value=0.001):
            event = substituteTeacher(child_player_school, type='message')
            assert event is not None
            assert event.type == 'messageEvent'


# ============================================================================
# Event Deduplication Tests
# ============================================================================

class TestEventDeduplication:
    """Tests that events don't trigger multiple times."""

    def test_question_events_use_asked_questions(self, child_player_school):
        """Question events check askedQuestions set."""
        from events.education.school_life import dropBooks

        # First time - should trigger
        with patch('random.random', return_value=0.0001):
            event1 = dropBooks(child_player_school, type='message')
            assert event1 is not None

        # Add to askedQuestions
        child_player_school.askedQuestions.add('dropBooks')

        # Second time - should not trigger
        with patch('random.random', return_value=0.0001):
            event2 = dropBooks(child_player_school, type='message')
            assert event2 is None

    def test_message_events_use_events_set(self, child_player_school):
        """Message events check events set."""
        from events.education.quick_wins import raisedHandNotCalled

        # First time - should trigger
        child_player_school.c.location = 'school'
        with patch('random.random', return_value=0.001):
            event1 = raisedHandNotCalled(child_player_school, type='message')
            assert event1 is not None

        # Add to events
        child_player_school.events.add('raisedHandNotCalled')

        # Second time - should not trigger
        with patch('random.random', return_value=0.001):
            event2 = raisedHandNotCalled(child_player_school, type='message')
            assert event2 is None


# ============================================================================
# Random Seed Control
# ============================================================================

class TestRandomSeedControl:
    """Tests for controlling random event triggering."""

    def test_random_seed_reproducible(self, child_player_school):
        """Events trigger reproducibly with same random seed."""
        from events.education.school_life import dropBooks

        child_player_school.c.location = 'school'

        # Set seed and test
        random.seed(42)
        result1 = dropBooks(child_player_school, type='message')

        # Reset seed and test again
        child_player_school.askedQuestions.clear()
        random.seed(42)
        result2 = dropBooks(child_player_school, type='message')

        # Results should be identical
        assert (result1 is None) == (result2 is None)
