"""
Unit Tests for Random Events

Tests random positive and negative events from ws/events/random/ including:
- Positive Events: foundAPenny, freeConcert, foundMoneyStreet, freeUpgrade,
                   unexpectedCompliment, perfectParkingSpot, rainbowSighting
- Negative Events: spilledDrink, phoneDropped, birdPooped, forgotWallet,
                   missedBus, badHairDay, caughtInRain, lostKeys,
                   phoneBreakdown, dataLoss, socialMediaCanceled, cyberbullying

Test Pattern (per TESTING_PLAN.md Section 4.4):
- test_event_triggers_at_correct_age - Event only triggers in age range
- test_event_requires_conditions - Event needs specific conditions met
- test_event_not_duplicate - Event fname not in player.events
- test_event_applies_costs_correctly - Money/energy/stats updated
- test_event_randomness - Random probability respected
"""

import pytest
import random
from unittest.mock import patch

from ws.core.models import playerClass, personClass
from ws.events.base import messageEvent, questionEvent, answerOption

# Import random event functions
from ws.events.random import positive as positive_events
from ws.events.random import negative as negative_events

# Positive events
foundAPenny = positive_events.foundAPenny
freeConcert = positive_events.freeConcert
foundMoneyStreet = positive_events.foundMoneyStreet
freeUpgrade = positive_events.freeUpgrade
unexpectedCompliment = positive_events.unexpectedCompliment
perfectParkingSpot = positive_events.perfectParkingSpot
rainbowSighting = positive_events.rainbowSighting

# Negative events
spilledDrink = negative_events.spilledDrink
phoneDropped = negative_events.phoneDropped
birdPooped = negative_events.birdPooped
forgotWallet = negative_events.forgotWallet
missedBus = negative_events.missedBus
badHairDay = negative_events.badHairDay
caughtInRain = negative_events.caughtInRain
lostKeys = negative_events.lostKeys
phoneBreakdown = negative_events.phoneBreakdown
dataLoss = negative_events.dataLoss


# ============================================================================
# FIXTURES
# ============================================================================

@pytest.fixture
def child_player():
    """Create a player with child character (age 8)"""
    player = playerClass()
    player.c = personClass()
    player.c.firstname = "Child"
    player.c.lastname = "TestKid"
    player.c.ageYears = 8
    player.c.ageDays = 365 * 8
    player.c.energy = 100
    player.c.happiness = 75
    player.c.health = 80
    player.c.social = 50
    player.c.money = 100
    player.c.canDrive = False
    player.c.location = "home123"
    player.events = set()
    player.askedQuestions = set()
    player.messageQueue = []
    player.date = "01-15"
    player.hourOfDay = 12
    player.gameSpeed = 1000
    player.previousGameSpeed = 1000
    return player


@pytest.fixture
def teen_player():
    """Create a player with teenager character (age 16)"""
    player = playerClass()
    player.c = personClass()
    player.c.firstname = "Teen"
    player.c.lastname = "TestKid"
    player.c.ageYears = 16
    player.c.ageDays = 365 * 16
    player.c.energy = 100
    player.c.happiness = 75
    player.c.health = 80
    player.c.social = 50
    player.c.money = 500
    player.c.canDrive = True
    player.c.location = "home123"
    player.c.stress = 20
    player.events = set()
    player.askedQuestions = set()
    player.messageQueue = []
    player.date = "01-15"
    player.hourOfDay = 12
    player.gameSpeed = 1000
    player.previousGameSpeed = 1000
    return player


@pytest.fixture
def adult_player():
    """Create a player with adult character (age 30)"""
    player = playerClass()
    player.c = personClass()
    player.c.firstname = "Adult"
    player.c.lastname = "TestPerson"
    player.c.ageYears = 30
    player.c.ageDays = 365 * 30
    player.c.energy = 80
    player.c.happiness = 70
    player.c.health = 75
    player.c.social = 60
    player.c.money = 2000
    player.c.stress = 30
    player.c.canDrive = True
    player.c.location = "home123"
    player.events = set()
    player.askedQuestions = set()
    player.messageQueue = []
    player.date = "01-15"
    player.hourOfDay = 12
    player.gameSpeed = 1000
    player.previousGameSpeed = 1000
    return player


