"""
Unit tests for adulthood events (ages 18+).

Tests life events, career progression, romance, marriage, parenthood, and retirement
events that occur during adulthood.

Run with: pytest tests/unit/events/test_adulthood_events.py -v
"""

import pytest
import sys
from pathlib import Path
from unittest.mock import Mock, patch, MagicMock
import random

# Imports from ws directory (setup by conftest.py)
from ws.core.models import playerClass, personClass
from ws.events.base import messageEvent, questionEvent, answerOption
from ws.events.adulthood.life_events import (
    firstApartment, workLifeBalance, forgotBirthdayCall, friendsDrifting,
    unexpectedBill, promotionOpportunity, agingParent, careerChangeDesire,
    coworkerRivalry, divorceConsideration
)
from ws.events.adulthood.romance import marriage, wedding
from ws.events.adulthood.family import haveChild, pregnant, childBorn
from types import SimpleNamespace


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

@pytest.fixture
def adult_player():
    """Create a player with adult character (age 30)"""
    player = playerClass()
    player.c = personClass()
    player.c.firstname = "Test"
    player.c.lastname = "Adult"
    player.c.ageYears = 30
    player.c.ageHours = 30 * 365 * 24
    player.c.ageDays = 30 * 365
    player.c.occupation = 'work'
    player.c.money = 5000
    player.c.energy = 100
    player.c.happiness = 50
    player.c.diamonds = 50
    player.events = set()
    player.askedQuestions = set()
    player.messageQueue = []
    player.r = []  # relationships
    player.date = "05-15"
    player.hourOfDay = 12
    return player


@pytest.fixture
def adult_with_job(adult_player):
    """Adult player with a job"""
    adult_player.c.job = SimpleNamespace(
        id='job1',
        title='Software Engineer',
        salary=80000
    )
    return adult_player


@pytest.fixture
def adult_with_partner(adult_player):
    """Adult player with romantic partner"""
    partner = personClass()
    partner.id = 'partner1'
    partner.firstname = "Jane"
    partner.lastname = "Partner"
    partner.relationship = 'partner'
    partner.affinity = 85
    
    adult_player.c.partner = partner.id
    adult_player.r.append(partner)
    return adult_player


@pytest.fixture
def adult_with_parent(adult_player):
    """Adult player with living parent"""
    parent = personClass()
    parent.id = 'parent1'
    parent.firstname = "Mom"
    parent.lastname = "Parent"
    parent.relationship = 'mother'
    parent.affinity = 70
    parent.alive = True
    
    adult_player.r.append(parent)
    return adult_player


@pytest.fixture
def adult_with_friend(adult_player):
    """Adult player with friend"""
    friend = personClass()
    friend.id = 'friend1'
    friend.firstname = "Best"
    friend.lastname = "Friend"
    friend.relationship = 'friend'
    friend.affinity = 60
    
    adult_player.r.append(friend)
    return adult_player


# ============================================================================
# FIRST APARTMENT TESTS (ages 18-25)
# ============================================================================

def test_first_apartment_triggers_at_correct_age(adult_player):
    """Test that firstApartment only triggers for ages 18-25"""
    # Age 20 - should be possible
    adult_player.c.ageYears = 20
    with patch('random.random', return_value=0):  # Force trigger
        result = firstApartment(adult_player)
        assert result is not None or result is None  # Can trigger
    
    # Age 30 - should not trigger (too old)
    adult_player.c.ageYears = 30
    with patch('random.random', return_value=0):
        result = firstApartment(adult_player)
        assert result is None
    
    # Age 17 - should not trigger (too young)
    adult_player.c.ageYears = 17
    with patch('random.random', return_value=0):
        result = firstApartment(adult_player)
        assert result is None


def test_first_apartment_not_duplicate(adult_player):
    """Test that firstApartment doesn't trigger if already asked"""
    adult_player.c.ageYears = 20
    adult_player.askedQuestions.add('firstApartment')
    
    with patch('random.random', return_value=0):
        result = firstApartment(adult_player)
        assert result is None


def test_first_apartment_choice_consequences(adult_player):
    """Test different answer choices lead to different outcomes"""
    adult_player.c.ageYears = 20
    initial_money = adult_player.c.money
    initial_happiness = adult_player.c.happiness
    
    # Test "Sign the lease" option
    response = {'option': 'Sign the lease!0'}
    firstApartment(adult_player, type='answer', response=response)
    
    assert adult_player.c.money == initial_money - 500  # Cost applied
    assert adult_player.c.happiness == initial_happiness + 20  # Happiness boost
    assert len(adult_player.messageQueue) > 0


