"""
Unit tests for BaoLife stat calculations and updates.

Tests core stat functions like updateAge, getPeakEnergy, updateDeathChance, etc.
"""
import sys
import os
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../..')))

import pytest
from tests.fixtures.player_fixtures import (
    create_newborn_player, create_child_player, create_adult_player
)

# Import functions from their new locations
from stats.stats_manager import updateAge, getPeakEnergy
from health.health_manager import updateDeathChance, getWeightType
from character.character_manager import updateBio, get_person, get_relationship
from relationships.relationship_manager import updateAffinity
from core.models import personClass


class TestAgeUpdates:
    """Tests for age-related functions."""

    def test_update_age_newborn(self):
        """Test age updates for newborn (ageHours increments)."""
        player = create_newborn_player()
        player.c.ageDays = 0
        player.c.ageHours = 0
        player.hourOfDay = 0

        result = updateAge(player)

        # First call increments ageHours from 0 to 1
        assert player.c.ageHours == 1
        # ageDays only increments when ageHours is divisible by 24
        assert player.c.ageDays == 0

        # After 23 more calls, should increment ageDays
        for _ in range(23):
            updateAge(player)

        assert player.c.ageHours == 24
        assert player.c.ageDays == 1

    def test_update_age_daily_increment(self):
        """Test that age increments daily after 24 hours."""
        player = create_child_player(age_years=8)
        initial_days = player.c.ageDays
        initial_hours = player.c.ageHours

        # Set ageHours to be 23 hours into a day
        player.c.ageHours = (initial_hours // 24) * 24 + 23
        player.hourOfDay = 23

        updateAge(player)

        # After one more hour (making 24), ageDays should increment
        assert player.c.ageHours == (initial_hours // 24) * 24 + 24
        assert player.c.ageDays == initial_days + 1

    def test_update_death_chance_young(self):
        """Test death chance is low for young people."""
        player = create_child_player(age_years=10)

        death_chance = updateDeathChance(player.c)

        assert death_chance < 0.01  # Less than 1%

    def test_update_death_chance_old(self):
        """Test death chance increases with age."""
        young = create_child_player(age_years=20)
        old = create_adult_player(age_years=80)

        young_death_chance = updateDeathChance(young.c)
        old_death_chance = updateDeathChance(old.c)

        assert old_death_chance > young_death_chance


class TestEnergyStats:
    """Tests for energy-related functions."""

    def test_get_peak_energy_child(self):
        """Test peak energy calculation for children."""
        player = create_child_player(age_years=8)
        player.c.peakEnergy = 0

        getPeakEnergy(player.c)

        # Children should have high peak energy (actually, base case with no activities is 0)
        assert player.c.peakEnergy >= 0

    def test_get_peak_energy_adult(self):
        """Test peak energy calculation for adults."""
        player = create_adult_player(age_years=40)
        player.c.peakEnergy = 0

        getPeakEnergy(player.c)

        # Base case with no activities should be 0
        assert player.c.peakEnergy >= 0

    def test_energy_bounds(self):
        """Test that energy stays within valid bounds."""
        person = personClass()
        person.energy = 150  # Over limit

        # Energy should be capped at 100
        person.energy = min(person.energy, 100)
        assert person.energy == 100


class TestWeightCalculations:
    """Tests for weight-related functions."""

    def test_get_weight_type_underweight(self):
        """Test weight classification for underweight."""
        weight_type = getWeightType(40)  # Very low weight

        assert weight_type == "Underweight"

    def test_get_weight_type_normal(self):
        """Test weight classification for normal weight."""
        weight_type = getWeightType(60)  # Normal weight (50-70 range)

        assert weight_type == "Normal"

    def test_get_weight_type_overweight(self):
        """Test weight classification for overweight."""
        weight_type = getWeightType(90)  # Higher weight

        assert weight_type in ["Overweight", "Obese"]


class TestBioUpdates:
    """Tests for biological stat updates."""

    def test_update_bio_hunger_increases(self):
        """Test that hunger increases over time."""
        player = create_adult_player()
        player.c.hunger = 0
        player.c.weightType = 'Normal'

        initial_hunger = player.c.hunger
        player = updateBio(player)

        # Hunger should increase (or at least not decrease significantly)
        assert player.c.hunger >= initial_hunger - 5

    def test_update_bio_thirst_increases(self):
        """Test that thirst increases over time."""
        player = create_adult_player()
        player.c.thirst = 0
        player.c.weightType = 'Normal'

        initial_thirst = player.c.thirst
        player = updateBio(player)

        # Thirst should increase (or at least not decrease significantly)
        assert player.c.thirst >= initial_thirst - 5


class TestRelationshipStats:
    """Tests for relationship stat functions."""

    def test_update_affinity_positive(self):
        """Test increasing affinity."""
        player = create_adult_player()

        # Add a relationship
        friend = personClass()
        friend.id = 'test_friend_123'
        friend.affinity = 50
        player.r.append(friend)

        player = updateAffinity(player, 'test_friend_123', 10)

        assert player.r[0].affinity == 60

    def test_update_affinity_negative(self):
        """Test decreasing affinity."""
        player = create_adult_player()

        # Add a relationship
        friend = personClass()
        friend.id = 'test_friend_456'
        friend.affinity = 50
        player.r.append(friend)

        player = updateAffinity(player, 'test_friend_456', -20)

        assert player.r[0].affinity == 30

    def test_get_person_by_id(self):
        """Test finding a person by ID."""
        player = create_adult_player()

        # Add a relationship
        friend = personClass()
        friend.id = 'unique_id_789'
        friend.firstname = 'TestFriend'
        player.r.append(friend)

        found = get_person(player, 'unique_id_789')

        assert found is not None
        assert found.firstname == 'TestFriend'

    def test_get_relationship_by_type(self):
        """Test finding a relationship by type."""
        player = create_adult_player()

        # Add a mother
        mother = personClass()
        mother.relationships = ['mother']
        mother.firstname = 'Mom'
        player.r.append(mother)

        found = get_relationship(player, 'mother')

        assert found is not None
        assert found.firstname == 'Mom'


class TestMoneyAndFinances:
    """Tests for money and financial functions."""

    def test_handle_finances_basic(self):
        """Test basic finance handling."""
        # Import from stats_manager, not health_manager
        from stats.stats_manager import handleFinances

        person = personClass()
        person.money = 1000
        person.job = None

        handleFinances(person)

        # Money should still be present
        assert hasattr(person, 'money')

    def test_money_cannot_go_negative(self):
        """Test that money stays non-negative after transactions."""
        player = create_adult_player()
        player.c.money = 100

        # Simulate spending more than available
        cost = 150
        if player.c.money >= cost:
            player.c.money -= cost
        else:
            # Don't allow negative money
            player.c.money = 0

        assert player.c.money >= 0