# ============================================================================
# POSITIVE RANDOM EVENTS TESTS
# ============================================================================

class TestFoundAPenny:
    """Test foundAPenny questionEvent (ages 5+)"""

    @patch('random.random', return_value=0.0)
    def test_found_penny_triggers_at_correct_age(self, mock_random, child_player):
        """Event should trigger at age 5+"""
        child_player.c.ageYears = 6
        result = foundAPenny(child_player)
        assert result is not None
        assert isinstance(result, questionEvent)

    @patch('random.random', return_value=0.0)
    def test_found_penny_not_before_age(self, mock_random, child_player):
        """Event should not trigger before age 5"""
        child_player.c.ageYears = 4
        result = foundAPenny(child_player)
        assert result is None

    @patch('random.random', return_value=0.0)
    def test_found_penny_not_duplicate(self, mock_random, child_player):
        """Event should not trigger if already in askedQuestions"""
        child_player.c.ageYears = 8
        child_player.askedQuestions.add('foundAPenny')
        result = foundAPenny(child_player)
        assert result is None

    def test_found_penny_answer_pick_up(self, child_player):
        """Picking up penny should add small money and happiness"""
        child_player.c.money = 10.0
        child_player.c.happiness = 70
        response = {'option': 'Pick it up'}
        foundAPenny(child_player, type='answer', response=response)
        assert child_player.c.money == 10.01
        assert child_player.c.happiness == 75


class TestFreeConcert:
    """Test freeConcert questionEvent (ages 14-100)"""

    @patch('random.random', return_value=0.0)
    def test_free_concert_triggers_at_correct_age(self, mock_random, teen_player):
        """Event should trigger between ages 14-100"""
        teen_player.c.ageYears = 18
        result = freeConcert(teen_player)
        assert result is not None
        assert isinstance(result, questionEvent)

    @patch('random.random', return_value=0.0)
    def test_free_concert_not_before_age(self, mock_random, child_player):
        """Event should not trigger before age 14"""
        child_player.c.ageYears = 13
        result = freeConcert(child_player)
        assert result is None

    def test_free_concert_answer_yes(self, teen_player):
        """Winning tickets should increase happiness"""
        teen_player.c.happiness = 70
        response = {'option': 'Yes'}
        freeConcert(teen_player, type='answer', response=response)
        assert teen_player.c.happiness == 80
        assert len(teen_player.messageQueue) > 0


class TestFoundMoneyStreet:
    """Test foundMoneyStreet messageEvent (ages 8-100)"""

    @patch('random.random', return_value=0.0)
    def test_found_money_triggers_at_correct_age(self, mock_random, child_player):
        """Event should trigger at age 8+"""
        child_player.c.ageYears = 10
        result = foundMoneyStreet(child_player)
        assert result is not None
        assert isinstance(result, messageEvent)

    @patch('random.random', return_value=0.0)
    def test_found_money_adds_money_and_happiness(self, mock_random, child_player):
        """Event should add $20 and increase happiness"""
        child_player.c.ageYears = 10
        child_player.c.money = 50
        child_player.c.happiness = 60
        result = foundMoneyStreet(child_player)
        assert child_player.c.money == 70
        assert child_player.c.happiness == 70
        assert 'foundMoneyStreet' in child_player.events


class TestFreeUpgrade:
    """Test freeUpgrade messageEvent (ages 16-100)"""

    @patch('random.random', return_value=0.0)
    def test_free_upgrade_triggers_at_correct_age(self, mock_random, teen_player):
        """Event should trigger at age 16+"""
        teen_player.c.ageYears = 20
        result = freeUpgrade(teen_player)
        assert result is not None
        assert isinstance(result, messageEvent)

    @patch('random.random', return_value=0.0)
    def test_free_upgrade_increases_happiness(self, mock_random, teen_player):
        """Event should increase happiness"""
        teen_player.c.ageYears = 20
        teen_player.c.happiness = 60
        result = freeUpgrade(teen_player)
        assert teen_player.c.happiness == 75