def test_first_apartment_applies_costs_correctly(adult_player):
    """Test that costs are applied correctly for different choices"""
    adult_player.c.ageYears = 20
    
    # Test roommate option (lower cost)
    initial_money = adult_player.c.money
    response = {'option': 'Find a roommate first2'}
    firstApartment(adult_player, type='answer', response=response)
    
    assert adult_player.c.money == initial_money - 250


# ============================================================================
# WORK-LIFE BALANCE TESTS (ages 25-50)
# ============================================================================

def test_work_life_balance_requires_job(adult_with_job):
    """Test that workLifeBalance requires a job"""
    # With job - can trigger
    adult_with_job.c.ageYears = 30
    with patch('random.random', return_value=0):
        result = workLifeBalance(adult_with_job)
        assert isinstance(result, questionEvent) or result is None
    
    # Without job - cannot trigger
    adult_with_job.c.job = None
    with patch('random.random', return_value=0):
        result = workLifeBalance(adult_with_job)
        assert result is None


def test_work_life_balance_affects_friend_affinity(adult_with_job, adult_with_friend):
    """Test that choosing overtime reduces friend affinity"""
    # Add friend to adult_with_job
    friend = adult_with_friend.r[0]
    adult_with_job.r.append(friend)
    
    initial_affinity = friend.affinity
    
    # Choose overtime (cancels plans with friends)
    response = {'option': 'Take the overtime0'}
    workLifeBalance(adult_with_job, type='answer', response=response)
    
    # Friend affinity should decrease
    assert friend.affinity < initial_affinity


def test_work_life_balance_money_reward(adult_with_job):
    """Test that taking overtime increases money"""
    initial_money = adult_with_job.c.money
    
    response = {'option': 'Take the overtime0'}
    workLifeBalance(adult_with_job, type='answer', response=response)
    
    assert adult_with_job.c.money == initial_money + 200


# ============================================================================
# UNEXPECTED BILL TESTS (ages 20-65)
# ============================================================================

def test_unexpected_bill_deducts_money(adult_player):
    """Test that unexpectedBill deducts money from player"""
    adult_player.c.ageYears = 30
    initial_money = adult_player.c.money
    
    with patch('random.random', return_value=0):
        with patch('random.randint', return_value=500):
            result = unexpectedBill(adult_player)
            
            if result:
                assert adult_player.c.money == initial_money - 500
                assert adult_player.c.happiness < 50  # Should reduce happiness


def test_unexpected_bill_not_duplicate(adult_player):
    """Test that unexpectedBill doesn't trigger twice"""
    adult_player.c.ageYears = 30
    adult_player.events.add('unexpectedBill')
    
    with patch('random.random', return_value=0):
        result = unexpectedBill(adult_player)
        assert result is None


# ============================================================================
# PROMOTION OPPORTUNITY TESTS (ages 25-55)
# ============================================================================

def test_promotion_opportunity_requires_job(adult_with_job):
    """Test that promotionOpportunity requires a job"""
    adult_with_job.c.ageYears = 35
    
    # With job
    with patch('random.random', return_value=0):
        result = promotionOpportunity(adult_with_job)
        assert isinstance(result, questionEvent) or result is None
    
    # Without job
    adult_with_job.c.job = None
    with patch('random.random', return_value=0):
        result = promotionOpportunity(adult_with_job)
        assert result is None


def test_promotion_opportunity_money_vs_happiness_tradeoff(adult_with_job):
    """Test that accepting promotion gives money but reduces happiness"""
    initial_money = adult_with_job.c.money
    initial_happiness = adult_with_job.c.happiness
    
    # Accept promotion
    response = {'option': 'Yes, take promotion0'}
    promotionOpportunity(adult_with_job, type='answer', response=response)
    
    assert adult_with_job.c.money > initial_money  # More money
    assert adult_with_job.c.happiness < initial_happiness  # Less happiness


def test_promotion_opportunity_negotiation_costs_diamonds(adult_with_job):
    """Test that negotiation option uses diamonds"""
    initial_diamonds = adult_with_job.c.diamonds
    
    # Negotiate (requires diamonds)
    response = {'option': 'Negotiate better terms2'}
    promotionOpportunity(adult_with_job, type='answer', response=response)
    
    # Diamonds should be deducted (handled by event system)
    # Money and happiness should both increase
    assert adult_with_job.c.money > 5000


# ============================================================================
# AGING PARENT TESTS (ages 35-60)
# ============================================================================