class TestUnexpectedCompliment:
    """Test unexpectedCompliment messageEvent (ages 10-100)"""

    @patch('random.random', return_value=0.0)
    def test_unexpected_compliment_triggers_at_correct_age(self, mock_random, teen_player):
        """Event should trigger at age 10+"""
        teen_player.c.ageYears = 15
        result = unexpectedCompliment(teen_player)
        assert result is not None
        assert isinstance(result, messageEvent)

    @patch('random.random', return_value=0.0)
    def test_unexpected_compliment_increases_happiness_and_social(self, mock_random, teen_player):
        """Event should increase happiness and social"""
        teen_player.c.ageYears = 15
        teen_player.c.happiness = 60
        teen_player.c.social = 50
        result = unexpectedCompliment(teen_player)
        assert teen_player.c.happiness == 70
        assert teen_player.c.social == 55


class TestPerfectParkingSpot:
    """Test perfectParkingSpot messageEvent (ages 16+, requires canDrive)"""

    @patch('random.random', return_value=0.0)
    def test_perfect_parking_requires_can_drive(self, mock_random, teen_player):
        """Event should require canDrive attribute"""
        teen_player.c.ageYears = 18
        teen_player.c.canDrive = True
        result = perfectParkingSpot(teen_player)
        assert result is not None
        assert isinstance(result, messageEvent)

    @patch('random.random', return_value=0.0)
    def test_perfect_parking_not_without_driving(self, mock_random, teen_player):
        """Event should not trigger if can't drive"""
        teen_player.c.ageYears = 18
        teen_player.c.canDrive = False
        result = perfectParkingSpot(teen_player)
        assert result is None


class TestRainbowSighting:
    """Test rainbowSighting messageEvent (ages 3-100)"""

    @patch('random.random', return_value=0.0)
    def test_rainbow_sighting_triggers_at_correct_age(self, mock_random, child_player):
        """Event should trigger at age 3+"""
        child_player.c.ageYears = 5
        result = rainbowSighting(child_player)
        assert result is not None
        assert isinstance(result, messageEvent)

    @patch('random.random', return_value=0.0)
    def test_rainbow_sighting_increases_happiness(self, mock_random, child_player):
        """Event should increase happiness"""
        child_player.c.ageYears = 5
        child_player.c.happiness = 60
        result = rainbowSighting(child_player)
        assert child_player.c.happiness == 70


# ============================================================================
# NEGATIVE RANDOM EVENTS TESTS
# ============================================================================

class TestSpilledDrink:
    """Test spilledDrink messageEvent (ages 8-100)"""

    @patch('random.random', return_value=0.0)
    def test_spilled_drink_triggers_at_correct_age(self, mock_random, child_player):
        """Event should trigger at age 8+"""
        child_player.c.ageYears = 10
        result = spilledDrink(child_player)
        assert result is not None
        assert isinstance(result, messageEvent)

    @patch('random.random', return_value=0.0)
    def test_spilled_drink_decreases_happiness_and_energy(self, mock_random, child_player):
        """Event should decrease happiness and energy"""
        child_player.c.ageYears = 10
        child_player.c.happiness = 70
        child_player.c.energy = 100
        result = spilledDrink(child_player)
        assert child_player.c.happiness == 60
        assert child_player.c.energy == 95


class TestPhoneDropped:
    """Test phoneDropped questionEvent with random outcomes (ages 12-100)"""

    @patch('random.random', return_value=0.0)
    def test_phone_dropped_triggers_at_correct_age(self, mock_random, teen_player):
        """Event should trigger at age 12+"""
        teen_player.c.ageYears = 16
        result = phoneDropped(teen_player)
        assert result is not None
        assert isinstance(result, questionEvent)

    @patch('random.random', return_value=0.0)
    def test_phone_dropped_not_before_age(self, mock_random, child_player):
        """Event should not trigger before age 12"""
        child_player.c.ageYears = 11
        result = phoneDropped(child_player)
        assert result is None

    @patch('random.choice', return_value=1)  # Screen cracked outcome
    def test_phone_dropped_answer_screen_cracked(self, mock_choice, teen_player):
        """Screen cracked outcome should cost money and happiness"""
        teen_player.c.money = 500
        teen_player.c.happiness = 70
        response = {'option': "It's fine!"}
        phoneDropped(teen_player, type='answer', response=response)
        assert teen_player.c.money == 300
        assert teen_player.c.happiness == 55


class TestBirdPooped:
    """Test birdPooped messageEvent (ages 5-100)"""

    @patch('random.random', return_value=0.0)
    def test_bird_pooped_triggers_at_correct_age(self, mock_random, child_player):
        """Event should trigger at age 5+"""
        child_player.c.ageYears = 8
        result = birdPooped(child_player)
        assert result is not None
        assert isinstance(result, messageEvent)

    @patch('random.random', return_value=0.0)
    def test_bird_pooped_decreases_happiness(self, mock_random, child_player):
        """Event should decrease happiness"""
        child_player.c.ageYears = 8
        child_player.c.happiness = 70
        result = birdPooped(child_player)
        assert child_player.c.happiness == 60


class TestForgotWallet:
    """Test forgotWallet messageEvent (ages 16-100)"""

    @patch('random.random', return_value=0.0)
    def test_forgot_wallet_triggers_at_correct_age(self, mock_random, teen_player):
        """Event should trigger at age 16+"""
        teen_player.c.ageYears = 18
        result = forgotWallet(teen_player)
        assert result is not None
        assert isinstance(result, messageEvent)

    @patch('random.random', return_value=0.0)
    def test_forgot_wallet_decreases_happiness_and_social(self, mock_random, teen_player):
        """Event should decrease happiness and social"""
        teen_player.c.ageYears = 18
        teen_player.c.happiness = 70
        teen_player.c.social = 60
        result = forgotWallet(teen_player)
        assert teen_player.c.happiness == 55
        assert teen_player.c.social == 50


class TestMissedBus:
    """Test missedBus messageEvent (ages 10-100)"""

    @patch('random.random', return_value=0.0)
    def test_missed_bus_triggers_at_correct_age(self, mock_random, teen_player):
        """Event should trigger at age 10+"""
        teen_player.c.ageYears = 14
        result = missedBus(teen_player)
        assert result is not None
        assert isinstance(result, messageEvent)

    @patch('random.random', return_value=0.0)
    def test_missed_bus_decreases_happiness_and_energy(self, mock_random, teen_player):
        """Event should decrease happiness and energy"""
        teen_player.c.ageYears = 14
        teen_player.c.happiness = 70
        teen_player.c.energy = 100
        result = missedBus(teen_player)
        assert teen_player.c.happiness == 60
        assert teen_player.c.energy == 90


class TestBadHairDay:
    """Test badHairDay messageEvent (ages 12-40)"""

    @patch('random.random', return_value=0.0)
    def test_bad_hair_day_triggers_at_correct_age(self, mock_random, teen_player):
        """Event should trigger between ages 12-40"""
        teen_player.c.ageYears = 18
        result = badHairDay(teen_player)
        assert result is not None
        assert isinstance(result, messageEvent)

    @patch('random.random', return_value=0.0)
    def test_bad_hair_day_not_after_age(self, mock_random, adult_player):
        """Event should not trigger after age 40"""
        adult_player.c.ageYears = 45
        result = badHairDay(adult_player)
        assert result is None


class TestCaughtInRain:
    """Test caughtInRain messageEvent (ages 5-100)"""

    @patch('random.random', return_value=0.0)
    def test_caught_in_rain_triggers_at_correct_age(self, mock_random, child_player):
        """Event should trigger at age 5+"""
        child_player.c.ageYears = 10
        result = caughtInRain(child_player)
        assert result is not None
        assert isinstance(result, messageEvent)

    @patch('random.random', return_value=0.0)
    def test_caught_in_rain_decreases_happiness_and_health(self, mock_random, child_player):
        """Event should decrease happiness and health"""
        child_player.c.ageYears = 10
        child_player.c.happiness = 70
        child_player.c.health = 80
        result = caughtInRain(child_player)
        assert child_player.c.happiness == 60
        assert child_player.c.health == 75