def test_aging_parent_requires_living_parent(adult_with_parent):
    """Test that agingParent requires a living parent"""
    adult_with_parent.c.ageYears = 45
    
    # With living parent
    with patch('random.random', return_value=0):
        result = agingParent(adult_with_parent)
        assert isinstance(result, questionEvent) or result is None
    
    # Without parent
    adult_with_parent.r = []
    with patch('random.random', return_value=0):
        result = agingParent(adult_with_parent)
        assert result is None


def test_aging_parent_visiting_increases_affinity(adult_with_parent):
    """Test that visiting parent increases affinity"""
    parent = adult_with_parent.r[0]
    initial_affinity = parent.affinity
    
    # Visit more often
    response = {'option': 'Visit more often0'}
    agingParent(adult_with_parent, type='answer', response=response)
    
    assert parent.affinity > initial_affinity


def test_aging_parent_hiring_help_costs_money(adult_with_parent):
    """Test that hiring outside help costs money"""
    initial_money = adult_with_parent.c.money
    
    # Hire help
    response = {'option': 'Hire outside help1'}
    agingParent(adult_with_parent, type='answer', response=response)
    
    # Money should be deducted (500)
    # Note: Cost deduction happens in event system, we just verify the choice was made
    assert len(adult_with_parent.messageQueue) > 0


# ============================================================================
# CAREER CHANGE TESTS (ages 30-50)
# ============================================================================

def test_career_change_requires_job(adult_with_job):
    """Test that careerChangeDesire requires a job"""
    adult_with_job.c.ageYears = 40
    
    # With job
    with patch('random.random', return_value=0):
        result = careerChangeDesire(adult_with_job)
        assert isinstance(result, questionEvent) or result is None
    
    # Without job
    adult_with_job.c.job = None
    with patch('random.random', return_value=0):
        result = careerChangeDesire(adult_with_job)
        assert result is None


def test_career_change_quit_increases_happiness(adult_with_job):
    """Test that quitting to pursue passion increases happiness"""
    initial_happiness = adult_with_job.c.happiness
    
    # Quit and pursue passion
    response = {'option': 'Quit and pursue passion0'}
    careerChangeDesire(adult_with_job, type='answer', response=response)
    
    assert adult_with_job.c.happiness > initial_happiness


def test_career_change_school_costs_money(adult_with_job):
    """Test that going back to school costs significant money"""
    # Go back to school option
    response = {'option': 'Go back to school3'}
    careerChangeDesire(adult_with_job, type='answer', response=response)
    
    # Should have message about enrolling
    assert len(adult_with_job.messageQueue) > 0


# ============================================================================
# MARRIAGE TESTS (ages 18+)
# ============================================================================

def test_marriage_requires_partner_with_high_affinity(adult_with_partner):
    """Test that marriage requires partner with affinity > 80"""
    adult_with_partner.c.ageYears = 28
    
    # Partner with high affinity (85)
    with patch('random.random', return_value=0):
        result = marriage(adult_with_partner)
        assert isinstance(result, questionEvent) or result is None
    
    # Lower partner affinity
    adult_with_partner.r[0].affinity = 50
    with patch('random.random', return_value=0):
        result = marriage(adult_with_partner)
        assert result is None


def test_marriage_accepting_sets_engaged(adult_with_partner):
    """Test that accepting marriage proposal sets engaged status"""
    # Accept proposal
    response = {'option': 'Yes'}
    marriage(adult_with_partner, type='answer', response=response)
    
    assert adult_with_partner.c.engaged == True


def test_marriage_not_duplicate(adult_with_partner):
    """Test that marriage doesn't ask twice"""
    adult_with_partner.askedQuestions.add('marriage')
    
    with patch('random.random', return_value=0):
        result = marriage(adult_with_partner)
        assert result is None


# ============================================================================
# FAMILY PLANNING TESTS (ages 18-50)
# ============================================================================

def test_have_child_requires_partner(adult_with_partner):
    """Test that haveChild requires a partner with high affinity"""
    adult_with_partner.c.ageYears = 30
    
    # With partner (affinity 85)
    with patch('random.random', return_value=0):
        result = haveChild(adult_with_partner)
        assert isinstance(result, questionEvent) or result is None
    
    # Without partner
    adult_with_partner.c.partner = False
    with patch('random.random', return_value=0):
        result = haveChild(adult_with_partner)
        assert result is None


def test_have_child_accepting_sets_trying_flag(adult_with_partner):
    """Test that accepting child decision sets tryingForChild flag"""
    # Accept having a child
    response = {'option': 'Yes'}
    haveChild(adult_with_partner, type='answer', response=response)
    
    assert adult_with_partner.c.tryingForChild == True