class TestLostKeys:
    """Test lostKeys messageEvent (ages 12-100)"""

    @patch('random.random', return_value=0.0)
    def test_lost_keys_triggers_at_correct_age(self, mock_random, teen_player):
        """Event should trigger at age 12+"""
        teen_player.c.ageYears = 16
        result = lostKeys(teen_player)
        assert result is not None
        assert isinstance(result, messageEvent)

    @patch('random.random', return_value=0.0)
    def test_lost_keys_increases_stress_decreases_stats(self, mock_random, teen_player):
        """Event should increase stress and decrease happiness and energy"""
        teen_player.c.ageYears = 16
        teen_player.c.happiness = 70
        teen_player.c.stress = 20
        teen_player.c.energy = 100
        result = lostKeys(teen_player)
        assert teen_player.c.happiness == 60
        assert teen_player.c.stress == 35
        assert teen_player.c.energy == 90


class TestPhoneBreakdown:
    """Test phoneBreakdown messageEvent (ages 12-100)"""

    @patch('random.random', return_value=0.0)
    def test_phone_breakdown_triggers_at_correct_age(self, mock_random, teen_player):
        """Event should trigger at age 12+"""
        teen_player.c.ageYears = 16
        result = phoneBreakdown(teen_player)
        assert result is not None
        assert isinstance(result, messageEvent)

    @patch('random.random', return_value=0.0)
    def test_phone_breakdown_applies_costs_correctly(self, mock_random, teen_player):
        """Event should cost significant money and increase stress"""
        teen_player.c.ageYears = 16
        teen_player.c.money = 1000
        teen_player.c.happiness = 70
        teen_player.c.stress = 20
        result = phoneBreakdown(teen_player)
        assert teen_player.c.money == 400
        assert teen_player.c.happiness == 50
        assert teen_player.c.stress == 35


class TestDataLoss:
    """Test dataLoss messageEvent (ages 16-100)"""

    @patch('random.random', return_value=0.0)
    def test_data_loss_triggers_at_correct_age(self, mock_random, teen_player):
        """Event should trigger at age 16+"""
        teen_player.c.ageYears = 20
        result = dataLoss(teen_player)
        assert result is not None
        assert isinstance(result, messageEvent)

    @patch('random.random', return_value=0.0)
    def test_data_loss_applies_severe_costs(self, mock_random, adult_player):
        """Event should severely decrease happiness, increase stress, decrease energy"""
        adult_player.c.ageYears = 30
        adult_player.c.happiness = 70
        adult_player.c.stress = 30
        adult_player.c.energy = 100
        result = dataLoss(adult_player)
        assert adult_player.c.happiness == 40
        assert adult_player.c.stress == 65
        assert adult_player.c.energy == 60


# ============================================================================
# RANDOMNESS TESTS
# ============================================================================

class TestEventRandomness:
    """Test that events respect random probability"""

    def test_positive_event_respects_random_probability(self, child_player):
        """Events should not trigger when random check fails"""
        child_player.c.ageYears = 10

        with patch('random.random', return_value=1.0):  # Always fail
            result = foundMoneyStreet(child_player)
            assert result is None

    def test_negative_event_respects_random_probability(self, teen_player):
        """Events should not trigger when random check fails"""
        teen_player.c.ageYears = 16

        with patch('random.random', return_value=1.0):  # Always fail
            result = spilledDrink(teen_player)
            assert result is None


# ============================================================================
# SUMMARY
# ============================================================================
"""
Test Summary:
=============

Positive Random Events (7 events):
- foundAPenny (4 tests)
- freeConcert (3 tests)
- foundMoneyStreet (2 tests)
- freeUpgrade (2 tests)
- unexpectedCompliment (2 tests)
- perfectParkingSpot (2 tests)
- rainbowSighting (2 tests)

Negative Random Events (10 events):
- spilledDrink (2 tests)
- phoneDropped (3 tests)
- birdPooped (2 tests)
- forgotWallet (2 tests)
- missedBus (2 tests)
- badHairDay (2 tests)
- caughtInRain (2 tests)
- lostKeys (2 tests)
- phoneBreakdown (2 tests)
- dataLoss (2 tests)

Randomness Tests (1 class):
- EventRandomness (2 tests)

Total: 17 random events tested with 40 test cases

Test Coverage:
- Age range validation
- Condition requirements (canDrive, etc.)
- Duplicate prevention
- Cost/stat application (positive and negative)
- Random probability
- Random outcomes (phoneDropped)
"""