def test_pregnant_requires_trying_for_child(adult_with_partner):
    """Test that pregnancy requires tryingForChild flag"""
    adult_with_partner.c.ageYears = 30
    
    # Not trying for child
    adult_with_partner.c.tryingForChild = False
    with patch('random.random', return_value=0):
        result = pregnant(adult_with_partner)
        assert result is None
    
    # Trying for child
    adult_with_partner.c.tryingForChild = True
    with patch('random.random', return_value=0):
        result = pregnant(adult_with_partner)
        # Can trigger if random check passes
        assert result is None or isinstance(result, messageEvent)


# ============================================================================
# DIVORCE CONSIDERATION TESTS (ages 25-65)
# ============================================================================

def test_divorce_requires_troubled_relationship(adult_with_partner):
    """Test that divorceConsideration requires partner with low affinity"""
    adult_with_partner.c.ageYears = 35
    
    # High affinity - should not trigger
    adult_with_partner.r[0].affinity = 85
    with patch('random.random', return_value=0):
        result = divorceConsideration(adult_with_partner)
        assert result is None
    
    # Low affinity - can trigger
    adult_with_partner.r[0].affinity = 15
    with patch('random.random', return_value=0):
        result = divorceConsideration(adult_with_partner)
        assert isinstance(result, questionEvent) or result is None


def test_divorce_therapy_increases_affinity(adult_with_partner):
    """Test that couples therapy increases partner affinity"""
    # Set low affinity
    partner = adult_with_partner.r[0]
    partner.affinity = 15
    initial_affinity = partner.affinity
    
    # Choose therapy
    response = {'option': 'Couples therapy0'}
    divorceConsideration(adult_with_partner, type='answer', response=response)
    
    assert partner.affinity > initial_affinity


def test_divorce_filing_sets_affinity_to_zero(adult_with_partner):
    """Test that filing for divorce sets partner affinity to 0"""
    partner = adult_with_partner.r[0]
    partner.affinity = 15
    
    # File for divorce
    response = {'option': 'Divorce2'}
    divorceConsideration(adult_with_partner, type='answer', response=response)
    
    assert partner.affinity == 0


# ============================================================================
# FORGOT BIRTHDAY CALL TESTS (ages 22-65)
# ============================================================================

def test_forgot_birthday_reduces_family_affinity(adult_with_parent):
    """Test that forgotBirthdayCall reduces family member affinity"""
    adult_with_parent.c.ageYears = 30
    parent = adult_with_parent.r[0]
    initial_affinity = parent.affinity
    
    with patch('random.random', return_value=0):
        result = forgotBirthdayCall(adult_with_parent)
        
        if result:
            assert parent.affinity < initial_affinity


def test_forgot_birthday_not_duplicate(adult_with_parent):
    """Test that forgotBirthdayCall doesn't trigger twice"""
    adult_with_parent.c.ageYears = 30
    adult_with_parent.events.add('forgotBirthdayCall')
    
    with patch('random.random', return_value=0):
        result = forgotBirthdayCall(adult_with_parent)
        assert result is None


# ============================================================================
# FRIENDS DRIFTING TESTS (ages 25-40)
# ============================================================================

def test_friends_drifting_triggers_at_correct_age(adult_player):
    """Test that friendsDrifting only triggers for ages 25-40"""
    # Age 30 - should be possible
    adult_player.c.ageYears = 30
    with patch('random.random', return_value=0):
        result = friendsDrifting(adult_player)
        assert isinstance(result, messageEvent) or result is None
    
    # Age 45 - should not trigger (too old)
    adult_player.c.ageYears = 45
    with patch('random.random', return_value=0):
        result = friendsDrifting(adult_player)
        assert result is None


# ============================================================================
# COWORKER RIVALRY TESTS (ages 25-60)
# ============================================================================

def test_coworker_rivalry_requires_job(adult_with_job):
    """Test that coworkerRivalry requires a job"""
    adult_with_job.c.ageYears = 35
    
    # With job
    with patch('random.random', return_value=0):
        result = coworkerRivalry(adult_with_job)
        assert isinstance(result, messageEvent) or result is None
    
    # Without job
    adult_with_job.c.job = None
    with patch('random.random', return_value=0):
        result = coworkerRivalry(adult_with_job)
        assert result is None


def test_coworker_rivalry_not_duplicate(adult_with_job):
    """Test that coworkerRivalry doesn't trigger twice"""
    adult_with_job.c.ageYears = 35
    adult_with_job.events.add('coworkerRivalry')
    
    with patch('random.random', return_value=0):
        result = coworkerRivalry(adult_with_job)
        assert result is None
